diff --git a/sway-core/src/asm_generation/evm/evm_asm_builder.rs b/sway-core/src/asm_generation/evm/evm_asm_builder.rs index ecddcc69ac5..20c35ca6ee0 100644 --- a/sway-core/src/asm_generation/evm/evm_asm_builder.rs +++ b/sway-core/src/asm_generation/evm/evm_asm_builder.rs @@ -350,7 +350,7 @@ impl<'ir, 'eng> EvmAsmBuilder<'ir, 'eng> { dst_val_ptr, src_val_ptr, byte_len, - } => self.compile_mem_copy_bytes(instr_val, dst_val_ptr, src_val_ptr, *byte_len), + } => self.compile_mem_copy_bytes(instr_val, dst_val_ptr, src_val_ptr, byte_len), InstOp::MemCopyVal { dst_val_ptr, src_val_ptr, @@ -506,7 +506,7 @@ impl<'ir, 'eng> EvmAsmBuilder<'ir, 'eng> { instr_val: &Value, dst_val_ptr: &Value, src_val_ptr: &Value, - byte_len: u64, + byte_len: &Value, ) { todo!(); } diff --git a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs index 5c98c09411e..eb1c62e630d 100644 --- a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs +++ b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs @@ -488,7 +488,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { dst_val_ptr, src_val_ptr, byte_len, - } => self.compile_mem_copy_bytes(instr_val, dst_val_ptr, src_val_ptr, *byte_len), + } => self.compile_mem_copy_bytes(instr_val, dst_val_ptr, src_val_ptr, byte_len), InstOp::MemCopyVal { dst_val_ptr, src_val_ptr, @@ -1445,6 +1445,40 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { } fn compile_mem_copy_bytes( + &mut self, + instr_val: &Value, + dst_val_ptr: &Value, + src_val_ptr: &Value, + byte_len: &Value, + ) -> Result<(), CompileError> { + if let Some(byte_len_const) = byte_len + .get_constant(self.context) + .and_then(|c| c.as_uint()) + { + return self.compile_mem_copy_const_bytes( + instr_val, + dst_val_ptr, + src_val_ptr, + byte_len_const, + ); + } + + let owning_span = self.md_mgr.val_to_span(self.context, *instr_val); + + let dst_reg = self.value_to_register(dst_val_ptr)?; + let src_reg = self.value_to_register(src_val_ptr)?; + let len_reg = self.value_to_register(byte_len)?; + + self.cur_bytecode.push(Op { + opcode: Either::Left(VirtualOp::MCP(dst_reg, src_reg, len_reg)), + comment: "copy memory".into(), + owning_span, + }); + + Ok(()) + } + + fn compile_mem_copy_const_bytes( &mut self, instr_val: &Value, dst_val_ptr: &Value, @@ -1461,6 +1495,18 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { let dst_reg = self.value_to_register(dst_val_ptr)?; let src_reg = self.value_to_register(src_val_ptr)?; + // If we can use an MCPI instead of MOVI + MCP, do that. + if let Ok(byte_len_imm) = + VirtualImmediate12::new(byte_len, owning_span.clone().unwrap_or(Span::dummy())) + { + self.cur_bytecode.push(Op { + opcode: Either::Left(VirtualOp::MCPI(dst_reg, src_reg, byte_len_imm)), + comment: "copy memory".into(), + owning_span, + }); + return Ok(()); + } + let len_reg = self.reg_seqr.next(); self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::MOVI( @@ -1500,7 +1546,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { ) })?; let byte_len = dst_ty.size(self.context).in_bytes(); - self.compile_mem_copy_bytes(instr_val, dst_val_ptr, src_val_ptr, byte_len) + self.compile_mem_copy_const_bytes(instr_val, dst_val_ptr, src_val_ptr, byte_len) } fn compile_log( @@ -2137,6 +2183,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { }) }) .ok_or_else(|| { + dbg!(value); let span = self.md_mgr.val_to_span(self.context, *value); CompileError::Internal( "An attempt to get register for unknown Value.", diff --git a/sway-core/src/ir_generation/function.rs b/sway-core/src/ir_generation/function.rs index 4171056c2e7..9ff6d468ecf 100644 --- a/sway-core/src/ir_generation/function.rs +++ b/sway-core/src/ir_generation/function.rs @@ -460,9 +460,10 @@ impl<'eng> FnCompiler<'eng> { .add_metadatum(context, span_md_idx); // copy the value of the struct variable into the slice + let c_16 = Constant::get_uint(context, 64, 16); self.current_block .append(context) - .mem_copy_bytes(slice_val, struct_val, 16); + .mem_copy_bytes(slice_val, struct_val, c_16); // return the slice Ok(TerminatorValue::new(slice_val, context)) @@ -1725,9 +1726,10 @@ impl<'eng> FnCompiler<'eng> { len, Type::get_uint8(context), ); + let len_const = Constant::get_uint(context, 64, 8 - offset); s.current_block .append(context) - .mem_copy_bytes(addr, item_ptr, 8 - offset); + .mem_copy_bytes(addr, item_ptr, len_const); Ok(increase_len(&mut s.current_block, context, len, 8 - offset)) } @@ -1996,9 +1998,10 @@ impl<'eng> FnCompiler<'eng> { len, Type::get_uint8(context), ); + let len_32 = Constant::get_uint(context, 64, 32); self.current_block .append(context) - .mem_copy_bytes(addr, item_ptr, 32); + .mem_copy_bytes(addr, item_ptr, len_32); increase_len(&mut self.current_block, context, len, 32) } TypeInfo::StringArray(string_len) => { @@ -2011,11 +2014,10 @@ impl<'eng> FnCompiler<'eng> { len, Type::get_uint8(context), ); - self.current_block.append(context).mem_copy_bytes( - addr, - item_ptr, - string_len.val() as u64, - ); + let len_const = Constant::get_uint(context, 64, string_len.val() as u64); + self.current_block + .append(context) + .mem_copy_bytes(addr, item_ptr, len_const); increase_len( &mut self.current_block, context, diff --git a/sway-core/src/lib.rs b/sway-core/src/lib.rs index 402a81d1242..87a06f82d35 100644 --- a/sway-core/src/lib.rs +++ b/sway-core/src/lib.rs @@ -47,8 +47,8 @@ use sway_features::ExperimentalFeatures; use sway_ir::{ create_o1_pass_group, register_known_passes, Context, Kind, Module, PassGroup, PassManager, PrintPassesOpts, ARG_DEMOTION_NAME, CONST_DEMOTION_NAME, DCE_NAME, FN_DCE_NAME, - FN_DEDUP_DEBUG_PROFILE_NAME, FN_INLINE_NAME, MEM2REG_NAME, MEMCPYOPT_NAME, MISC_DEMOTION_NAME, - RET_DEMOTION_NAME, SIMPLIFY_CFG_NAME, SROA_NAME, + FN_DEDUP_DEBUG_PROFILE_NAME, FN_DEDUP_DEMONOMORPHIZE_NAME, FN_INLINE_NAME, MEM2REG_NAME, + MEMCPYOPT_NAME, MISC_DEMOTION_NAME, RET_DEMOTION_NAME, SIMPLIFY_CFG_NAME, SROA_NAME, }; use sway_types::constants::DOC_COMMENT_ATTRIBUTE_NAME; use sway_types::SourceEngine; @@ -949,6 +949,9 @@ pub(crate) fn compile_ast_to_ir_to_asm( pass_group.append_pass(SROA_NAME); pass_group.append_pass(MEM2REG_NAME); pass_group.append_pass(DCE_NAME); + pass_group.append_pass(FN_DEDUP_DEMONOMORPHIZE_NAME); + pass_group.append_pass(DCE_NAME); + pass_group.append_pass(FN_DCE_NAME); } OptLevel::Opt0 => {} } diff --git a/sway-ir/src/function.rs b/sway-ir/src/function.rs index cedfee85930..a478a723fe5 100644 --- a/sway-ir/src/function.rs +++ b/sway-ir/src/function.rs @@ -358,6 +358,11 @@ impl Function { .copied() } + /// Get the i'th arg value + pub fn get_ith_arg(&self, context: &Context, i: usize) -> Value { + context.functions[self.0].arguments[i].1 + } + /// Append an extra argument to the function signature. /// /// NOTE: `arg` must be a `BlockArgument` value with the correct index otherwise `add_arg` will diff --git a/sway-ir/src/instruction.rs b/sway-ir/src/instruction.rs index cd710a609c0..aab3261e958 100644 --- a/sway-ir/src/instruction.rs +++ b/sway-ir/src/instruction.rs @@ -101,7 +101,7 @@ pub enum InstOp { MemCopyBytes { dst_val_ptr: Value, src_val_ptr: Value, - byte_len: u64, + byte_len: Value, }, /// Copy a value from one pointer to another. MemCopyVal { @@ -1063,7 +1063,7 @@ impl<'a, 'eng> InstructionInserter<'a, 'eng> { ) } - pub fn mem_copy_bytes(self, dst_val_ptr: Value, src_val_ptr: Value, byte_len: u64) -> Value { + pub fn mem_copy_bytes(self, dst_val_ptr: Value, src_val_ptr: Value, byte_len: Value) -> Value { insert_instruction!( self, InstOp::MemCopyBytes { diff --git a/sway-ir/src/optimize/fn_dedup.rs b/sway-ir/src/optimize/fn_dedup.rs index c53aef4cb43..e322bf78c6c 100644 --- a/sway-ir/src/optimize/fn_dedup.rs +++ b/sway-ir/src/optimize/fn_dedup.rs @@ -7,18 +7,23 @@ //! generating a new function for each instantiation even when the exact //! same instantiation exists. -use std::hash::{Hash, Hasher}; +use std::{ + hash::{Hash, Hasher}, + iter, +}; +use itertools::Itertools; use rustc_hash::{FxHashMap, FxHashSet, FxHasher}; use crate::{ - build_call_graph, callee_first_order, AnalysisResults, Block, Context, Function, InstOp, - Instruction, IrError, MetadataIndex, Metadatum, Module, Pass, PassMutability, ScopedPass, - Value, + build_call_graph, callee_first_order, AnalysisResults, Block, BlockArgument, Constant, Context, + Function, InstOp, Instruction, InstructionInserter, IrError, LocalVar, MetadataIndex, + Metadatum, Module, Pass, PassMutability, ScopedPass, Type, Value, ValueDatum, }; pub const FN_DEDUP_DEBUG_PROFILE_NAME: &str = "fn-dedup-debug"; pub const FN_DEDUP_RELEASE_PROFILE_NAME: &str = "fn-dedup-release"; +pub const FN_DEDUP_DEMONOMORPHIZE_NAME: &str = "fn-dedup-demonomorphize"; pub fn create_fn_dedup_release_profile_pass() -> Pass { Pass { @@ -38,6 +43,15 @@ pub fn create_fn_dedup_debug_profile_pass() -> Pass { } } +pub fn create_fn_dedup_demonomorphize_pass() -> Pass { + Pass { + name: FN_DEDUP_DEMONOMORPHIZE_NAME, + descr: "Function deduplication via demonomorphization", + deps: vec![], + runner: ScopedPass::ModulePass(PassMutability::Transform(dedup_fn_demonomorphize)), + } +} + // Functions that are equivalent are put in the same set. struct EqClass { // Map a function hash to its equivalence class. @@ -51,6 +65,7 @@ fn hash_fn( function: Function, eq_class: &mut EqClass, ignore_metadata: bool, + ignore_pointee_type: bool, ) -> u64 { let state = &mut FxHasher::default(); @@ -91,6 +106,14 @@ fn hash_fn( } } + fn hash_type(context: &Context, hasher: &mut FxHasher, t: Type, ignore_pointee_type: bool) { + if t.is_ptr(context) && ignore_pointee_type { + std::mem::discriminant(t.get_content(context)).hash(hasher); + } else { + t.hash(hasher); + } + } + fn hash_metadata( context: &Context, m: MetadataIndex, @@ -141,7 +164,12 @@ fn hash_fn( } // Start with the function return type. - function.get_return_type(context).hash(state); + hash_type( + context, + state, + function.get_return_type(context), + ignore_pointee_type, + ); // ... and local variables. for (local_name, local_var) in function.locals_iter(context) { @@ -149,7 +177,15 @@ fn hash_fn( if let Some(init) = local_var.get_initializer(context) { init.hash(state); } - local_var.get_type(context).hash(state); + // Locals are pointers, so if we should ignore the pointee type, ignore the type of locals also. + if !ignore_pointee_type { + hash_type( + context, + state, + local_var.get_type(context), + ignore_pointee_type, + ); + } } // Process every block, first its arguments and then the instructions. @@ -157,7 +193,12 @@ fn hash_fn( get_localised_id(block, localised_block_id).hash(state); for &arg in block.arg_iter(context) { get_localised_id(arg, localised_value_id).hash(state); - arg.get_argument(context).unwrap().ty.hash(state); + hash_type( + context, + state, + arg.get_argument(context).unwrap().ty, + ignore_pointee_type, + ); } for inst in block.instruction_iter(context) { get_localised_id(inst, localised_value_id).hash(state); @@ -187,7 +228,7 @@ fn hash_fn( if let Some(return_name) = &asm_block.return_name { return_name.as_str().hash(state); } - asm_block.return_type.hash(state); + hash_type(context, state, asm_block.return_type, ignore_pointee_type); for asm_inst in &asm_block.body { asm_inst.op_name.as_str().hash(state); for arg in &asm_inst.args { @@ -200,7 +241,9 @@ fn hash_fn( } crate::InstOp::UnaryOp { op, .. } => op.hash(state), crate::InstOp::BinaryOp { op, .. } => op.hash(state), - crate::InstOp::BitCast(_, ty) => ty.hash(state), + crate::InstOp::BitCast(_, ty) => { + hash_type(context, state, *ty, ignore_pointee_type) + } crate::InstOp::Branch(b) => { get_localised_id(b.block, localised_block_id).hash(state) } @@ -216,7 +259,9 @@ fn hash_fn( } } } - crate::InstOp::CastPtr(_, ty) => ty.hash(state), + crate::InstOp::CastPtr(_, ty) => { + hash_type(context, state, *ty, ignore_pointee_type) + } crate::InstOp::Cmp(p, _, _) => p.hash(state), crate::InstOp::ConditionalBranch { cond_value: _, @@ -235,7 +280,9 @@ fn hash_fn( crate::FuelVmInstruction::Gtf { tx_field_id, .. } => { tx_field_id.hash(state) } - crate::FuelVmInstruction::Log { log_ty, .. } => log_ty.hash(state), + crate::FuelVmInstruction::Log { log_ty, .. } => { + hash_type(context, state, *log_ty, ignore_pointee_type) + } crate::FuelVmInstruction::ReadRegister(reg) => reg.hash(state), crate::FuelVmInstruction::Revert(_) | crate::FuelVmInstruction::JmpMem @@ -260,13 +307,19 @@ fn hash_fn( .unwrap() .hash(state), crate::InstOp::GetConfig(_, name) => name.hash(state), - crate::InstOp::GetElemPtr { elem_ptr_ty, .. } => elem_ptr_ty.hash(state), - crate::InstOp::IntToPtr(_, ty) => ty.hash(state), + crate::InstOp::GetElemPtr { elem_ptr_ty, .. } => { + hash_type(context, state, *elem_ptr_ty, ignore_pointee_type) + } + crate::InstOp::IntToPtr(_, ty) => { + hash_type(context, state, *ty, ignore_pointee_type) + } crate::InstOp::Load(_) => (), crate::InstOp::MemCopyBytes { byte_len, .. } => byte_len.hash(state), crate::InstOp::MemCopyVal { .. } | crate::InstOp::Nop => (), - crate::InstOp::PtrToInt(_, ty) => ty.hash(state), - crate::InstOp::Ret(_, ty) => ty.hash(state), + crate::InstOp::PtrToInt(_, ty) => { + hash_type(context, state, *ty, ignore_pointee_type) + } + crate::InstOp::Ret(_, ty) => hash_type(context, state, *ty, ignore_pointee_type), crate::InstOp::Store { .. } => (), } } @@ -292,7 +345,7 @@ pub fn dedup_fns( let cg = build_call_graph(context, &context.modules.get(module.0).unwrap().functions); let callee_first = callee_first_order(&cg); for function in callee_first { - let hash = hash_fn(context, function, eq_class, ignore_metadata); + let hash = hash_fn(context, function, eq_class, ignore_metadata, false); eq_class .hash_set_map .entry(hash) @@ -391,3 +444,730 @@ fn dedup_fn_release_profile( ) -> Result { dedup_fns(context, analysis_results, module, true) } + +fn dedup_fn_demonomorphize( + context: &mut Context, + _analysis_results: &AnalysisResults, + module: Module, +) -> Result { + // println!("{}", context); + + let modified = false; + let eq_class = &mut EqClass { + hash_set_map: FxHashMap::default(), + function_hash_map: FxHashMap::default(), + }; + let cg = build_call_graph(context, &context.modules.get(module.0).unwrap().functions); + let callee_first = callee_first_order(&cg); + for function in callee_first { + let hash = hash_fn(context, function, eq_class, true, true); + eq_class + .hash_set_map + .entry(hash) + .and_modify(|class| { + class.insert(function); + }) + .or_insert(vec![function].into_iter().collect()); + eq_class.function_hash_map.insert(function, hash); + } + + for (_class_id, class) in &eq_class.hash_set_map { + if class.len() <= 1 { + continue; + } + struct OthersTracker<'a> { + locals_iter: Box + 'a>, + instr_iter: Box + 'a>, + } + let mut class_iter = class.iter(); + let leader = class_iter.next().unwrap(); + let mut others: FxHashMap<_, _> = class_iter + .map(|f| { + ( + *f, + OthersTracker { + locals_iter: Box::new(f.locals_iter(context)), + instr_iter: Box::new(f.instruction_iter(context)), + }, + ) + }) + .collect(); + + // Note down arguments and retun value that need to be type erased. + let mut type_erase_args = vec![]; + let mut type_erase_ret = false; + for (arg_idx, arg) in leader.args_iter(context).enumerate() { + let ty = arg.1.get_type(context).unwrap(); + if !ty.is_ptr(context) { + continue; + } + for other_func in others.iter_mut() { + let other_arg = other_func.0.get_ith_arg(context, arg_idx); + let other_arg_ty = other_arg.get_type(context).unwrap(); + assert!( + other_arg_ty.is_ptr(context), + "Functions wouldn't be in the same class if args differ" + ); + if ty.get_pointee_type(context).unwrap() + != other_arg_ty.get_pointee_type(context).unwrap() + { + type_erase_args.push(arg_idx); + break; + } + } + } + + let ret_ty = leader.get_return_type(context); + let mut ret_ty_map = FxHashMap::default(); + ret_ty_map.insert(*leader, ret_ty); + if ret_ty.is_ptr(context) { + for other_func in others.iter_mut() { + let other_ret_ty = other_func.0.get_return_type(context); + ret_ty_map.insert(*other_func.0, other_ret_ty); + assert!( + other_ret_ty.is_ptr(context), + "Function't wouldn't be in the same class if ret type differs" + ); + if ret_ty.get_pointee_type(context).unwrap() + != other_ret_ty.get_pointee_type(context).unwrap() + { + type_erase_ret = true; + } + } + } + + // Collect those locals that need to be shifted to an argument. + // The key is a local from the leader and the value is a list of + // corresponding locals from others in the class. + let mut locals_to_args = FxHashMap::default(); + for local in leader.locals_iter(context) { + let mut other_locals = Vec::new(); + let mut shift_to_arg = false; + // If this local differs from a corresponding one in others in the class, + // we'll need to shift it to be a caller allocated parameter with an opaque + // pointer passed as parameter. + let ty = local.1.get_inner_type(context); + for other_func in others.iter_mut() { + let other_local = other_func.1.locals_iter.next().unwrap(); + assert!( + local.0 == other_local.0, + "If names differed, then the functions wouldn't be in the same class" + ); + other_locals.push(*other_local.1); + let other_local_ty = other_local.1.get_inner_type(context); + if ty != other_local_ty { + shift_to_arg = true; + } + } + if shift_to_arg { + locals_to_args.insert(*local.1, other_locals); + } + } + + let mut can_optimize = true; + + #[derive(Default)] + struct ChangeInstrs { + // all the CastPtr/IntToPtr that need change in the leader + cast_to_ptr: FxHashSet, + // all the GetLocal that need change in the leader + get_local: FxHashSet, + // All the GEPs Map that need to become a + // "add pointer + offset", where offset if parameterized instruction. + gep: FxHashMap>, + // All the MemCopyVals Map that need to become + // MemCopyBytes with the size parameterized. + mem_copy_val: FxHashMap>, + } + + let mut type_erase_block_args = FxHashSet::default(); + let mut change_instrs = ChangeInstrs::default(); + 'leader_loop: for (block, inst) in leader.instruction_iter(context) { + let mut block_args_checked = false; + for other_func in others.iter_mut() { + let (other_block, other_instr) = other_func.1.instr_iter.next().unwrap(); + // Check if any of the block args (except for the entry block) need their type erased. + if !block_args_checked && leader.get_entry_block(context) != block { + block_args_checked = true; + for (arg_idx, arg) in block.arg_iter(context).enumerate() { + let ty = arg.get_type(context).unwrap(); + if !ty.is_ptr(context) { + continue; + } + let other_ty = other_block + .get_arg(context, arg_idx) + .unwrap() + .get_type(context) + .unwrap(); + assert!( + other_ty.is_ptr(context), + "If this isn't a pointer, functions shouldn't be in same class" + ); + if ty.get_pointee_type(context).unwrap() + != other_ty.get_pointee_type(context).unwrap() + { + type_erase_block_args.insert(*arg); + } + } + } + // Throughout this loop we check only for differing types between the leader and + // its followers. Other differences aren't checked for because then the hashes would + // be different and they wouldn't be in the same class. + match &inst.get_instruction(context).unwrap().op { + InstOp::AsmBlock(asm_block, _args) => { + let InstOp::AsmBlock(other_asm_block, _) = + &other_instr.get_instruction(context).unwrap().op + else { + panic!("Leader and follower are different instructions in same class"); + }; + if asm_block.return_type != other_asm_block.return_type { + can_optimize = false; + break 'leader_loop; + } + } + InstOp::UnaryOp { .. } => {} + InstOp::BinaryOp { .. } => {} + InstOp::BitCast(_value, ty) => { + let InstOp::BitCast(_other_value, other_ty) = + &other_instr.get_instruction(context).unwrap().op + else { + panic!("Leader and follower are different instructions in same class"); + }; + if ty != other_ty { + can_optimize = false; + break 'leader_loop; + } + } + InstOp::Branch(..) => {} + InstOp::Call(..) => {} + InstOp::CastPtr(_, target_ty) | InstOp::IntToPtr(_, target_ty) => { + match &other_instr.get_instruction(context).unwrap().op { + InstOp::CastPtr(_, other_target_ty) + | InstOp::IntToPtr(_, other_target_ty) => { + if target_ty != other_target_ty { + change_instrs.cast_to_ptr.insert(inst); + } + } + _ => { + panic!( + "Leader and follower are different instructions in same class" + ); + } + } + } + InstOp::Cmp(..) => {} + InstOp::ConditionalBranch { .. } => {} + InstOp::ContractCall { .. } => {} + InstOp::FuelVm(_fuel_vm_instruction) => {} + InstOp::GetLocal(local_var) => { + if locals_to_args.contains_key(local_var) { + change_instrs.get_local.insert(inst); + } + } + InstOp::GetConfig(..) => {} + InstOp::GetElemPtr { + elem_ptr_ty: _, + indices, + base, + } => { + let InstOp::GetElemPtr { + elem_ptr_ty: _, + indices: other_indices, + base: other_base, + } = &other_instr.get_instruction(context).unwrap().op + else { + panic!("Leader and follower are different instructions in same class"); + }; + let base_ty = base + .get_type(context) + .unwrap() + .get_pointee_type(context) + .unwrap(); + let other_base_ty = other_base + .get_type(context) + .unwrap() + .get_pointee_type(context) + .unwrap(); + if base_ty != other_base_ty { + // If we can't determine the offset to a compile time constant, + // we cannot do the optimization. + if base_ty.get_value_indexed_offset(context, indices).is_none() + || other_base_ty + .get_value_indexed_offset(context, &other_indices) + .is_none() + { + can_optimize = false; + break 'leader_loop; + } + change_instrs + .gep + .entry(inst) + .and_modify(|others| others.push(other_instr)) + .or_insert(vec![other_instr]); + } + } + InstOp::Load(_value) => { + if inst.get_type(context) != other_instr.get_type(context) { + can_optimize = false; + break 'leader_loop; + } + } + InstOp::MemCopyBytes { .. } => {} + InstOp::MemCopyVal { dst_val_ptr, .. } => { + let InstOp::MemCopyVal { + dst_val_ptr: other_dst_val_ptr, + .. + } = &other_instr.get_instruction(context).unwrap().op + else { + panic!("Leader and follower are different instructions in same class"); + }; + let copied_ty = dst_val_ptr.get_type(context).unwrap(); + let other_copied_ty = other_dst_val_ptr.get_type(context).unwrap(); + if copied_ty != other_copied_ty { + change_instrs + .mem_copy_val + .entry(inst) + .and_modify(|others| others.push(other_instr)) + .or_insert(vec![other_instr]); + } + } + InstOp::Nop => {} + InstOp::PtrToInt(..) => {} + InstOp::Ret(..) => {} + InstOp::Store { stored_val, .. } => { + let InstOp::Store { + stored_val: other_stored_val, + .. + } = &other_instr.get_instruction(context).unwrap().op + else { + panic!("Leader and follower are different instructions in same class"); + }; + if stored_val.get_type(context) != other_stored_val.get_type(context) { + can_optimize = false; + break 'leader_loop; + } + } + } + } + } + + if !can_optimize { + continue; + } + + if change_instrs.cast_to_ptr.is_empty() + && change_instrs.gep.is_empty() + && change_instrs.get_local.is_empty() + && change_instrs.mem_copy_val.is_empty() + { + continue; + } + + // Map every function in the class to an index. Useful later on. + let class_fn_to_idx: FxHashMap<_, _> = iter::once(leader) + .chain(others.keys()) + .enumerate() + .map(|(idx, f)| (*f, idx)) + .collect(); + + // Note down all call sites for later use. + let call_sites = context + .module_iter() + .flat_map(|module| module.function_iter(context)) + .flat_map(|ref call_from_func| { + call_from_func + .block_iter(context) + .flat_map(|ref block| { + block + .instruction_iter(context) + .filter_map(|instr_val| { + if let Instruction { + op: InstOp::Call(call_to_func, _), + .. + } = instr_val + .get_instruction(context) + .expect("`instruction_iter()` must return instruction values.") + { + iter::once(leader) + .chain(others.keys()) + .contains(call_to_func) + .then_some((*call_from_func, *block, instr_val)) + } else { + None + } + }) + .collect::>() + }) + .collect::>() + }) + .collect::>(); + + // `others` captures `context`, so let's drop it now. + drop(others); + + let unit_ptr_ty = Type::new_ptr(context, Type::get_unit(context)); + + // Track the additional arguments we're adding. + #[derive(Clone)] + enum NewArg { + // A local which is now allocated in the caller and + // whose pointer is passed as parameter. + CallerAllocatedLocal(LocalVar), + // A u64 value + Size(u64), + } + // New arguments for the leader followed by every other. + let mut new_args: Vec> = vec![Vec::new(); class.len()]; + // Argument number for a local + let mut local_to_argno = FxHashMap::default(); + + // We'll collect all the new arguments first, + // and then actually add them and modify the instructions. + for (local, other_locals) in locals_to_args { + new_args[0].push(NewArg::CallerAllocatedLocal(local)); + for (i, ty) in other_locals + .iter() + .map(|other_local| NewArg::CallerAllocatedLocal(*other_local)) + .enumerate() + { + new_args[i + 1].push(ty); + } + local_to_argno.insert(local, new_args[0].len() - 1); + } + + // Map a GEP or MemCopyVal to the new size parameter. + let mut gep_memcpyval_to_argno = FxHashMap::default(); + + for (inst, other_insts) in change_instrs + .gep + .iter() + .chain(change_instrs.mem_copy_val.iter()) + { + let mut this_params: Vec = Vec::new(); + for inst in std::iter::once(inst).chain(other_insts) { + match &inst.get_instruction(context).unwrap().op { + InstOp::GetElemPtr { + elem_ptr_ty: _, + indices, + base, + } => { + let base_ty = base + .get_type(context) + .unwrap() + .get_pointee_type(context) + .unwrap(); + let offset = base_ty.get_value_indexed_offset(context, indices).unwrap(); + this_params.push(offset); + } + InstOp::MemCopyVal { + dst_val_ptr, + src_val_ptr: _, + } => { + let copied_ty = dst_val_ptr + .get_type(context) + .unwrap() + .get_pointee_type(context) + .unwrap(); + let size_copied_type_bytes = copied_ty.size(context).in_bytes(); + this_params.push(size_copied_type_bytes); + } + _ => { + unreachable!("Expected only GEPs or MemCopyVals") + } + } + } + assert!(this_params.len() == class.len()); + // Check if any row in new_args is already the same as this_params, + // in which case we can reuse that parameter. + let argno = (0..new_args[0].len()).find_map(|i| { + if matches!(new_args[0][i], NewArg::CallerAllocatedLocal(..)) { + return None; + } + let ith_params: Vec<_> = new_args + .iter() + .map(|params| { + let NewArg::Size(size_param) = params[i] else { + panic!("We just filtered for Size parameters above"); + }; + size_param + }) + .collect(); + (this_params == ith_params).then_some(i) + }); + if let Some(argno) = argno { + gep_memcpyval_to_argno.insert(inst, argno); + } else { + let argno = new_args[0].len(); + gep_memcpyval_to_argno.insert(inst, argno); + + // Let's add a new row to new_args. + for (i, param) in this_params.iter().enumerate() { + new_args[i].push(NewArg::Size(*param)); + } + } + } + + // We are now equipped to actually modify the program. + // 1(a) Type erase existing arguments / return type if necessary + for arg_idx in &type_erase_args { + let arg_val = leader.get_ith_arg(context, *arg_idx); + let arg = arg_val.get_argument_mut(context).unwrap(); + arg.ty = unit_ptr_ty; + } + for block_arg in type_erase_block_args { + let arg = block_arg.get_argument_mut(context).unwrap(); + arg.ty = unit_ptr_ty; + } + if type_erase_ret { + leader.set_return_type(context, unit_ptr_ty); + } + + // 1(b) Add the new arguments. + let mut new_arg_values = Vec::with_capacity(new_args[0].len()); + let entry_block = leader.get_entry_block(context); + for (arg_idx, new_arg) in (&new_args[0]).iter().enumerate() { + let (new_block_arg, new_arg_name) = match new_arg { + NewArg::CallerAllocatedLocal(..) => ( + BlockArgument { + block: entry_block, + idx: leader.num_args(context), + ty: unit_ptr_ty, + }, + "demonomorphize_alloca_arg_".to_string() + &arg_idx.to_string(), + ), + NewArg::Size(_) => ( + BlockArgument { + block: entry_block, + idx: leader.num_args(context), + ty: Type::get_uint64(context), + }, + "demonomorphize_size_arg_".to_string() + &arg_idx.to_string(), + ), + }; + let new_arg_value = Value::new_argument(context, new_block_arg); + leader.add_arg(context, new_arg_name, new_arg_value); + entry_block.add_arg(context, new_arg_value); + new_arg_values.push(new_arg_value); + } + + // 2. Modify pointer casts. + for cast_to_ptr in change_instrs.cast_to_ptr { + let instr = cast_to_ptr.get_instruction(context).unwrap(); + let new_instr = match &instr.op { + InstOp::CastPtr(source, _target_ty) => InstOp::CastPtr(*source, unit_ptr_ty), + InstOp::IntToPtr(source, _target_ty) => InstOp::IntToPtr(*source, unit_ptr_ty), + _ => unreachable!(), + }; + let new_instr = ValueDatum::Instruction(Instruction { + op: new_instr, + parent: instr.parent, + }); + cast_to_ptr.replace(context, new_instr); + } + + // 3. Modify GEPs. + for (gep, _) in &change_instrs.gep { + let instr = gep.get_instruction(context).unwrap(); + let InstOp::GetElemPtr { + elem_ptr_ty, + indices: _, + base, + } = instr.op + else { + panic!("Should be GEP"); + }; + let arg_idx = gep_memcpyval_to_argno[gep]; + let arg_value = new_arg_values[arg_idx]; + let parent_block = instr.parent; + + let replacement_add = Value::new_instruction( + context, + parent_block, + InstOp::BinaryOp { + op: crate::BinaryOpKind::Add, + arg1: base, + arg2: arg_value, + }, + ); + let mut inserter = InstructionInserter::new( + context, + parent_block, + crate::InsertionPosition::Before(*gep), + ); + inserter.insert(replacement_add); + let ptr_cast = ValueDatum::Instruction(Instruction { + parent: parent_block, + op: InstOp::CastPtr(replacement_add, elem_ptr_ty), + }); + + gep.replace(context, ptr_cast); + } + + // 4. Modify MemCopyVals + for (mem_copy_val, _) in &change_instrs.mem_copy_val { + let instr = mem_copy_val.get_instruction(context).unwrap(); + let InstOp::MemCopyVal { + dst_val_ptr, + src_val_ptr, + } = &instr.op + else { + panic!("Should be MemCopyVal"); + }; + let arg_idx = gep_memcpyval_to_argno[mem_copy_val]; + let arg_value = new_arg_values[arg_idx]; + let replacement_memcpybyte = ValueDatum::Instruction(Instruction { + op: InstOp::MemCopyBytes { + dst_val_ptr: *dst_val_ptr, + src_val_ptr: *src_val_ptr, + byte_len: arg_value, + }, + parent: instr.parent, + }); + mem_copy_val.replace(context, replacement_memcpybyte); + } + + // 5. Update the uses of get_local instructions to directly use the argument. + let mut replacements = FxHashMap::default(); + for get_local in &change_instrs.get_local { + let InstOp::GetLocal(local_var) = get_local.get_instruction(context).unwrap().op else { + panic!("Expected GetLocal"); + }; + let arg = local_to_argno.get(&local_var).unwrap(); + replacements.insert(*get_local, new_arg_values[*arg]); + } + leader.replace_values(context, &replacements, None); + + // 6. Finally modify calls to each function in the class. + for (caller, call_block, call_inst) in call_sites { + // Update the callee in call_inst first, all calls go to the leader now. + let (callee, params) = { + let InstOp::Call(callee, params) = + &mut call_inst.get_instruction_mut(context).unwrap().op + else { + panic!("Expected Call"); + }; + let original_callee = *callee; + *callee = *leader; + (original_callee, params) + }; + + // Update existing params to erase type, if necessary. + let mut new_params = params.clone(); + let mut new_instrs = vec![]; + for arg_idx in &type_erase_args { + let new_param = Value::new_instruction( + context, + call_block, + InstOp::CastPtr(new_params[*arg_idx], unit_ptr_ty), + ); + new_instrs.push(new_param); + new_params[*arg_idx] = new_param; + } + let mut inserter = InstructionInserter::new( + context, + call_block, + crate::InsertionPosition::Before(call_inst), + ); + inserter.insert_slice(&new_instrs); + let InstOp::Call(_callee, params) = + &mut call_inst.get_instruction_mut(context).unwrap().op + else { + panic!("Expected Call"); + }; + *params = new_params; + + // Now add the new args. + let callee_idx = class_fn_to_idx[&callee]; + let new_args = &new_args[callee_idx]; + for new_arg in new_args { + match new_arg { + NewArg::CallerAllocatedLocal(original_local) => { + let name = callee + .lookup_local_name(context, original_local) + .cloned() + .unwrap_or("".to_string()) + + "_demonomorphized"; + let new_local = caller.new_unique_local_var( + context, + name, + original_local + .get_type(context) + .get_pointee_type(context) + .unwrap(), + original_local.get_initializer(context).cloned(), + original_local.is_mutable(context), + ); + let new_local_ptr = Value::new_instruction( + context, + call_block, + InstOp::GetLocal(new_local), + ); + let new_local_ptr_casted = Value::new_instruction( + context, + call_block, + InstOp::CastPtr(new_local_ptr, unit_ptr_ty), + ); + let mut inserter = InstructionInserter::new( + context, + call_block, + crate::InsertionPosition::Before(call_inst), + ); + inserter.insert_slice(&[new_local_ptr, new_local_ptr_casted]); + let InstOp::Call(_, args) = + &mut call_inst.get_instruction_mut(context).unwrap().op + else { + panic!("Expected Call"); + }; + args.push(new_local_ptr_casted); + } + NewArg::Size(val) => { + let new_size_const = Constant::new_uint(context, 64, *val); + let new_size_arg = Value::new_constant(context, new_size_const); + let InstOp::Call(_, args) = + &mut call_inst.get_instruction_mut(context).unwrap().op + else { + panic!("Expected Call"); + }; + args.push(new_size_arg); + } + } + } + if type_erase_ret { + let inserter = InstructionInserter::new( + context, + call_block, + crate::InsertionPosition::After(call_inst), + ); + let ret_cast = inserter.cast_ptr(call_inst, ret_ty_map[&callee]); + caller.replace_value(context, call_inst, ret_cast, None); + // caller.replace_value will replace call_inst in the just inserted cast. Fix it. + let Instruction { + op: InstOp::CastPtr(ptr, _), + .. + } = ret_cast.get_instruction_mut(context).unwrap() + else { + panic!("We just created this to be a Castptr"); + }; + *ptr = call_inst; + + // Modify all return instructions + for (_, ret) in leader + .instruction_iter(context) + .filter(|inst| { + matches!(inst.1.get_instruction(context).unwrap().op, InstOp::Ret(..)) + }) + .collect::>() + { + let InstOp::Ret(__entry, ty) = + &mut ret.get_instruction_mut(context).unwrap().op + else { + panic!("We just filtered for Rets") + }; + *ty = unit_ptr_ty; + } + } + } + } + + Ok(modified) +} diff --git a/sway-ir/src/optimize/memcpyopt.rs b/sway-ir/src/optimize/memcpyopt.rs index 265db81695b..1b8ab4d4378 100644 --- a/sway-ir/src/optimize/memcpyopt.rs +++ b/sway-ir/src/optimize/memcpyopt.rs @@ -286,7 +286,7 @@ fn local_copy_prop( fn kill_defined_symbol( context: &Context, value: Value, - len: u64, + len: Option, available_copies: &mut FxHashSet, src_to_copies: &mut FxIndexMap>, dest_to_copies: &mut FxIndexMap>, @@ -297,7 +297,16 @@ fn local_copy_prop( if let Some(copies) = src_to_copies.get_mut(&sym) { for copy in &*copies { let (_, src_ptr, copy_size) = deconstruct_memcpy(context, *copy); - if memory_utils::may_alias(context, value, len, src_ptr, copy_size) { + if len.is_none() + || copy_size.is_none() + || memory_utils::may_alias( + context, + value, + len.unwrap(), + src_ptr, + copy_size.unwrap(), + ) + { available_copies.remove(copy); } } @@ -315,7 +324,10 @@ fn local_copy_prop( byte_len, }, .. - } => (*dst_val_ptr, *byte_len), + } => ( + *dst_val_ptr, + byte_len.get_constant(context).and_then(|c| c.as_uint()), + ), Instruction { op: InstOp::MemCopyVal { @@ -325,11 +337,22 @@ fn local_copy_prop( .. } => ( *dst_val_ptr, - memory_utils::pointee_size(context, *dst_val_ptr), + Some(memory_utils::pointee_size(context, *dst_val_ptr)), ), _ => panic!("Unexpected copy instruction"), }; - if memory_utils::may_alias(context, value, len, dest_ptr, copy_size) { + // If we don't know the copy size or there's a possible alias, + // we decide that this copy won't be available. + if len.is_none() + || copy_size.is_none() + || memory_utils::may_alias( + context, + value, + len.unwrap(), + dest_ptr, + copy_size.unwrap(), + ) + { available_copies.remove(copy); } } @@ -381,7 +404,7 @@ fn local_copy_prop( } // Deconstruct a memcpy into (dst_val_ptr, src_val_ptr, copy_len). - fn deconstruct_memcpy(context: &Context, inst: Value) -> (Value, Value, u64) { + fn deconstruct_memcpy(context: &Context, inst: Value) -> (Value, Value, Option) { match inst.get_instruction(context).unwrap() { Instruction { op: @@ -391,7 +414,11 @@ fn local_copy_prop( byte_len, }, .. - } => (*dst_val_ptr, *src_val_ptr, *byte_len), + } => ( + *dst_val_ptr, + *src_val_ptr, + byte_len.get_constant(context).and_then(|c| c.as_uint()), + ), Instruction { op: InstOp::MemCopyVal { @@ -402,7 +429,7 @@ fn local_copy_prop( } => ( *dst_val_ptr, *src_val_ptr, - memory_utils::pointee_size(context, *dst_val_ptr), + Some(memory_utils::pointee_size(context, *dst_val_ptr)), ), _ => unreachable!("Only memcpy instructions handled"), } @@ -445,13 +472,15 @@ fn local_copy_prop( // matches. This isn't really needed as the copy happens and the // data we want is safe to access. But we just don't know how to // generate the right GEP always. So that's left for another day. - if memory_utils::must_alias( - context, - src_val_ptr, - memory_utils::pointee_size(context, src_val_ptr), - dst_ptr_memcpy, - copy_len, - ) { + if copy_len.is_some_and(|copy_len| { + memory_utils::must_alias( + context, + src_val_ptr, + memory_utils::pointee_size(context, src_val_ptr), + dst_ptr_memcpy, + copy_len, + ) + }) { // Replace src_val_ptr with src_ptr_memcpy. if src_val_ptr.get_type(context) == src_ptr_memcpy.get_type(context) { replacements.insert(inst, Replacement::OldGep(src_ptr_memcpy)); @@ -474,7 +503,9 @@ fn local_copy_prop( .get_pointee_type(context) .unwrap(); if memcpy_src_sym_type == memcpy_dst_sym_type - && memcpy_dst_sym_type.size(context).in_bytes() == copy_len + && copy_len.is_some_and(|copy_len| { + memcpy_dst_sym_type.size(context).in_bytes() == copy_len + }) { replacements.insert( inst, @@ -533,7 +564,7 @@ fn local_copy_prop( kill_defined_symbol( context, *arg, - max_size, + Some(max_size), available_copies, src_to_copies, dest_to_copies, @@ -644,7 +675,7 @@ fn local_copy_prop( kill_defined_symbol( context, *dst_val_ptr, - memory_utils::pointee_size(context, *dst_val_ptr), + Some(memory_utils::pointee_size(context, *dst_val_ptr)), &mut available_copies, &mut src_to_copies, &mut dest_to_copies, @@ -665,7 +696,7 @@ fn local_copy_prop( kill_defined_symbol( context, *result, - memory_utils::pointee_size(context, *result), + Some(memory_utils::pointee_size(context, *result)), &mut available_copies, &mut src_to_copies, &mut dest_to_copies, diff --git a/sway-ir/src/parser.rs b/sway-ir/src/parser.rs index fb577d05e15..ad503081f4a 100644 --- a/sway-ir/src/parser.rs +++ b/sway-ir/src/parser.rs @@ -359,7 +359,7 @@ mod ir_builder { } rule op_mem_copy_bytes() -> IrAstOperation - = "mem_copy_bytes" _ dst_name:id() comma() src_name:id() comma() len:decimal() { + = "mem_copy_bytes" _ dst_name:id() comma() src_name:id() comma() len:id() { IrAstOperation::MemCopyBytes(dst_name, src_name, len) } @@ -754,7 +754,7 @@ mod ir_builder { IntToPtr(String, IrAstTy), Load(String), Log(IrAstTy, String, String), - MemCopyBytes(String, String, u64), + MemCopyBytes(String, String, String), MemCopyVal(String, String), Nop, PtrToInt(String, IrAstTy), @@ -1345,7 +1345,7 @@ mod ir_builder { .mem_copy_bytes( *val_map.get(&dst_name).unwrap(), *val_map.get(&src_name).unwrap(), - len, + *val_map.get(&len).unwrap(), ) .add_metadatum(context, opt_metadata), IrAstOperation::MemCopyVal(dst_name, src_name) => block diff --git a/sway-ir/src/pass_manager.rs b/sway-ir/src/pass_manager.rs index b1c069a39dd..0b12ece8d83 100644 --- a/sway-ir/src/pass_manager.rs +++ b/sway-ir/src/pass_manager.rs @@ -2,14 +2,15 @@ use crate::{ create_arg_demotion_pass, create_ccp_pass, create_const_demotion_pass, create_const_folding_pass, create_cse_pass, create_dce_pass, create_dom_fronts_pass, create_dominators_pass, create_escaped_symbols_pass, create_fn_dce_pass, - create_fn_dedup_debug_profile_pass, create_fn_dedup_release_profile_pass, - create_fn_inline_pass, create_mem2reg_pass, create_memcpyopt_pass, create_misc_demotion_pass, - create_module_printer_pass, create_module_verifier_pass, create_postorder_pass, - create_ret_demotion_pass, create_simplify_cfg_pass, create_sroa_pass, Context, Function, - IrError, Module, ARG_DEMOTION_NAME, CCP_NAME, CONST_DEMOTION_NAME, CONST_FOLDING_NAME, - CSE_NAME, DCE_NAME, FN_DCE_NAME, FN_DEDUP_DEBUG_PROFILE_NAME, FN_DEDUP_RELEASE_PROFILE_NAME, - FN_INLINE_NAME, MEM2REG_NAME, MEMCPYOPT_NAME, MISC_DEMOTION_NAME, RET_DEMOTION_NAME, - SIMPLIFY_CFG_NAME, SROA_NAME, + create_fn_dedup_debug_profile_pass, create_fn_dedup_demonomorphize_pass, + create_fn_dedup_release_profile_pass, create_fn_inline_pass, create_mem2reg_pass, + create_memcpyopt_pass, create_misc_demotion_pass, create_module_printer_pass, + create_module_verifier_pass, create_postorder_pass, create_ret_demotion_pass, + create_simplify_cfg_pass, create_sroa_pass, Context, Function, IrError, Module, + ARG_DEMOTION_NAME, CCP_NAME, CONST_DEMOTION_NAME, CONST_FOLDING_NAME, CSE_NAME, DCE_NAME, + FN_DCE_NAME, FN_DEDUP_DEBUG_PROFILE_NAME, FN_DEDUP_RELEASE_PROFILE_NAME, FN_INLINE_NAME, + MEM2REG_NAME, MEMCPYOPT_NAME, MISC_DEMOTION_NAME, RET_DEMOTION_NAME, SIMPLIFY_CFG_NAME, + SROA_NAME, }; use downcast_rs::{impl_downcast, Downcast}; use rustc_hash::FxHashMap; @@ -393,6 +394,7 @@ pub fn register_known_passes(pm: &mut PassManager) { // Optimization passes. pm.register(create_fn_dedup_release_profile_pass()); pm.register(create_fn_dedup_debug_profile_pass()); + pm.register(create_fn_dedup_demonomorphize_pass()); pm.register(create_mem2reg_pass()); pm.register(create_sroa_pass()); pm.register(create_fn_inline_pass()); diff --git a/sway-ir/src/printer.rs b/sway-ir/src/printer.rs index b589c329c3b..12845db14f9 100644 --- a/sway-ir/src/printer.rs +++ b/sway-ir/src/printer.rs @@ -993,15 +993,15 @@ fn instruction_to_doc<'a>( dst_val_ptr, src_val_ptr, byte_len, - } => Doc::line( + } => maybe_constant_to_doc(context, md_namer, namer, byte_len).append(Doc::line( Doc::text(format!( "mem_copy_bytes {}, {}, {}", namer.name(context, dst_val_ptr), namer.name(context, src_val_ptr), - byte_len, + namer.name(context, byte_len), )) .append(md_namer.md_idx_to_doc(context, metadata)), - ), + )), InstOp::MemCopyVal { dst_val_ptr, src_val_ptr, diff --git a/sway-ir/src/verify.rs b/sway-ir/src/verify.rs index f5bbb39cea9..57caa009865 100644 --- a/sway-ir/src/verify.rs +++ b/sway-ir/src/verify.rs @@ -333,8 +333,8 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { InstOp::MemCopyBytes { dst_val_ptr, src_val_ptr, - byte_len, - } => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr, byte_len)?, + byte_len: _, + } => self.verify_mem_copy_bytes(dst_val_ptr, src_val_ptr)?, InstOp::MemCopyVal { dst_val_ptr, src_val_ptr, @@ -524,11 +524,14 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { return Err(IrError::VerifyBinaryOpIncorrectArgType); } } - BinaryOpKind::Add - | BinaryOpKind::Sub - | BinaryOpKind::Mul - | BinaryOpKind::Div - | BinaryOpKind::Mod => { + BinaryOpKind::Add => { + if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) { + if !(arg1_ty.is_ptr(self.context) && arg2_ty.is_uint64(self.context)) { + return Err(IrError::VerifyBinaryOpIncorrectArgType); + } + } + } + BinaryOpKind::Sub | BinaryOpKind::Mul | BinaryOpKind::Div | BinaryOpKind::Mod => { if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) { return Err(IrError::VerifyBinaryOpIncorrectArgType); } @@ -892,7 +895,6 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { &self, dst_val_ptr: &Value, src_val_ptr: &Value, - _byte_len: &u64, ) -> Result<(), IrError> { // Just confirm both values are pointers. self.get_ptr_type(dst_val_ptr, IrError::VerifyMemcopyNonPointer) diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_consts/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_consts/json_abi_oracle_new_encoding.json index 71cf1f22ff7..d8195619956 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_consts/json_abi_oracle_new_encoding.json +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/configurable_consts/json_abi_oracle_new_encoding.json @@ -62,82 +62,82 @@ { "concreteTypeId": "b760f44fa5965c2474a3b471467a22c43185152129295af588b022ae50b50903", "name": "BOOL", - "offset": 7048 + "offset": 6704 }, { "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", "name": "U8", - "offset": 7240 + "offset": 6896 }, { "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", "name": "ANOTHER_U8", - "offset": 6976 + "offset": 6632 }, { "concreteTypeId": "29881aad8730c5ab11d275376323d8e4ff4179aae8ccb6c13fe4902137e162ef", "name": "U16", - "offset": 7184 + "offset": 6840 }, { "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", "name": "U32", - "offset": 7224 + "offset": 6880 }, { "concreteTypeId": "d7649d428b9ff33d188ecbf38a7e4d8fd167fa01b2e10fe9a8f9308e52f1d7cc", "name": "U64", - "offset": 7232 + "offset": 6888 }, { "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", "name": "U256", - "offset": 7192 + "offset": 6848 }, { "concreteTypeId": "7c5ee1cecf5f8eacd1284feb5f0bf2bdea533a51e2f0c9aabe9236d335989f3b", "name": "B256", - "offset": 7016 + "offset": 6672 }, { "concreteTypeId": "81fc10c4681a3271cf2d66b2ec6fbc8ed007a442652930844fcf11818c295bff", "name": "CONFIGURABLE_STRUCT", - "offset": 7136 + "offset": 6792 }, { "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", "name": "CONFIGURABLE_ENUM_A", - "offset": 7056 + "offset": 6712 }, { "concreteTypeId": "a2922861f03be8a650595dd76455b95383a61b46dd418f02607fa2e00dc39d5c", "name": "CONFIGURABLE_ENUM_B", - "offset": 7096 + "offset": 6752 }, { "concreteTypeId": "4926d35d1a5157936b0a29bc126b8aace6d911209a5c130e9b716b0c73643ea6", "name": "ARRAY_BOOL", - "offset": 6984 + "offset": 6640 }, { "concreteTypeId": "776fb5a3824169d6736138565fdc20aad684d9111266a5ff6d5c675280b7e199", "name": "ARRAY_U64", - "offset": 6992 + "offset": 6648 }, { "concreteTypeId": "c998ca9a5f221fe7b5c66ae70c8a9562b86d964408b00d17f883c906bc1fe4be", "name": "TUPLE_BOOL_U64", - "offset": 7168 + "offset": 6824 }, { "concreteTypeId": "94f0fa95c830be5e4f711963e83259fe7e8bc723278ab6ec34449e791a99b53a", "name": "STR_4", - "offset": 7160 + "offset": 6816 }, { "concreteTypeId": "c89951a24c6ca28c13fd1cfdc646b2b656d69e61a92b91023be7eb58eb914b6b", "name": "NOT_USED", - "offset": 7152 + "offset": 6808 } ], "encodingVersion": "1", diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/u256_abi/json_abi_oracle_new_encoding.json b/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/u256_abi/json_abi_oracle_new_encoding.json index c58838960ad..3246e38ae49 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/u256_abi/json_abi_oracle_new_encoding.json +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/u256_abi/json_abi_oracle_new_encoding.json @@ -9,7 +9,7 @@ { "concreteTypeId": "1b5759d94094368cfd443019e7ca5ec4074300e544e5ea993a979f5da627261e", "name": "SOME_U256", - "offset": 872 + "offset": 760 } ], "encodingVersion": "1",