- 
                Notifications
    You must be signed in to change notification settings 
- Fork 13.9k
Mir-Opt for copying enums with large discrepancies #85158
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
          
     Merged
      
      
    
  
     Merged
                    Changes from all commits
      Commits
    
    
            Show all changes
          
          
            9 commits
          
        
        Select commit
          Hold shift + click to select a range
      
      18144b6
              
                Create initial version of opt
              
              
                JulianKnodt 96db5e9
              
                Add comments
              
              
                JulianKnodt 33b4d20
              
                Clean up MIR transform
              
              
                JulianKnodt f7cbf2e
              
                Update with comments
              
              
                JulianKnodt 3e97cef
              
                Set mir-opt-level = 0 on some codegen tests
              
              
                JulianKnodt 5d9f514
              
                Rm allocation in candidate
              
              
                JulianKnodt 610e1a1
              
                Add tag for ignoring wasm
              
              
                JulianKnodt 15f4eec
              
                Leave FIXME for wasm layout difference.
              
              
                JulianKnodt 15d4728
              
                Add de-init to destination place
              
              
                JulianKnodt File filter
Filter by extension
Conversations
          Failed to load comments.   
        
        
          
      Loading
        
  Jump to
        
          Jump to file
        
      
      
          Failed to load files.   
        
        
          
      Loading
        
  Diff view
Diff view
There are no files selected for viewing
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,298 @@ | ||
| use crate::rustc_middle::ty::util::IntTypeExt; | ||
| use crate::MirPass; | ||
| use rustc_data_structures::fx::FxHashMap; | ||
| use rustc_middle::mir::interpret::AllocId; | ||
| use rustc_middle::mir::*; | ||
| use rustc_middle::ty::{self, AdtDef, Const, ParamEnv, Ty, TyCtxt}; | ||
| use rustc_session::Session; | ||
| use rustc_target::abi::{HasDataLayout, Size, TagEncoding, Variants}; | ||
|  | ||
| /// A pass that seeks to optimize unnecessary moves of large enum types, if there is a large | ||
| /// enough discrepancy between them. | ||
| /// | ||
| /// i.e. If there is are two variants: | ||
| /// ``` | ||
| /// enum Example { | ||
| /// Small, | ||
| /// Large([u32; 1024]), | ||
| /// } | ||
| /// ``` | ||
| /// Instead of emitting moves of the large variant, | ||
| /// Perform a memcpy instead. | ||
| /// Based off of [this HackMD](https://hackmd.io/@ft4bxUsFT5CEUBmRKYHr7w/rJM8BBPzD). | ||
| /// | ||
| /// In summary, what this does is at runtime determine which enum variant is active, | ||
| /// and instead of copying all the bytes of the largest possible variant, | ||
| /// copy only the bytes for the currently active variant. | ||
| pub struct EnumSizeOpt { | ||
| pub(crate) discrepancy: u64, | ||
| } | ||
|  | ||
| impl<'tcx> MirPass<'tcx> for EnumSizeOpt { | ||
| fn is_enabled(&self, sess: &Session) -> bool { | ||
| sess.opts.unstable_opts.unsound_mir_opts || sess.mir_opt_level() >= 3 | ||
| } | ||
| fn run_pass(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | ||
| // NOTE: This pass may produce different MIR based on the alignment of the target | ||
| // platform, but it will still be valid. | ||
| self.optim(tcx, body); | ||
| } | ||
| } | ||
|  | ||
| impl EnumSizeOpt { | ||
| fn candidate<'tcx>( | ||
| &self, | ||
| tcx: TyCtxt<'tcx>, | ||
| param_env: ParamEnv<'tcx>, | ||
| ty: Ty<'tcx>, | ||
| alloc_cache: &mut FxHashMap<Ty<'tcx>, AllocId>, | ||
| ) -> Option<(AdtDef<'tcx>, usize, AllocId)> { | ||
| let adt_def = match ty.kind() { | ||
| ty::Adt(adt_def, _substs) if adt_def.is_enum() => adt_def, | ||
| _ => return None, | ||
| }; | ||
| let layout = tcx.layout_of(param_env.and(ty)).ok()?; | ||
| let variants = match &layout.variants { | ||
| Variants::Single { .. } => return None, | ||
| Variants::Multiple { tag_encoding, .. } | ||
| if matches!(tag_encoding, TagEncoding::Niche { .. }) => | ||
| { | ||
| return None; | ||
| } | ||
| Variants::Multiple { variants, .. } if variants.len() <= 1 => return None, | ||
| Variants::Multiple { variants, .. } => variants, | ||
| }; | ||
| let min = variants.iter().map(|v| v.size).min().unwrap(); | ||
| let max = variants.iter().map(|v| v.size).max().unwrap(); | ||
| if max.bytes() - min.bytes() < self.discrepancy { | ||
| return None; | ||
| } | ||
|  | ||
| let num_discrs = adt_def.discriminants(tcx).count(); | ||
| if variants.iter_enumerated().any(|(var_idx, _)| { | ||
| let discr_for_var = adt_def.discriminant_for_variant(tcx, var_idx).val; | ||
| (discr_for_var > usize::MAX as u128) || (discr_for_var as usize >= num_discrs) | ||
| }) { | ||
| return None; | ||
| } | ||
| if let Some(alloc_id) = alloc_cache.get(&ty) { | ||
| return Some((*adt_def, num_discrs, *alloc_id)); | ||
| } | ||
|  | ||
| let data_layout = tcx.data_layout(); | ||
| let ptr_sized_int = data_layout.ptr_sized_integer(); | ||
| let target_bytes = ptr_sized_int.size().bytes() as usize; | ||
| let mut data = vec![0; target_bytes * num_discrs]; | ||
| macro_rules! encode_store { | ||
| ($curr_idx: expr, $endian: expr, $bytes: expr) => { | ||
| let bytes = match $endian { | ||
| rustc_target::abi::Endian::Little => $bytes.to_le_bytes(), | ||
| rustc_target::abi::Endian::Big => $bytes.to_be_bytes(), | ||
| }; | ||
| for (i, b) in bytes.into_iter().enumerate() { | ||
| data[$curr_idx + i] = b; | ||
| } | ||
| }; | ||
| } | ||
|  | ||
| for (var_idx, layout) in variants.iter_enumerated() { | ||
| let curr_idx = | ||
| target_bytes * adt_def.discriminant_for_variant(tcx, var_idx).val as usize; | ||
| let sz = layout.size; | ||
| match ptr_sized_int { | ||
| rustc_target::abi::Integer::I32 => { | ||
| encode_store!(curr_idx, data_layout.endian, sz.bytes() as u32); | ||
| } | ||
| rustc_target::abi::Integer::I64 => { | ||
| encode_store!(curr_idx, data_layout.endian, sz.bytes()); | ||
| } | ||
| _ => unreachable!(), | ||
| }; | ||
| } | ||
| let alloc = interpret::Allocation::from_bytes( | ||
| data, | ||
| tcx.data_layout.ptr_sized_integer().align(&tcx.data_layout).abi, | ||
| Mutability::Not, | ||
| ); | ||
| let alloc = tcx.create_memory_alloc(tcx.intern_const_alloc(alloc)); | ||
| Some((*adt_def, num_discrs, *alloc_cache.entry(ty).or_insert(alloc))) | ||
| } | ||
| fn optim<'tcx>(&self, tcx: TyCtxt<'tcx>, body: &mut Body<'tcx>) { | ||
| let mut alloc_cache = FxHashMap::default(); | ||
| let body_did = body.source.def_id(); | ||
| let param_env = tcx.param_env(body_did); | ||
|  | ||
| let blocks = body.basic_blocks.as_mut(); | ||
| let local_decls = &mut body.local_decls; | ||
|  | ||
| for bb in blocks { | ||
| bb.expand_statements(|st| { | ||
| if let StatementKind::Assign(box ( | ||
| lhs, | ||
| Rvalue::Use(Operand::Copy(rhs) | Operand::Move(rhs)), | ||
| )) = &st.kind | ||
| { | ||
| let ty = lhs.ty(local_decls, tcx).ty; | ||
|  | ||
| let source_info = st.source_info; | ||
| let span = source_info.span; | ||
|  | ||
| let (adt_def, num_variants, alloc_id) = | ||
| self.candidate(tcx, param_env, ty, &mut alloc_cache)?; | ||
| let alloc = tcx.global_alloc(alloc_id).unwrap_memory(); | ||
|  | ||
| let tmp_ty = tcx.mk_ty(ty::Array( | ||
| tcx.types.usize, | ||
| Const::from_usize(tcx, num_variants as u64), | ||
| )); | ||
|  | ||
| let size_array_local = local_decls.push(LocalDecl::new(tmp_ty, span)); | ||
| let store_live = Statement { | ||
| source_info, | ||
| kind: StatementKind::StorageLive(size_array_local), | ||
| }; | ||
|  | ||
| let place = Place::from(size_array_local); | ||
| let constant_vals = Constant { | ||
| span, | ||
| user_ty: None, | ||
| literal: ConstantKind::Val( | ||
| interpret::ConstValue::ByRef { alloc, offset: Size::ZERO }, | ||
| tmp_ty, | ||
| ), | ||
| }; | ||
| let rval = Rvalue::Use(Operand::Constant(box (constant_vals))); | ||
|  | ||
| let const_assign = | ||
| Statement { source_info, kind: StatementKind::Assign(box (place, rval)) }; | ||
|  | ||
| let discr_place = Place::from( | ||
| local_decls | ||
| .push(LocalDecl::new(adt_def.repr().discr_type().to_ty(tcx), span)), | ||
| ); | ||
|  | ||
| let store_discr = Statement { | ||
| source_info, | ||
| kind: StatementKind::Assign(box (discr_place, Rvalue::Discriminant(*rhs))), | ||
| }; | ||
|  | ||
| let discr_cast_place = | ||
| Place::from(local_decls.push(LocalDecl::new(tcx.types.usize, span))); | ||
|  | ||
| let cast_discr = Statement { | ||
| source_info, | ||
| kind: StatementKind::Assign(box ( | ||
| discr_cast_place, | ||
| Rvalue::Cast( | ||
| CastKind::IntToInt, | ||
| Operand::Copy(discr_place), | ||
| tcx.types.usize, | ||
| ), | ||
| )), | ||
| }; | ||
|  | ||
| let size_place = | ||
| Place::from(local_decls.push(LocalDecl::new(tcx.types.usize, span))); | ||
|  | ||
| let store_size = Statement { | ||
| source_info, | ||
| kind: StatementKind::Assign(box ( | ||
| size_place, | ||
| Rvalue::Use(Operand::Copy(Place { | ||
| local: size_array_local, | ||
| projection: tcx.intern_place_elems(&[PlaceElem::Index( | ||
| discr_cast_place.local, | ||
| )]), | ||
| })), | ||
| )), | ||
| }; | ||
|  | ||
| let dst = | ||
| Place::from(local_decls.push(LocalDecl::new(tcx.mk_mut_ptr(ty), span))); | ||
|  | ||
| let dst_ptr = Statement { | ||
| source_info, | ||
| kind: StatementKind::Assign(box ( | ||
| dst, | ||
| Rvalue::AddressOf(Mutability::Mut, *lhs), | ||
| )), | ||
| }; | ||
|  | ||
| let dst_cast_ty = tcx.mk_mut_ptr(tcx.types.u8); | ||
| let dst_cast_place = | ||
| Place::from(local_decls.push(LocalDecl::new(dst_cast_ty, span))); | ||
|  | ||
| let dst_cast = Statement { | ||
| source_info, | ||
| kind: StatementKind::Assign(box ( | ||
| dst_cast_place, | ||
| Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(dst), dst_cast_ty), | ||
| )), | ||
| }; | ||
|  | ||
| let src = | ||
| Place::from(local_decls.push(LocalDecl::new(tcx.mk_imm_ptr(ty), span))); | ||
|  | ||
| let src_ptr = Statement { | ||
| source_info, | ||
| kind: StatementKind::Assign(box ( | ||
| src, | ||
| Rvalue::AddressOf(Mutability::Not, *rhs), | ||
| )), | ||
| }; | ||
|  | ||
| let src_cast_ty = tcx.mk_imm_ptr(tcx.types.u8); | ||
| let src_cast_place = | ||
| Place::from(local_decls.push(LocalDecl::new(src_cast_ty, span))); | ||
|  | ||
| let src_cast = Statement { | ||
| source_info, | ||
| kind: StatementKind::Assign(box ( | ||
| src_cast_place, | ||
| Rvalue::Cast(CastKind::PtrToPtr, Operand::Copy(src), src_cast_ty), | ||
| )), | ||
| }; | ||
|  | ||
| let deinit_old = | ||
| Statement { source_info, kind: StatementKind::Deinit(box dst) }; | ||
|  | ||
| let copy_bytes = Statement { | ||
| source_info, | ||
| kind: StatementKind::Intrinsic( | ||
| box NonDivergingIntrinsic::CopyNonOverlapping(CopyNonOverlapping { | ||
| src: Operand::Copy(src_cast_place), | ||
| dst: Operand::Copy(dst_cast_place), | ||
| count: Operand::Copy(size_place), | ||
| }), | ||
| ), | ||
| }; | ||
|  | ||
| let store_dead = Statement { | ||
| source_info, | ||
| kind: StatementKind::StorageDead(size_array_local), | ||
| }; | ||
| let iter = [ | ||
| store_live, | ||
| const_assign, | ||
| store_discr, | ||
| cast_discr, | ||
| store_size, | ||
| dst_ptr, | ||
| dst_cast, | ||
| src_ptr, | ||
| src_cast, | ||
| deinit_old, | ||
| copy_bytes, | ||
| store_dead, | ||
| ] | ||
| .into_iter(); | ||
|  | ||
| st.make_nop(); | ||
| Some(iter) | ||
| } else { | ||
| None | ||
| } | ||
| }); | ||
| } | ||
| } | ||
| } | ||
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              
  
    
      This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
      Learn more about bidirectional Unicode characters
    
  
  
    
              | Original file line number | Diff line number | Diff line change | 
|---|---|---|
| @@ -0,0 +1,68 @@ | ||
| - // MIR for `cand` before EnumSizeOpt | ||
| + // MIR for `cand` after EnumSizeOpt | ||
|  | ||
| fn cand() -> Candidate { | ||
| let mut _0: Candidate; // return place in scope 0 at $DIR/enum_opt.rs:+0:18: +0:27 | ||
| let mut _1: Candidate; // in scope 0 at $DIR/enum_opt.rs:+1:7: +1:12 | ||
| let mut _2: Candidate; // in scope 0 at $DIR/enum_opt.rs:+2:7: +2:34 | ||
| let mut _3: [u8; 8196]; // in scope 0 at $DIR/enum_opt.rs:+2:24: +2:33 | ||
| + let mut _4: [usize; 2]; // in scope 0 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + let mut _5: isize; // in scope 0 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + let mut _6: usize; // in scope 0 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + let mut _7: usize; // in scope 0 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + let mut _8: *mut Candidate; // in scope 0 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + let mut _9: *mut u8; // in scope 0 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + let mut _10: *const Candidate; // in scope 0 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + let mut _11: *const u8; // in scope 0 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + let mut _12: [usize; 2]; // in scope 0 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + let mut _13: isize; // in scope 0 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + let mut _14: usize; // in scope 0 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + let mut _15: usize; // in scope 0 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + let mut _16: *mut Candidate; // in scope 0 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + let mut _17: *mut u8; // in scope 0 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + let mut _18: *const Candidate; // in scope 0 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + let mut _19: *const u8; // in scope 0 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| scope 1 { | ||
| debug a => _1; // in scope 1 at $DIR/enum_opt.rs:+1:7: +1:12 | ||
| } | ||
|  | ||
| bb0: { | ||
| StorageLive(_1); // scope 0 at $DIR/enum_opt.rs:+1:7: +1:12 | ||
| _1 = Candidate::Small(const 1_u8); // scope 0 at $DIR/enum_opt.rs:+1:15: +1:34 | ||
| StorageLive(_2); // scope 1 at $DIR/enum_opt.rs:+2:7: +2:34 | ||
| StorageLive(_3); // scope 1 at $DIR/enum_opt.rs:+2:24: +2:33 | ||
| _3 = [const 1_u8; 8196]; // scope 1 at $DIR/enum_opt.rs:+2:24: +2:33 | ||
| _2 = Candidate::Large(move _3); // scope 1 at $DIR/enum_opt.rs:+2:7: +2:34 | ||
| StorageDead(_3); // scope 1 at $DIR/enum_opt.rs:+2:33: +2:34 | ||
| - _1 = move _2; // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + StorageLive(_4); // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + _4 = const [2_usize, 8197_usize]; // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + _5 = discriminant(_2); // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + _6 = _5 as usize (IntToInt); // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + _7 = _4[_6]; // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + _8 = &raw mut _1; // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + _9 = _8 as *mut u8 (PtrToPtr); // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + _10 = &raw const _2; // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + _11 = _10 as *const u8 (PtrToPtr); // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + Deinit(_8); // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + copy_nonoverlapping(dst = _9, src = _11, count = _7); // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| + StorageDead(_4); // scope 1 at $DIR/enum_opt.rs:+2:3: +2:34 | ||
| StorageDead(_2); // scope 1 at $DIR/enum_opt.rs:+2:33: +2:34 | ||
| - _0 = move _1; // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + StorageLive(_12); // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + _12 = const [2_usize, 8197_usize]; // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + _13 = discriminant(_1); // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + _14 = _13 as usize (IntToInt); // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + _15 = _12[_14]; // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + _16 = &raw mut _0; // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + _17 = _16 as *mut u8 (PtrToPtr); // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + _18 = &raw const _1; // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + _19 = _18 as *const u8 (PtrToPtr); // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + Deinit(_16); // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + copy_nonoverlapping(dst = _17, src = _19, count = _15); // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| + StorageDead(_12); // scope 1 at $DIR/enum_opt.rs:+3:3: +3:4 | ||
| StorageDead(_1); // scope 0 at $DIR/enum_opt.rs:+4:1: +4:2 | ||
| return; // scope 0 at $DIR/enum_opt.rs:+4:2: +4:2 | ||
| } | ||
| } | ||
|  | 
      
      Oops, something went wrong.
        
    
  
  Add this suggestion to a batch that can be applied as a single commit.
  This suggestion is invalid because no changes were made to the code.
  Suggestions cannot be applied while the pull request is closed.
  Suggestions cannot be applied while viewing a subset of changes.
  Only one suggestion per line can be applied in a batch.
  Add this suggestion to a batch that can be applied as a single commit.
  Applying suggestions on deleted lines is not supported.
  You must change the existing code in this line in order to create a valid suggestion.
  Outdated suggestions cannot be applied.
  This suggestion has been applied or marked resolved.
  Suggestions cannot be applied from pending reviews.
  Suggestions cannot be applied on multi-line comments.
  Suggestions cannot be applied while the pull request is queued to merge.
  Suggestion cannot be applied right now. Please check back later.
  
    
  
    
Uh oh!
There was an error while loading. Please reload this page.