diff --git a/Cargo.lock b/Cargo.lock index 6bd62e868a2..1a428a25f54 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3802,9 +3802,9 @@ dependencies = [ [[package]] name = "num-traits" -version = "0.2.15" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd" +checksum = "f30b0abd723be7e2ffca1272140fac1a2f084c77ec3e123c192b66af1ee9e6c2" dependencies = [ "autocfg", ] @@ -5543,9 +5543,12 @@ dependencies = [ "itertools", "lazy_static", "miden-core", + "num-bigint", + "num-traits", "pest", "pest_derive", "petgraph", + "prettydiff 0.6.4", "rustc-hash", "serde", "serde_json", @@ -5585,7 +5588,10 @@ dependencies = [ "downcast-rs", "filecheck", "generational-arena", + "num-bigint", + "num-traits", "peg", + "prettydiff 0.6.4", "rustc-hash", "sway-ir-macros", "sway-types", @@ -5925,6 +5931,7 @@ dependencies = [ "gag", "hex", "miden", + "num-bigint", "prettydiff 0.6.4", "rand", "regex", diff --git a/Cargo.toml b/Cargo.toml index addc9919881..728e050fde0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,11 +24,7 @@ members = [ "swayfmt", "test", ] -exclude = [ - "examples/*", - "swayfmt/test_macros", - "forc-test/test_data" -] +exclude = ["examples/*", "swayfmt/test_macros", "forc-test/test_data"] [workspace.dependencies] fuel-asm = "0.34.1" diff --git a/forc-plugins/forc-doc/src/render/item/type_anchor.rs b/forc-plugins/forc-doc/src/render/item/type_anchor.rs index f57d67e2f7a..94ccf077c64 100644 --- a/forc-plugins/forc-doc/src/render/item/type_anchor.rs +++ b/forc-plugins/forc-doc/src/render/item/type_anchor.rs @@ -96,6 +96,7 @@ pub(crate) fn render_type_anchor( IntegerBits::Sixteen => "u16", IntegerBits::ThirtyTwo => "u32", IntegerBits::SixtyFour => "u64", + IntegerBits::V256 => "u256", }; Ok(box_html! { : uint; diff --git a/sway-ast/src/literal.rs b/sway-ast/src/literal.rs index d2e8f4e94ae..c04700e2107 100644 --- a/sway-ast/src/literal.rs +++ b/sway-ast/src/literal.rs @@ -25,6 +25,7 @@ pub enum LitIntType { U16, U32, U64, + U256, I8, I16, I32, diff --git a/sway-core/Cargo.toml b/sway-core/Cargo.toml index 66f3da2a850..1280c3cd020 100644 --- a/sway-core/Cargo.toml +++ b/sway-core/Cargo.toml @@ -27,9 +27,12 @@ im = "15.0" itertools = "0.10" lazy_static = "1.4" miden-core = "0.3.0" +num-bigint = { version = "0.4.3", features = ["serde"] } +num-traits = "0.2.16" pest = "2.1.3" pest_derive = "2.1" petgraph = "0.6" +prettydiff = "0.6.4" rustc-hash = "1.1.0" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.91" @@ -47,5 +50,6 @@ tracing = "0.1" uint = "0.9" vec1 = "1.8.0" + [target.'cfg(not(target_os = "macos"))'.dependencies] sysinfo = "0.29.0" diff --git a/sway-core/src/abi_generation/evm_abi.rs b/sway-core/src/abi_generation/evm_abi.rs index a326aba9380..c58a5043289 100644 --- a/sway-core/src/abi_generation/evm_abi.rs +++ b/sway-core/src/abi_generation/evm_abi.rs @@ -89,6 +89,7 @@ pub fn abi_str(type_info: &TypeInfo, type_engine: &TypeEngine, decl_engine: &Dec IntegerBits::Sixteen => "uint16", IntegerBits::ThirtyTwo => "uint32", IntegerBits::SixtyFour => "uint64", + IntegerBits::V256 => todo!(), } .into(), Boolean => "bool".into(), @@ -149,6 +150,7 @@ pub fn abi_param_type( IntegerBits::Sixteen => ethabi::ParamType::Uint(16), IntegerBits::ThirtyTwo => ethabi::ParamType::Uint(32), IntegerBits::SixtyFour => ethabi::ParamType::Uint(64), + IntegerBits::V256 => todo!(), }, Boolean => ethabi::ParamType::Bool, B256 => ethabi::ParamType::Uint(256), diff --git a/sway-core/src/abi_generation/fuel_abi.rs b/sway-core/src/abi_generation/fuel_abi.rs index 1d4439adc58..30d59b0a8dd 100644 --- a/sway-core/src/abi_generation/fuel_abi.rs +++ b/sway-core/src/abi_generation/fuel_abi.rs @@ -790,6 +790,7 @@ impl TypeInfo { IntegerBits::Sixteen => "u16", IntegerBits::ThirtyTwo => "u32", IntegerBits::SixtyFour => "u64", + IntegerBits::V256 => "u256", } .into(), Boolean => "bool".into(), diff --git a/sway-core/src/asm_generation/from_ir.rs b/sway-core/src/asm_generation/from_ir.rs index b8ec8d3326f..92a75f587ad 100644 --- a/sway-core/src/asm_generation/from_ir.rs +++ b/sway-core/src/asm_generation/from_ir.rs @@ -135,6 +135,8 @@ fn compile_module_to_asm( println!("{abstract_program}\n"); } + // let before = abstract_program.to_string(); + let allocated_program = check!( CompileResult::from(abstract_program.into_allocated_program()), return err(warnings, errors), @@ -142,6 +144,10 @@ fn compile_module_to_asm( errors ); + // let after = allocated_program.to_string(); + // println!("############################################# into_allocated_program"); + // println!("{}", prettydiff::diff_lines(&before, &after)); + if build_config .map(|cfg| cfg.print_intermediate_asm) .unwrap_or(false) diff --git a/sway-core/src/asm_generation/fuel/abstract_instruction_set.rs b/sway-core/src/asm_generation/fuel/abstract_instruction_set.rs index e110f1b6802..60dc1623639 100644 --- a/sway-core/src/asm_generation/fuel/abstract_instruction_set.rs +++ b/sway-core/src/asm_generation/fuel/abstract_instruction_set.rs @@ -24,6 +24,8 @@ pub struct AbstractInstructionSet { impl AbstractInstructionSet { pub(crate) fn optimize(self) -> AbstractInstructionSet { + // println!("AbstractInstructionSet:: Before:"); + // println!("{}", self.to_string()); self.remove_sequential_jumps() .remove_redundant_moves() .remove_unused_ops() @@ -57,6 +59,8 @@ impl AbstractInstructionSet { } fn remove_redundant_moves(mut self) -> AbstractInstructionSet { + // let before = self.to_string(); + // This has a lot of room for improvement. // // For now it is just removing MOVEs to registers which are _never_ used. It doesn't @@ -107,6 +111,9 @@ impl AbstractInstructionSet { } } + // let after = self.to_string(); + // println!("######################################### remove_redundant_moves"); + // println!("{}", prettydiff::diff_lines(&before, &after)); self } diff --git a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs index 00199d6f2c8..f48997a51d1 100644 --- a/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs +++ b/sway-core/src/asm_generation/fuel/allocated_abstract_instruction_set.rs @@ -40,6 +40,8 @@ impl AllocatedAbstractInstructionSet { /// Typically there will be only one of each but the code here allows for nested sections or /// even overlapping sections. pub(crate) fn emit_pusha_popa(mut self) -> Self { + // let before = self.to_string(); + // Gather the sets of used registers per section. Using a fold here because it's actually // simpler to manage. We use a HashSet to keep track of the active section labels and then // build a HashMap of Label to HashSet of registers. @@ -171,6 +173,12 @@ impl AllocatedAbstractInstructionSet { new_ops }); + // let after = self.to_string(); + // println!( + // "################################################################# emit_pusha_popa" + // ); + // println!("{}", prettydiff::diff_lines(&before, &after)); + self } diff --git a/sway-core/src/asm_generation/fuel/data_section.rs b/sway-core/src/asm_generation/fuel/data_section.rs index ff079dfa33f..b2488eb4515 100644 --- a/sway-core/src/asm_generation/fuel/data_section.rs +++ b/sway-core/src/asm_generation/fuel/data_section.rs @@ -99,8 +99,20 @@ impl Entry { match &constant.value { ConstantValue::Undef | ConstantValue::Unit => Entry::new_word(0, size, name), ConstantValue::Bool(b) => Entry::new_word(u64::from(*b), size, name), - ConstantValue::Uint(u) => Entry::new_word(*u, size, name), - + ConstantValue::Uint(u) => match constant.ty.get_uint_width(context) { + Some(width) if width <= 64 => { + let u = u64::try_from(u).unwrap(); + Entry::new_word(u, size, name) + } + Some(256) => { + let number_bytes = u.to_bytes_be(); + let mut bytes = vec![0u8; 32 - number_bytes.len()]; + bytes.extend(number_bytes); + assert!(bytes.len() == size.unwrap()); + Entry::new_byte_array(bytes, size, name) + } + _ => todo!(), + }, ConstantValue::B256(bs) => Entry::new_byte_array(bs.to_vec(), size, name), ConstantValue::String(bs) => Entry::new_byte_array(bs.clone(), size, name), 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 e848a08f5b9..1f5a6cce7f4 100644 --- a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs +++ b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs @@ -10,12 +10,17 @@ use crate::{ }, ProgramKind, }, - asm_lang::{virtual_register::*, Label, Op, VirtualImmediate12, VirtualImmediate18, VirtualOp}, + asm_lang::{ + virtual_register::*, Label, Op, VirtualImmediate12, VirtualImmediate18, VirtualOp, + WideCmpImmediate, WideCmpOp, WideDivImmediate, WideMulImmediate, WideOperation, + WideOperationsImmediate, + }, decl_engine::DeclRefFunction, error::*, metadata::MetadataManager, }; +use num_traits::{One, Zero}; use sway_error::{error::CompileError, warning::CompileWarning, warning::Warning}; use sway_ir::*; use sway_types::{span::Span, Spanned}; @@ -485,21 +490,164 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { let val1_reg = self.value_to_register(arg1)?; let val2_reg = self.value_to_register(arg2)?; let res_reg = self.reg_seqr.next(); - let opcode = match op { - BinaryOpKind::Add => Either::Left(VirtualOp::ADD(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::Sub => Either::Left(VirtualOp::SUB(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::Mul => Either::Left(VirtualOp::MUL(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::Div => Either::Left(VirtualOp::DIV(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::And => Either::Left(VirtualOp::AND(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::Or => Either::Left(VirtualOp::OR(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::Xor => Either::Left(VirtualOp::XOR(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::Mod => Either::Left(VirtualOp::MOD(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::Rsh => Either::Left(VirtualOp::SRL(res_reg.clone(), val1_reg, val2_reg)), - BinaryOpKind::Lsh => Either::Left(VirtualOp::SLL(res_reg.clone(), val1_reg, val2_reg)), + + // Check op is on 256 bits and use the special opcodes + let arg1_width = arg1 + .get_type(self.context) + .and_then(|x| x.get_uint_width(self.context)); + let arg2_width = arg2 + .get_type(self.context) + .and_then(|x| x.get_uint_width(self.context)); + let (opcode, comment) = match (arg1_width, arg2_width) { + (Some(256), Some(256)) => { + let locals = self.locals_ctxs.last_mut().unwrap(); + let local_addr = locals.0 as u16; + locals.0 += 32; + + self.cur_bytecode.push(Op { + opcode: Either::Left(VirtualOp::ADDI( + res_reg.clone(), + VirtualRegister::Constant(ConstantRegister::LocalsBase), + VirtualImmediate12 { value: local_addr }, + )), + comment: "temp for wide operations".into(), + owning_span: self.md_mgr.val_to_span(self.context, *instr_val), + }); + + match op { + BinaryOpKind::Add => ( + VirtualOp::WQOP( + res_reg.clone(), + val1_reg, + val2_reg, + WideOperationsImmediate { + op: WideOperation::Add, + right_indirect: true, + }, + ), + Some("add"), + ), + BinaryOpKind::Sub => ( + VirtualOp::WQOP( + res_reg.clone(), + val1_reg, + val2_reg, + WideOperationsImmediate { + op: WideOperation::Sub, + right_indirect: true, + }, + ), + Some("sub"), + ), + BinaryOpKind::Or => ( + VirtualOp::WQOP( + res_reg.clone(), + val1_reg, + val2_reg, + WideOperationsImmediate { + op: WideOperation::Or, + right_indirect: true, + }, + ), + Some("or"), + ), + BinaryOpKind::Xor => ( + VirtualOp::WQOP( + res_reg.clone(), + val1_reg, + val2_reg, + WideOperationsImmediate { + op: WideOperation::Xor, + right_indirect: true, + }, + ), + Some("xor"), + ), + BinaryOpKind::And => ( + VirtualOp::WQOP( + res_reg.clone(), + val1_reg, + val2_reg, + WideOperationsImmediate { + op: WideOperation::And, + right_indirect: true, + }, + ), + Some("and"), + ), + BinaryOpKind::Lsh => ( + VirtualOp::WQOP( + res_reg.clone(), + val1_reg, + val2_reg, + WideOperationsImmediate { + op: WideOperation::Shl, + right_indirect: false, + }, + ), + Some("shl"), + ), + BinaryOpKind::Rsh => ( + VirtualOp::WQOP( + res_reg.clone(), + val1_reg, + val2_reg, + WideOperationsImmediate { + op: WideOperation::Shr, + right_indirect: false, + }, + ), + Some("shr"), + ), + BinaryOpKind::Mul => ( + VirtualOp::WQML( + res_reg.clone(), + val1_reg, + val2_reg, + WideMulImmediate { + left_indirect: true, + right_indirect: true, + }, + ), + Some("mul"), + ), + BinaryOpKind::Div => ( + VirtualOp::WQDV( + res_reg.clone(), + val1_reg, + val2_reg, + WideDivImmediate { + right_indirect: true, + }, + ), + Some("div"), + ), + BinaryOpKind::Mod => todo!(), + } + } + _ => { + // Use the standard opcodes + ( + match op { + BinaryOpKind::Add => VirtualOp::ADD(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::Sub => VirtualOp::SUB(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::Mul => VirtualOp::MUL(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::Div => VirtualOp::DIV(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::And => VirtualOp::AND(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::Or => VirtualOp::OR(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::Xor => VirtualOp::XOR(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::Mod => VirtualOp::MOD(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::Rsh => VirtualOp::SRL(res_reg.clone(), val1_reg, val2_reg), + BinaryOpKind::Lsh => VirtualOp::SLL(res_reg.clone(), val1_reg, val2_reg), + }, + None, + ) + } }; + self.cur_bytecode.push(Op { - opcode, - comment: String::new(), + opcode: Either::Left(opcode), + comment: comment.map(Into::into).unwrap_or_default(), owning_span: self.md_mgr.val_to_span(self.context, *instr_val), }); @@ -526,31 +674,66 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { let lhs_reg = self.value_to_register(lhs_value)?; let rhs_reg = self.value_to_register(rhs_value)?; let res_reg = self.reg_seqr.next(); - let comment = String::new(); - let owning_span = self.md_mgr.val_to_span(self.context, *instr_val); - match pred { - Predicate::Equal => { - self.cur_bytecode.push(Op { - opcode: Either::Left(VirtualOp::EQ(res_reg.clone(), lhs_reg, rhs_reg)), - comment, - owning_span, - }); - } - Predicate::LessThan => { - self.cur_bytecode.push(Op { - opcode: Either::Left(VirtualOp::LT(res_reg.clone(), lhs_reg, rhs_reg)), - comment, - owning_span, - }); - } - Predicate::GreaterThan => { - self.cur_bytecode.push(Op { - opcode: Either::Left(VirtualOp::GT(res_reg.clone(), lhs_reg, rhs_reg)), - comment, - owning_span, - }); - } - } + + // Check op is on 256 bits and use the special opcodes + let lhs_width = lhs_value + .get_type(self.context) + .and_then(|x| x.get_uint_width(self.context)); + let rhs_width = rhs_value + .get_type(self.context) + .and_then(|x| x.get_uint_width(self.context)); + let (opcode, comment) = match (lhs_width, rhs_width) { + (Some(256), Some(256)) => match pred { + Predicate::Equal => ( + VirtualOp::WQCM( + res_reg.clone(), + lhs_reg, + rhs_reg, + WideCmpImmediate { + op: WideCmpOp::Equality, + right_indirect: true, + }, + ), + Some("eq cmp"), + ), + Predicate::LessThan => ( + VirtualOp::WQCM( + res_reg.clone(), + lhs_reg, + rhs_reg, + WideCmpImmediate { + op: WideCmpOp::LessThan, + right_indirect: true, + }, + ), + Some("gt cmp"), + ), + Predicate::GreaterThan => ( + VirtualOp::WQCM( + res_reg.clone(), + lhs_reg, + rhs_reg, + WideCmpImmediate { + op: WideCmpOp::GreaterThan, + right_indirect: true, + }, + ), + Some("gt cmp"), + ), + }, + _ => match pred { + Predicate::Equal => (VirtualOp::EQ(res_reg.clone(), lhs_reg, rhs_reg), None), + Predicate::LessThan => (VirtualOp::LT(res_reg.clone(), lhs_reg, rhs_reg), None), + Predicate::GreaterThan => (VirtualOp::GT(res_reg.clone(), lhs_reg, rhs_reg), None), + }, + }; + + self.cur_bytecode.push(Op { + opcode: Either::Left(opcode), + comment: comment.map(Into::into).unwrap_or_default(), + owning_span: self.md_mgr.val_to_span(self.context, *instr_val), + }); + self.reg_map.insert(*instr_val, res_reg); Ok(()) } @@ -671,8 +854,9 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { idx_val .get_constant(self.context) .and_then(|idx_const| { - if let ConstantValue::Uint(idx) = idx_const.value { - Some(idx as usize) + if let ConstantValue::Uint(idx) = &idx_const.value { + let idx = usize::try_from(idx).unwrap(); + Some(idx) } else { None } @@ -918,6 +1102,28 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { fn compile_load(&mut self, instr_val: &Value, src_val: &Value) -> Result<(), CompileError> { let owning_span = self.md_mgr.val_to_span(self.context, *instr_val); + + // if is ptr u256, do nothing + match src_val + .get_type(self.context) + .and_then(|x| x.get_pointee_type(self.context)) + { + Some(t) if t.is_uint_of(self.context, 256) => { + let src_reg = self.value_to_register(src_val)?; + let instr_reg = self.reg_seqr.next(); + + self.cur_bytecode.push(Op { + opcode: Either::Left(VirtualOp::MOVE(instr_reg.clone(), src_reg)), + comment: "load value".into(), + owning_span, + }); + + self.reg_map.insert(*instr_val, instr_reg); + return Ok(()); + } + _ => {} + } + if src_val .get_type(self.context) .and_then(|src_ty| src_ty.get_pointee_type(self.context)) @@ -1399,20 +1605,26 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { )) } } else { - let dst_reg = self.value_to_register(dst_val)?; - let val_reg = self.value_to_register(stored_val)?; - - self.cur_bytecode.push(Op { - opcode: Either::Left(VirtualOp::SW( - dst_reg, - val_reg, - VirtualImmediate12 { value: 0 }, - )), - comment: "store value".into(), - owning_span, - }); - - Ok(()) + let stored_ty = stored_val.get_type(self.context).unwrap(); + let stored_size_bytes = ir_type_size_in_bytes(self.context, &stored_ty); + // If fits into one word, use SW + if stored_size_bytes < 8 { + let dst_reg = self.value_to_register(dst_val)?; + let val_reg = self.value_to_register(stored_val)?; + self.cur_bytecode.push(Op { + opcode: Either::Left(VirtualOp::SW( + dst_reg, + val_reg, + VirtualImmediate12 { value: 0 }, + )), + comment: "store value".into(), + owning_span, + }); + Ok(()) + } else { + // otherwise memcpy + self.compile_mem_copy_bytes(instr_val, dst_val, stored_val, stored_size_bytes) + } } } @@ -1441,15 +1653,36 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { config_name: Option, span: Option, ) -> (VirtualRegister, Option) { + // Use cheaper $zero or $one registers if possible. match &constant.value { - // Use cheaper $zero or $one registers if possible. - ConstantValue::Unit | ConstantValue::Bool(false) | ConstantValue::Uint(0) - if config_name.is_none() => + ConstantValue::Uint(n) + if n.is_zero() + && config_name.is_none() + && constant + .ty + .get_uint_width(self.context) + .map(|x| x <= 64) + .unwrap_or(false) => { (VirtualRegister::Constant(ConstantRegister::Zero), None) } - ConstantValue::Bool(true) | ConstantValue::Uint(1) if config_name.is_none() => { + ConstantValue::Unit | ConstantValue::Bool(false) if config_name.is_none() => { + (VirtualRegister::Constant(ConstantRegister::Zero), None) + } + + ConstantValue::Uint(num) + if num.is_one() + && config_name.is_none() + && constant + .ty + .get_uint_width(self.context) + .map(|x| x <= 64) + .unwrap_or(false) => + { + (VirtualRegister::Constant(ConstantRegister::One), None) + } + ConstantValue::Bool(true) if config_name.is_none() => { (VirtualRegister::Constant(ConstantRegister::One), None) } diff --git a/sway-core/src/asm_generation/fuel/functions.rs b/sway-core/src/asm_generation/fuel/functions.rs index 14f63e5f741..355fbe9bbd7 100644 --- a/sway-core/src/asm_generation/fuel/functions.rs +++ b/sway-core/src/asm_generation/fuel/functions.rs @@ -645,6 +645,7 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { let ptr_ty = ptr.get_inner_type(self.context); let var_size = match ptr_ty.get_content(self.context) { + TypeContent::Uint(256) => 4, TypeContent::Unit | TypeContent::Bool | TypeContent::Uint(_) @@ -672,6 +673,26 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { }, ); + // Reserve space for wide operations locals + let mut cfei_value = stack_base * 8; + for i in function.instruction_iter(self.context) { + match i.1.get_instruction(self.context) { + Some(i) => match i { + Instruction::BinaryOp { arg1, .. } => { + if let Some(256) = arg1 + .get_type(self.context) + .unwrap() + .get_uint_width(self.context) + { + cfei_value += 32; + } + } + _ => {} + }, + None => todo!(), + } + } + // Reserve space on the stack (in bytes) for all our locals which require it. Firstly save // the current $sp. let locals_base_reg = VirtualRegister::Constant(ConstantRegister::LocalsBase); @@ -688,9 +709,9 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { } self.cur_bytecode.push(Op { opcode: Either::Left(VirtualOp::CFEI(VirtualImmediate24 { - value: locals_size as u32, + value: cfei_value as u32, })), - comment: format!("allocate {locals_size} bytes for locals"), + comment: format!("allocate {cfei_value} bytes for locals"), owning_span: None, }); (locals_size, locals_base_reg, init_mut_vars) diff --git a/sway-core/src/asm_generation/miden_vm/miden_vm_asm_builder.rs b/sway-core/src/asm_generation/miden_vm/miden_vm_asm_builder.rs index 7efd3a23480..8b7f685dac6 100644 --- a/sway-core/src/asm_generation/miden_vm/miden_vm_asm_builder.rs +++ b/sway-core/src/asm_generation/miden_vm/miden_vm_asm_builder.rs @@ -615,11 +615,14 @@ impl<'ir, 'eng> MidenVMAsmBuilder<'ir, 'eng> { /// Pushes a constant to the top of the stack pub(crate) fn render_constant(&self, constant: &Constant) -> Vec { use sway_ir::ConstantValue::*; - match constant.value { + match &constant.value { Undef => todo!(), Unit => vec![DirectOp::push(MidenStackValue::Unit)], - Bool(b) => vec![DirectOp::push(b)], - Uint(x) => vec![DirectOp::push(x)], + Bool(b) => vec![DirectOp::push(*b)], + Uint(x) => { + let x = u64::try_from(x).unwrap(); + vec![DirectOp::push(x)] + } B256(_) => todo!(), String(_) => todo!(), Array(_) => todo!(), diff --git a/sway-core/src/asm_generation/programs/abstract.rs b/sway-core/src/asm_generation/programs/abstract.rs index cb52a9ab7de..0c0569780e9 100644 --- a/sway-core/src/asm_generation/programs/abstract.rs +++ b/sway-core/src/asm_generation/programs/abstract.rs @@ -70,6 +70,7 @@ impl AbstractProgram { .collect::, _>>()?; // Allocate the registers for each function. + let functions = abstract_functions .into_iter() .map(|fn_ops| { diff --git a/sway-core/src/asm_lang/allocated_ops.rs b/sway-core/src/asm_lang/allocated_ops.rs index b2caec4ba1a..7d84ed818c3 100644 --- a/sway-core/src/asm_lang/allocated_ops.rs +++ b/sway-core/src/asm_lang/allocated_ops.rs @@ -91,6 +91,30 @@ pub(crate) enum AllocatedOpcode { SUBI(AllocatedRegister, AllocatedRegister, VirtualImmediate12), XOR(AllocatedRegister, AllocatedRegister, AllocatedRegister), XORI(AllocatedRegister, AllocatedRegister, VirtualImmediate12), + WQOP( + AllocatedRegister, + AllocatedRegister, + AllocatedRegister, + VirtualImmediate06, + ), + WQML( + AllocatedRegister, + AllocatedRegister, + AllocatedRegister, + VirtualImmediate06, + ), + WQDV( + AllocatedRegister, + AllocatedRegister, + AllocatedRegister, + VirtualImmediate06, + ), + WQCM( + AllocatedRegister, + AllocatedRegister, + AllocatedRegister, + VirtualImmediate06, + ), /* Conrol Flow Instructions */ JMP(AllocatedRegister), @@ -243,6 +267,10 @@ impl AllocatedOpcode { SUBI(r1, _r2, _i) => vec![r1], XOR(r1, _r2, _r3) => vec![r1], XORI(r1, _r2, _i) => vec![r1], + WQOP(r1, _, _, _) => vec![r1], + WQML(r1, _, _, _) => vec![r1], + WQDV(r1, _, _, _) => vec![r1], + WQCM(r1, _, _, _) => vec![r1], /* Control Flow Instructions */ JMP(_r1) => vec![], @@ -356,6 +384,10 @@ impl fmt::Display for AllocatedOpcode { SUBI(a, b, c) => write!(fmtr, "subi {a} {b} {c}"), XOR(a, b, c) => write!(fmtr, "xor {a} {b} {c}"), XORI(a, b, c) => write!(fmtr, "xori {a} {b} {c}"), + WQOP(a, b, c, d) => write!(fmtr, "wqop {a} {b} {c} {d}"), + WQML(a, b, c, d) => write!(fmtr, "wqml {a} {b} {c} {d}"), + WQDV(a, b, c, d) => write!(fmtr, "wqdv {a} {b} {c} {d}"), + WQCM(a, b, c, d) => write!(fmtr, "wqcm {a} {b} {c} {d}"), /* Control Flow Instructions */ JMP(a) => write!(fmtr, "jmp {a}"), @@ -463,7 +495,7 @@ type DoubleWideData = [u8; 8]; impl AllocatedOp { pub(crate) fn to_fuel_asm( &self, - offset_to_data_section: u64, + offset_to_data_section_in_bytes: u64, data_section: &mut DataSection, ) -> Either, DoubleWideData> { use AllocatedOpcode::*; @@ -500,6 +532,18 @@ impl AllocatedOp { SUBI(a, b, c) => op::SUBI::new(a.to_reg_id(), b.to_reg_id(), c.value.into()).into(), XOR(a, b, c) => op::XOR::new(a.to_reg_id(), b.to_reg_id(), c.to_reg_id()).into(), XORI(a, b, c) => op::XORI::new(a.to_reg_id(), b.to_reg_id(), c.value.into()).into(), + WQOP(a, b, c, d) => { + op::WQOP::new(a.to_reg_id(), b.to_reg_id(), c.to_reg_id(), d.value.into()).into() + } + WQML(a, b, c, d) => { + op::WQML::new(a.to_reg_id(), b.to_reg_id(), c.to_reg_id(), d.value.into()).into() + } + WQDV(a, b, c, d) => { + op::WQDV::new(a.to_reg_id(), b.to_reg_id(), c.to_reg_id(), d.value.into()).into() + } + WQCM(a, b, c, d) => { + op::WQCM::new(a.to_reg_id(), b.to_reg_id(), c.to_reg_id(), d.value.into()).into() + } /* Control Flow Instructions */ JMP(a) => op::JMP::new(a.to_reg_id()).into(), @@ -590,7 +634,7 @@ impl AllocatedOp { ) } DataSectionOffsetPlaceholder => { - return Either::Right(offset_to_data_section.to_be_bytes()) + return Either::Right(offset_to_data_section_in_bytes.to_be_bytes()) } DataSectionRegisterLoadPlaceholder => op::LW::new( fuel_asm::RegId::new(DATA_SECTION_REGISTER), @@ -599,7 +643,12 @@ impl AllocatedOp { ) .into(), LWDataId(a, b) => { - return Either::Left(realize_lw(a, b, data_section, offset_to_data_section)) + return Either::Left(realize_lw( + a, + b, + data_section, + offset_to_data_section_in_bytes, + )) } Undefined => unreachable!("Sway cannot generate undefined ASM opcodes"), }]) @@ -614,7 +663,7 @@ fn realize_lw( dest: &AllocatedRegister, data_id: &DataId, data_section: &mut DataSection, - offset_to_data_section: u64, + offset_to_data_section_in_bytes: u64, ) -> Vec { // all data is word-aligned right now, and `offset_to_id` returns the offset in bytes let offset_bytes = data_section.data_id_to_offset(data_id) as u64; @@ -633,18 +682,19 @@ fn realize_lw( // load the pointer itself into the register // `offset_to_data_section` is in bytes. We want a byte // address here - let pointer_offset_from_instruction_start = offset_to_data_section + offset_bytes; + let pointer_offset_from_instruction_start = offset_to_data_section_in_bytes + offset_bytes; // insert the pointer as bytes as a new data section entry at the end of the data let data_id_for_pointer = data_section.append_pointer(pointer_offset_from_instruction_start); // now load the pointer we just created into the `dest`ination let mut buf = Vec::with_capacity(2); - buf.append(&mut realize_lw( + let v = &mut realize_lw( dest, &data_id_for_pointer, data_section, - offset_to_data_section, - )); + offset_to_data_section_in_bytes, + ); + buf.append(v); // add $is to the pointer since it is relative to the data section buf.push( fuel_asm::op::ADD::new( diff --git a/sway-core/src/asm_lang/mod.rs b/sway-core/src/asm_lang/mod.rs index 9f544cff6ac..7f41619b9f2 100644 --- a/sway-core/src/asm_lang/mod.rs +++ b/sway-core/src/asm_lang/mod.rs @@ -1440,6 +1440,10 @@ impl fmt::Display for VirtualOp { SUBI(a, b, c) => write!(fmtr, "subi {a} {b} {c}"), XOR(a, b, c) => write!(fmtr, "xor {a} {b} {c}"), XORI(a, b, c) => write!(fmtr, "xori {a} {b} {c}"), + WQOP(a, b, c, d) => write!(fmtr, "wqop {a} {b} {c} {d}"), + WQML(a, b, c, d) => write!(fmtr, "wqml {a} {b} {c} {d}"), + WQDV(a, b, c, d) => write!(fmtr, "wqdv {a} {b} {c} {d}"), + WQCM(a, b, c, d) => write!(fmtr, "wqcm {a} {b} {c} {d}"), /* Control Flow Instructions */ JMP(a) => write!(fmtr, "jmp {a}"), diff --git a/sway-core/src/asm_lang/virtual_ops.rs b/sway-core/src/asm_lang/virtual_ops.rs index dfb352b7b45..2363f3c9e1e 100644 --- a/sway-core/src/asm_lang/virtual_ops.rs +++ b/sway-core/src/asm_lang/virtual_ops.rs @@ -11,11 +11,142 @@ use super::{ Op, }; use crate::asm_generation::fuel::{data_section::DataId, register_allocator::RegisterPool}; - use std::collections::{BTreeSet, HashMap}; - use std::fmt; +#[derive(Clone, Copy, Debug)] +#[repr(u16)] +pub enum WideOperation { + // Add + Add = 0, + // Subtract + Sub = 1, + // Invert bits (discards rhs) + Not = 2, + // Bitwise or + Or = 3, + // Bitwise exclusive or + Xor = 4, + // Bitwise and + And = 5, + // Shift left (logical) + Shl = 6, + // Shift right (logical) + Shr = 7, +} + +#[derive(Clone, Debug)] +pub(crate) struct WideOperationsImmediate { + pub op: WideOperation, + pub right_indirect: bool, +} + +impl fmt::Display for WideOperationsImmediate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "i{}", self.immediate_value()) + } +} + +impl WideOperationsImmediate { + pub fn immediate_value(&self) -> VirtualImmediate06 { + let a = self.op as u8; + let b = if self.right_indirect { 32u8 } else { 0u8 }; + VirtualImmediate06 { value: a | b } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct WideMulImmediate { + pub left_indirect: bool, + pub right_indirect: bool, +} + +impl fmt::Display for WideMulImmediate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "i{}", self.immediate_value()) + } +} + +impl WideMulImmediate { + pub fn immediate_value(&self) -> VirtualImmediate06 { + let a = if self.left_indirect { 16 } else { 0 }; + let b = if self.right_indirect { 32 } else { 0 }; + VirtualImmediate06 { value: a | b } + } +} + +#[derive(Clone, Debug)] +pub(crate) struct WideDivImmediate { + pub right_indirect: bool, +} + +impl fmt::Display for WideDivImmediate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "i{}", self.immediate_value()) + } +} + +impl WideDivImmediate { + pub fn immediate_value(&self) -> VirtualImmediate06 { + let a = if self.right_indirect { 32 } else { 0 }; + VirtualImmediate06 { value: a } + } +} + +#[repr(u8)] +#[derive(Clone, Debug)] +pub(crate) enum WideCmpOp { + Equality = 0, + Inequality = 1, + LessThan = 2, + GreaterThan = 3, + LessThanOrEquals = 4, + GreaterThanOrEquals = 5, +} + +#[derive(Clone, Debug)] +pub(crate) struct WideCmpImmediate { + pub op: WideCmpOp, + pub right_indirect: bool, +} + +impl fmt::Display for WideCmpImmediate { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "i{}", self.immediate_value()) + } +} + +impl WideCmpImmediate { + pub fn immediate_value(&self) -> VirtualImmediate06 { + let a = self.op.clone() as u8; + let b = if self.right_indirect { 32 } else { 0 }; + VirtualImmediate06 { value: a | b } + } +} + +#[test] +fn wq_imm_tests() { + assert_eq!( + 32, + WideOperationsImmediate { + op: WideOperation::Add, + right_indirect: true, + } + .immediate_value() + .value + ); + + assert_eq!( + 32, + WideCmpImmediate { + op: WideCmpOp::Equality, + right_indirect: true, + } + .immediate_value() + .value + ); +} + /// This enum is unfortunately a redundancy of the [fuel_asm::Opcode] enum. This variant, however, /// allows me to use the compiler's internal [VirtualRegister] types and maintain type safety /// between virtual ops and the real opcodes. A bit of copy/paste seemed worth it for that safety, @@ -55,6 +186,34 @@ pub(crate) enum VirtualOp { SUBI(VirtualRegister, VirtualRegister, VirtualImmediate12), XOR(VirtualRegister, VirtualRegister, VirtualRegister), XORI(VirtualRegister, VirtualRegister, VirtualImmediate12), + /// WQOP: Misc 256-bit integer operations + WQOP( + VirtualRegister, + VirtualRegister, + VirtualRegister, + WideOperationsImmediate, + ), + // WQML: Multiply 256-bit integers + WQML( + VirtualRegister, + VirtualRegister, + VirtualRegister, + WideMulImmediate, + ), + // WQDV: 256-bit integer division + WQDV( + VirtualRegister, + VirtualRegister, + VirtualRegister, + WideDivImmediate, + ), + // WQCM: 256-bit integer comparison + WQCM( + VirtualRegister, + VirtualRegister, + VirtualRegister, + WideCmpImmediate, + ), /* Control Flow Instructions */ JMP(VirtualRegister), @@ -207,6 +366,10 @@ impl VirtualOp { SUBI(r1, r2, _i) => vec![r1, r2], XOR(r1, r2, r3) => vec![r1, r2, r3], XORI(r1, r2, _i) => vec![r1, r2], + WQOP(r1, r2, r3, _) => vec![r1, r2, r3], + WQML(r1, r2, r3, _) => vec![r1, r2, r3], + WQDV(r1, r2, r3, _) => vec![r1, r2, r3], + WQCM(r1, r2, r3, _) => vec![r1, r2, r3], /* Control Flow Instructions */ JMP(r1) => vec![r1], @@ -316,6 +479,10 @@ impl VirtualOp { SUBI(_r1, r2, _i) => vec![r2], XOR(_r1, r2, r3) => vec![r2, r3], XORI(_r1, r2, _i) => vec![r2], + WQOP(_, r2, r3, _) => vec![r2, r3], + WQML(_, r2, r3, _) => vec![r2, r3], + WQDV(_, r2, r3, _) => vec![r2, r3], + WQCM(_, r2, r3, _) => vec![r2, r3], /* Control Flow Instructions */ JMP(r1) => vec![r1], @@ -425,6 +592,10 @@ impl VirtualOp { SUBI(r1, _r2, _i) => vec![r1], XOR(r1, _r2, _r3) => vec![r1], XORI(r1, _r2, _i) => vec![r1], + WQOP(r1, _, _, _) => vec![r1], + WQML(r1, _, _, _) => vec![r1], + WQDV(r1, _, _, _) => vec![r1], + WQCM(r1, _, _, _) => vec![r1], /* Control Flow Instructions */ JMP(_r1) => vec![], @@ -670,6 +841,30 @@ impl VirtualOp { update_reg(reg_to_reg_map, r2), i.clone(), ), + WQOP(r1, r2, r3, i) => Self::WQOP( + update_reg(reg_to_reg_map, r1), + update_reg(reg_to_reg_map, r2), + update_reg(reg_to_reg_map, r3), + i.clone(), + ), + WQML(r1, r2, r3, i) => Self::WQML( + update_reg(reg_to_reg_map, r1), + update_reg(reg_to_reg_map, r2), + update_reg(reg_to_reg_map, r3), + i.clone(), + ), + WQDV(r1, r2, r3, i) => Self::WQDV( + update_reg(reg_to_reg_map, r1), + update_reg(reg_to_reg_map, r2), + update_reg(reg_to_reg_map, r3), + i.clone(), + ), + WQCM(r1, r2, r3, i) => Self::WQCM( + update_reg(reg_to_reg_map, r1), + update_reg(reg_to_reg_map, r2), + update_reg(reg_to_reg_map, r3), + i.clone(), + ), /* Control Flow Instructions */ JMP(r1) => Self::JMP(update_reg(reg_to_reg_map, r1)), @@ -1088,6 +1283,30 @@ impl VirtualOp { map_reg(&mapping, reg2), imm.clone(), ), + WQOP(reg1, reg2, reg3, imm) => AllocatedOpcode::WQOP( + map_reg(&mapping, reg1), + map_reg(&mapping, reg2), + map_reg(&mapping, reg3), + imm.immediate_value(), + ), + WQML(reg1, reg2, reg3, imm) => AllocatedOpcode::WQML( + map_reg(&mapping, reg1), + map_reg(&mapping, reg2), + map_reg(&mapping, reg3), + imm.immediate_value(), + ), + WQDV(reg1, reg2, reg3, imm) => AllocatedOpcode::WQDV( + map_reg(&mapping, reg1), + map_reg(&mapping, reg2), + map_reg(&mapping, reg3), + imm.immediate_value(), + ), + WQCM(reg1, reg2, reg3, imm) => AllocatedOpcode::WQCM( + map_reg(&mapping, reg1), + map_reg(&mapping, reg2), + map_reg(&mapping, reg3), + imm.immediate_value(), + ), /* Control Flow Instructions */ JMP(reg1) => AllocatedOpcode::JMP(map_reg(&mapping, reg1)), diff --git a/sway-core/src/ir_generation.rs b/sway-core/src/ir_generation.rs index 8c0dc9f835d..0ba40406965 100644 --- a/sway-core/src/ir_generation.rs +++ b/sway-core/src/ir_generation.rs @@ -98,7 +98,7 @@ pub fn compile_program<'eng>( ), }?; - //println!("{ctx}"); + // println!("{}", ctx.to_string()); ctx.verify().map_err(|ir_error: sway_ir::IrError| { CompileError::InternalOwned(ir_error.to_string(), Span::dummy()) diff --git a/sway-core/src/ir_generation/const_eval.rs b/sway-core/src/ir_generation/const_eval.rs index 85fec65b37a..75f1fce5e1b 100644 --- a/sway-core/src/ir_generation/const_eval.rs +++ b/sway-core/src/ir_generation/const_eval.rs @@ -1,4 +1,4 @@ -use std::ops::{BitAnd, BitOr, BitXor}; +use std::ops::{BitAnd, BitOr, BitXor, Shl, Shr}; use crate::{ asm_generation::from_ir::{ir_type_size_in_bytes, ir_type_str_size_in_bytes}, @@ -18,6 +18,7 @@ use super::{ types::*, }; +use num_traits::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; use sway_ast::Intrinsic; use sway_error::error::CompileError; use sway_ir::{ @@ -462,7 +463,7 @@ fn const_eval_typed_expr( ); if let Ok(enum_ty) = aggregate { - let tag_value = Constant::new_uint(lookup.context, 64, *tag as u64); + let tag_value = Constant::new_uint(lookup.context, 64, (*tag).into()); let mut fields: Vec = vec![tag_value]; match contents { @@ -590,6 +591,7 @@ fn const_eval_typed_expr( .. }), ) => { + let index = u64::try_from(index).unwrap(); let count = items.len() as u64; if index < count { Some(items[index as usize].clone()) @@ -747,14 +749,15 @@ fn const_eval_intrinsic( // All arithmetic is done as if it were u64 let result = match intrinsic.kind { - Intrinsic::Add => arg1.checked_add(*arg2), - Intrinsic::Sub => arg1.checked_sub(*arg2), - Intrinsic::Mul => arg1.checked_mul(*arg2), - Intrinsic::Div => arg1.checked_div(*arg2), + Intrinsic::Add => arg1.checked_add(arg2), + Intrinsic::Sub => arg1.checked_sub(arg2), + Intrinsic::Mul => arg1.checked_mul(arg2), + Intrinsic::Div => arg1.checked_div(arg2), Intrinsic::And => Some(arg1.bitand(arg2)), - Intrinsic::Or => Some(arg1.bitor(*arg2)), - Intrinsic::Xor => Some(arg1.bitxor(*arg2)), - Intrinsic::Mod => arg1.checked_rem(*arg2), + Intrinsic::Or => Some(arg1.bitor(arg2)), + Intrinsic::Xor => Some(arg1.bitxor(arg2)), + // TODO This needs to be checked? + Intrinsic::Mod => Some(arg1.modpow(&(1u64.into()), arg2)), _ => unreachable!(), }; @@ -769,7 +772,7 @@ fn const_eval_intrinsic( } } sway_ast::Intrinsic::Lsh | sway_ast::Intrinsic::Rsh => { - let ty = args[0].ty; + let ty: Type = args[0].ty; assert!( args.len() == 2 && ty.is_uint(lookup.context) @@ -782,13 +785,14 @@ fn const_eval_intrinsic( panic!("Type checker allowed incorrect args to binary op"); }; + // TODO these need to be checked? let result = match intrinsic.kind { - Intrinsic::Lsh => u32::try_from(*arg2) + Intrinsic::Lsh => u32::try_from(arg2.clone()) .ok() - .and_then(|arg2| arg1.checked_shl(arg2)), - Intrinsic::Rsh => u32::try_from(*arg2) + .and_then(|arg2| Some(arg1.shl(arg2))), + Intrinsic::Rsh => u32::try_from(arg2.clone()) .ok() - .and_then(|arg2| arg1.checked_shr(arg2)), + .and_then(|arg2| Some(arg1.shr(arg2))), _ => unreachable!(), }; @@ -814,7 +818,7 @@ fn const_eval_intrinsic( .map_err(ConstEvalError::CompileError)?; Ok(Some(Constant { ty: Type::get_uint64(lookup.context), - value: ConstantValue::Uint(ir_type_size_in_bytes(lookup.context, &ir_type)), + value: ConstantValue::Uint(ir_type_size_in_bytes(lookup.context, &ir_type).into()), })) } sway_ast::Intrinsic::SizeOfVal => { @@ -830,7 +834,7 @@ fn const_eval_intrinsic( .map_err(ConstEvalError::CompileError)?; Ok(Some(Constant { ty: Type::get_uint64(lookup.context), - value: ConstantValue::Uint(ir_type_size_in_bytes(lookup.context, &ir_type)), + value: ConstantValue::Uint(ir_type_size_in_bytes(lookup.context, &ir_type).into()), })) } sway_ast::Intrinsic::SizeOfStr => { @@ -845,7 +849,9 @@ fn const_eval_intrinsic( .map_err(ConstEvalError::CompileError)?; Ok(Some(Constant { ty: Type::get_uint64(lookup.context), - value: ConstantValue::Uint(ir_type_str_size_in_bytes(lookup.context, &ir_type)), + value: ConstantValue::Uint( + ir_type_str_size_in_bytes(lookup.context, &ir_type).into(), + ), })) } sway_ast::Intrinsic::Eq => { @@ -908,16 +914,28 @@ fn const_eval_intrinsic( }; let v = match arg.ty.get_uint_width(lookup.context) { - Some(8) => !(v as u8) as u64, - Some(16) => !(v as u16) as u64, - Some(32) => !(v as u32) as u64, - Some(64) => !v, + Some(8) => { + let v = u8::try_from(v).unwrap(); + !(v as u8) as u64 + } + Some(16) => { + let v = u16::try_from(v).unwrap(); + !(v as u16) as u64 + } + Some(32) => { + let v = u32::try_from(v).unwrap(); + !(v as u32) as u64 + } + Some(64) => { + let v = u64::try_from(v).unwrap(); + !v + } _ => unreachable!("Invalid unsigned integer width"), }; Ok(Some(Constant { ty: arg.ty, - value: ConstantValue::Uint(v), + value: ConstantValue::Uint(v.into()), })) } } diff --git a/sway-core/src/ir_generation/convert.rs b/sway-core/src/ir_generation/convert.rs index 0f26a80a472..d6dbda32007 100644 --- a/sway-core/src/ir_generation/convert.rs +++ b/sway-core/src/ir_generation/convert.rs @@ -9,7 +9,7 @@ use super::types::{create_tagged_union_type, create_tuple_aggregate}; use sway_error::error::CompileError; use sway_ir::{Constant, Context, Type, Value}; -use sway_types::span::Span; +use sway_types::{integer_bits::IntegerBits, span::Span}; pub(super) fn convert_literal_to_value(context: &mut Context, ast_literal: &Literal) -> Value { match ast_literal { @@ -21,11 +21,12 @@ pub(super) fn convert_literal_to_value(context: &mut Context, ast_literal: &Lite // concerned. // // XXX The above isn't true for other targets. We need to improved this. - Literal::U8(n) => Constant::get_uint(context, 64, *n as u64), - Literal::U16(n) => Constant::get_uint(context, 64, *n as u64), - Literal::U32(n) => Constant::get_uint(context, 64, *n as u64), - Literal::U64(n) => Constant::get_uint(context, 64, *n), - Literal::Numeric(n) => Constant::get_uint(context, 64, *n), + Literal::U8(n) => Constant::get_uint(context, 64, (*n).into()), + Literal::U16(n) => Constant::get_uint(context, 64, (*n).into()), + Literal::U32(n) => Constant::get_uint(context, 64, (*n).into()), + Literal::U64(n) => Constant::get_uint(context, 64, (*n).into()), + Literal::U256(n) => Constant::get_uint(context, 256, n.clone()), + Literal::Numeric(n) => Constant::get_uint(context, 64, n.clone()), Literal::String(s) => Constant::get_string(context, s.as_str().as_bytes().to_vec()), Literal::Boolean(b) => Constant::get_bool(context, *b), Literal::B256(bs) => Constant::get_b256(context, *bs), @@ -38,11 +39,12 @@ pub(super) fn convert_literal_to_constant( ) -> Constant { match ast_literal { // All integers are `u64`. See comment above. - Literal::U8(n) => Constant::new_uint(context, 64, *n as u64), - Literal::U16(n) => Constant::new_uint(context, 64, *n as u64), - Literal::U32(n) => Constant::new_uint(context, 64, *n as u64), - Literal::U64(n) => Constant::new_uint(context, 64, *n), - Literal::Numeric(n) => Constant::new_uint(context, 64, *n), + Literal::U8(n) => Constant::new_uint(context, 64, (*n).into()), + Literal::U16(n) => Constant::new_uint(context, 64, (*n).into()), + Literal::U32(n) => Constant::new_uint(context, 64, (*n).into()), + Literal::U64(n) => Constant::new_uint(context, 64, (*n).into()), + Literal::U256(_) => todo!(), + Literal::Numeric(n) => Constant::new_uint(context, 64, n.clone()), Literal::String(s) => Constant::new_string(context, s.as_str().as_bytes().to_vec()), Literal::Boolean(b) => Constant::new_bool(context, *b), Literal::B256(bs) => Constant::new_b256(context, *bs), @@ -99,6 +101,7 @@ fn convert_resolved_type( Ok(match ast_type { // All integers are `u64`, see comment in convert_literal_to_value() above. + TypeInfo::UnsignedInteger(IntegerBits::V256) => Type::get_uint(context, 256), TypeInfo::UnsignedInteger(_) => Type::get_uint64(context), TypeInfo::Numeric => Type::get_uint64(context), TypeInfo::Boolean => Type::get_bool(context), diff --git a/sway-core/src/ir_generation/function.rs b/sway-core/src/ir_generation/function.rs index a93ef16451a..57412b35541 100644 --- a/sway-core/src/ir_generation/function.rs +++ b/sway-core/src/ir_generation/function.rs @@ -509,7 +509,7 @@ impl<'eng> FnCompiler<'eng> { Ok(Constant::get_uint( context, 64, - ir_type_size_in_bytes(context, &ir_type), + ir_type_size_in_bytes(context, &ir_type).into(), )) } Intrinsic::SizeOfType => { @@ -524,7 +524,7 @@ impl<'eng> FnCompiler<'eng> { Ok(Constant::get_uint( context, 64, - ir_type_size_in_bytes(context, &ir_type), + ir_type_size_in_bytes(context, &ir_type).into(), )) } Intrinsic::SizeOfStr => { @@ -539,7 +539,7 @@ impl<'eng> FnCompiler<'eng> { Ok(Constant::get_uint( context, 64, - ir_type_str_size_in_bytes(context, &ir_type), + ir_type_str_size_in_bytes(context, &ir_type).into(), )) } Intrinsic::IsReferenceType => { @@ -607,6 +607,7 @@ impl<'eng> FnCompiler<'eng> { let span_md_idx = md_mgr.span_to_md(context, &span); // The `gtf` instruction + let tx_field_id = u64::try_from(tx_field_id).unwrap(); let gtf_reg = self .current_block .ins(context) @@ -793,6 +794,7 @@ impl<'eng> FnCompiler<'eng> { Intrinsic::Lsh => BinaryOpKind::Lsh, _ => unreachable!(), }; + let lhs = &arguments[0]; let rhs = &arguments[1]; let lhs_value = self.compile_expression_to_value(context, md_mgr, lhs)?; @@ -829,8 +831,11 @@ impl<'eng> FnCompiler<'eng> { &len.type_id, &len.span, )?; - let len_value = - Constant::get_uint(context, 64, ir_type_size_in_bytes(context, &ir_type)); + let len_value = Constant::get_uint( + context, + 64, + ir_type_size_in_bytes(context, &ir_type).into(), + ); let lhs = &arguments[0]; let count = &arguments[1]; @@ -901,7 +906,7 @@ impl<'eng> FnCompiler<'eng> { let message_id_val = self .messages_types_map .get(&arguments[1].return_type) - .map(|&msg_id| Constant::get_uint(context, 64, *msg_id as u64)) + .map(|&msg_id| Constant::get_uint(context, 64, (*msg_id).into())) .ok_or_else(|| { CompileError::Internal( "Unable to determine ID for smo instance.", @@ -930,7 +935,8 @@ impl<'eng> FnCompiler<'eng> { .add_metadatum(context, span_md_idx); /* Third operand: the size of the message data */ - let user_message_size_val = Constant::get_uint(context, 64, user_message_size); + let user_message_size_val = + Constant::get_uint(context, 64, user_message_size.into()); /* Fourth operand: the amount of coins to send */ let coins = self.compile_expression_to_value(context, md_mgr, &arguments[2])?; @@ -1071,7 +1077,7 @@ impl<'eng> FnCompiler<'eng> { let u64_ty = Type::get_uint64(context); let user_args_val = match compiled_args.len() { - 0 => Constant::get_uint(context, 64, 0), + 0 => Constant::get_uint(context, 64, 0u64.into()), 1 => { // The single arg doesn't need to be put into a struct. let arg0 = compiled_args[0]; @@ -1924,12 +1930,12 @@ impl<'eng> FnCompiler<'eng> { }) .map(|(field_idx, field_type_id)| { *cur_type_id = field_type_id; - Constant::get_uint(context, 64, field_idx) + Constant::get_uint(context, 64, field_idx.into()) }) } (ProjectionKind::TupleField { index, .. }, TypeInfo::Tuple(field_tys)) => { *cur_type_id = field_tys[*index].type_id; - Ok(Constant::get_uint(context, 64, *index as u64)) + Ok(Constant::get_uint(context, 64, (*index).into())) } (ProjectionKind::ArrayIndex { index, .. }, TypeInfo::Array(elem_ty, _)) => { *cur_type_id = elem_ty.type_id; @@ -2056,6 +2062,12 @@ impl<'eng> FnCompiler<'eng> { index_expr, ) { let count = array_type.get_array_len(context).unwrap(); + let constant_value = + u64::try_from(constant_value).map_err(|_| CompileError::ArrayOutOfBounds { + index: u64::MAX, + count, + span: index_expr_span.clone(), + })?; if constant_value >= count { return Err(CompileError::ArrayOutOfBounds { index: constant_value, @@ -2230,7 +2242,7 @@ impl<'eng> FnCompiler<'eng> { &enum_decl.variants, )?; let tag_value = - Constant::get_uint(context, 64, tag as u64).add_metadatum(context, span_md_idx); + Constant::get_uint(context, 64, tag.into()).add_metadatum(context, span_md_idx); // Start with a temporary local struct and insert the tag. let temp_name = self.lexical_map.insert_anon(); @@ -2582,7 +2594,7 @@ impl<'eng> FnCompiler<'eng> { .add_metadatum(context, span_md_idx); // Store the offset as the second field in the `StorageKey` struct - let offset_within_slot_val = Constant::get_uint(context, 64, offset_within_slot); + let offset_within_slot_val = Constant::get_uint(context, 64, offset_within_slot.into()); let gep_1_val = self.current_block .ins(context) diff --git a/sway-core/src/ir_generation/storage.rs b/sway-core/src/ir_generation/storage.rs index 3ba5b41217d..f107eebe463 100644 --- a/sway-core/src/ir_generation/storage.rs +++ b/sway-core/src/ir_generation/storage.rs @@ -88,7 +88,7 @@ pub fn serialize_to_storage_slots( vec![StorageSlot::new( get_storage_key(ix, indices), Bytes32::new( - n.to_be_bytes() + n.to_bytes_be() .iter() .cloned() .chain([0; 24].iter().cloned()) @@ -154,9 +154,11 @@ pub fn serialize_to_words(constant: &Constant, context: &Context, ty: &Type) -> .unwrap(), )] } - ConstantValue::Uint(n) if ty.is_uint(context) => { - vec![Bytes8::new(n.to_be_bytes())] - } + ConstantValue::Uint(n) if ty.is_uint(context) => n + .iter_u64_digits() + .rev() + .map(|n| Bytes8::new(n.to_be_bytes())) + .collect(), ConstantValue::B256(b) if ty.is_b256(context) => { Vec::from_iter((0..4).map(|i| Bytes8::new(b[8 * i..8 * i + 8].try_into().unwrap()))) } diff --git a/sway-core/src/language/literal.rs b/sway-core/src/language/literal.rs index 83714384e14..8a40042ed9b 100644 --- a/sway-core/src/language/literal.rs +++ b/sway-core/src/language/literal.rs @@ -1,5 +1,6 @@ use crate::{type_system::*, Engines}; +use num_bigint::BigUint; use sway_error::error::CompileError; use sway_types::{integer_bits::IntegerBits, span}; @@ -15,8 +16,9 @@ pub enum Literal { U16(u16), U32(u32), U64(u64), + U256(BigUint), String(span::Span), - Numeric(u64), + Numeric(BigUint), Boolean(bool), B256([u8; 32]), } @@ -41,6 +43,7 @@ impl Hash for Literal { state.write_u8(4); x.hash(state); } + U256(_) => todo!(), Numeric(x) => { state.write_u8(5); x.hash(state); @@ -68,6 +71,7 @@ impl PartialEq for Literal { (Self::U16(l0), Self::U16(r0)) => l0 == r0, (Self::U32(l0), Self::U32(r0)) => l0 == r0, (Self::U64(l0), Self::U64(r0)) => l0 == r0, + (Self::U256(l0), Self::U256(r0)) => l0 == r0, (Self::String(l0), Self::String(r0)) => *l0.as_str() == *r0.as_str(), (Self::Numeric(l0), Self::Numeric(r0)) => l0 == r0, (Self::Boolean(l0), Self::Boolean(r0)) => l0 == r0, @@ -84,6 +88,7 @@ impl fmt::Display for Literal { Literal::U16(content) => content.to_string(), Literal::U32(content) => content.to_string(), Literal::U64(content) => content.to_string(), + Literal::U256(_) => todo!(), Literal::Numeric(content) => content.to_string(), Literal::String(content) => content.as_str().to_string(), Literal::Boolean(content) => content.to_string(), @@ -132,6 +137,7 @@ impl Literal { Literal::U16(_) => TypeInfo::UnsignedInteger(IntegerBits::Sixteen), Literal::U32(_) => TypeInfo::UnsignedInteger(IntegerBits::ThirtyTwo), Literal::U64(_) => TypeInfo::UnsignedInteger(IntegerBits::SixtyFour), + Literal::U256(_) => todo!(), Literal::Boolean(_) => TypeInfo::Boolean, Literal::B256(_) => TypeInfo::B256, } diff --git a/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs b/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs index 14372e012bd..3d117172c2c 100644 --- a/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs +++ b/sway-core/src/semantic_analysis/ast_node/declaration/declaration.rs @@ -46,6 +46,7 @@ impl ty::TyDecl { with the assigned expression's type.", ); let result = ty::TyExpression::type_check(ctx.by_ref(), body); + let body = check!( result, ty::TyExpression::error(name.span(), engines), diff --git a/sway-core/src/semantic_analysis/ast_node/expression/match_expression/analysis/pattern.rs b/sway-core/src/semantic_analysis/ast_node/expression/match_expression/analysis/pattern.rs index f36ce58c6dd..c42019e44e9 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/match_expression/analysis/pattern.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/match_expression/analysis/pattern.rs @@ -194,9 +194,14 @@ impl Pattern { Literal::U16(x) => Pattern::U16(Range::from_single(x)), Literal::U32(x) => Pattern::U32(Range::from_single(x)), Literal::U64(x) => Pattern::U64(Range::from_single(x)), + Literal::U256(_) => todo!(), Literal::B256(x) => Pattern::B256(x), Literal::Boolean(b) => Pattern::Boolean(b), - Literal::Numeric(x) => Pattern::Numeric(Range::from_single(x)), + Literal::Numeric(x) => { + // Patterns limited to u64 + let x = u64::try_from(&x).unwrap(); + Pattern::Numeric(Range::from_single(x)) + } Literal::String(s) => Pattern::String(s.as_str().to_string()), } } diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs index 2b5d7ff7262..a36be62752e 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression.rs @@ -396,6 +396,7 @@ impl ty::TyExpression { Literal::U16(_) => TypeInfo::UnsignedInteger(IntegerBits::Sixteen), Literal::U32(_) => TypeInfo::UnsignedInteger(IntegerBits::ThirtyTwo), Literal::U64(_) => TypeInfo::UnsignedInteger(IntegerBits::SixtyFour), + Literal::U256(_) => TypeInfo::UnsignedInteger(IntegerBits::V256), Literal::Boolean(_) => TypeInfo::Boolean, Literal::B256(_) => TypeInfo::B256, }; @@ -2014,11 +2015,10 @@ impl ty::TyExpression { }), new_type, ), + IntegerBits::V256 => (Ok(Literal::U256(num)), new_type), }, TypeInfo::Numeric => ( - num.to_string().parse().map(Literal::Numeric).map_err(|e| { - Literal::handle_parse_int_error(engines, e, TypeInfo::Numeric, span.clone()) - }), + Ok(Literal::Numeric(num)), type_engine.insert(engines, TypeInfo::Numeric), ), _ => unreachable!("Unexpected type for integer literals"), diff --git a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs index 28a629f706a..5aaf26c302b 100644 --- a/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs +++ b/sway-core/src/semantic_analysis/ast_node/expression/typed_expression/method_application.rs @@ -34,12 +34,13 @@ pub(crate) fn type_check_method_application( .by_ref() .with_help_text("") .with_type_annotation(type_engine.insert(engines, TypeInfo::Unknown)); - args_buf.push_back(check!( + let r = check!( ty::TyExpression::type_check(ctx, arg.clone()), ty::TyExpression::error(span.clone(), engines), warnings, errors - )); + ); + args_buf.push_back(r); } // resolve the method name to a typed function declaration and type_check diff --git a/sway-core/src/semantic_analysis/coins_analysis.rs b/sway-core/src/semantic_analysis/coins_analysis.rs index d5df49cb5a3..c35298441a2 100644 --- a/sway-core/src/semantic_analysis/coins_analysis.rs +++ b/sway-core/src/semantic_analysis/coins_analysis.rs @@ -9,10 +9,11 @@ pub fn possibly_nonzero_u64_expression( decl_engine: &DeclEngine, expr: &ty::TyExpression, ) -> bool { + use num_traits::Zero; use ty::TyExpressionVariant::*; match &expr.expression { Literal(crate::language::Literal::U64(value)) => *value != 0, - Literal(crate::language::Literal::Numeric(value)) => *value != 0, + Literal(crate::language::Literal::Numeric(value)) => !value.is_zero(), // not a u64 literal, hence we return true to be on the safe side Literal(_) => true, ConstantExpression { const_decl, .. } => match &const_decl.value { diff --git a/sway-core/src/semantic_analysis/node_dependencies.rs b/sway-core/src/semantic_analysis/node_dependencies.rs index a025a2a5f20..e5618e53fce 100644 --- a/sway-core/src/semantic_analysis/node_dependencies.rs +++ b/sway-core/src/semantic_analysis/node_dependencies.rs @@ -849,6 +849,7 @@ fn type_info_name(type_info: &TypeInfo) -> String { IntegerBits::Sixteen => "uint16", IntegerBits::ThirtyTwo => "uint32", IntegerBits::SixtyFour => "uint64", + IntegerBits::V256 => "uint256", }, TypeInfo::Boolean => "bool", TypeInfo::Custom { diff --git a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs index 00f96eab05e..694909c204f 100644 --- a/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs +++ b/sway-core/src/transform/to_parsed_lang/convert_parse_tree.rs @@ -4,7 +4,6 @@ use crate::{ type_system::*, BuildTarget, Engines, }; - use itertools::Itertools; use sway_ast::{ attribute::Annotated, @@ -1257,6 +1256,7 @@ pub(crate) fn type_name_to_type_info_opt(name: &Ident) -> Option { "u16" => Some(TypeInfo::UnsignedInteger(IntegerBits::Sixteen)), "u32" => Some(TypeInfo::UnsignedInteger(IntegerBits::ThirtyTwo)), "u64" => Some(TypeInfo::UnsignedInteger(IntegerBits::SixtyFour)), + "u256" => Some(TypeInfo::UnsignedInteger(IntegerBits::V256)), "bool" => Some(TypeInfo::Boolean), "unit" => Some(TypeInfo::Tuple(Vec::new())), "b256" => Some(TypeInfo::B256), @@ -2804,12 +2804,13 @@ fn literal_to_literal( span, } = lit_int; match ty_opt { + // The literal int dont have any suffix None => { let orig_str = span.as_str(); if let Some(hex_digits) = orig_str.strip_prefix("0x") { let num_digits = hex_digits.chars().filter(|c| *c != '_').count(); match num_digits { - 1..=16 => Literal::Numeric(u64::try_from(parsed).unwrap()), + 1..=16 => Literal::Numeric(parsed), 64 => { let bytes = parsed.to_bytes_be(); let mut full_bytes = [0u8; 32]; @@ -2824,7 +2825,7 @@ fn literal_to_literal( } else if let Some(bin_digits) = orig_str.strip_prefix("0b") { let num_digits = bin_digits.chars().filter(|c| *c != '_').count(); match num_digits { - 1..=64 => Literal::Numeric(u64::try_from(parsed).unwrap()), + 1..=64 => Literal::Numeric(parsed), 256 => { let bytes = parsed.to_bytes_be(); let mut full_bytes = [0u8; 32]; @@ -2837,15 +2838,10 @@ fn literal_to_literal( } } } else { - match u64::try_from(&parsed) { - Ok(value) => Literal::Numeric(value), - Err(..) => { - let error = ConvertParseTreeError::IntLiteralOutOfRange { span }; - return Err(handler.emit_err(error.into())); - } - } + Literal::Numeric(parsed) } } + // Literal have a suffix Some((lit_int_type, _)) => match lit_int_type { LitIntType::U8 => { let value = match u8::try_from(parsed) { @@ -2887,6 +2883,7 @@ fn literal_to_literal( }; Literal::U64(value) } + LitIntType::U256 => Literal::U256(parsed), LitIntType::I8 | LitIntType::I16 | LitIntType::I32 | LitIntType::I64 => { let error = ConvertParseTreeError::SignedIntegersNotSupported { span }; return Err(handler.emit_err(error.into())); diff --git a/sway-core/src/type_system/info.rs b/sway-core/src/type_system/info.rs index 2d73371ff11..a4f1938a2fd 100644 --- a/sway-core/src/type_system/info.rs +++ b/sway-core/src/type_system/info.rs @@ -67,6 +67,7 @@ impl PartialEqWithEngines for VecSet { } /// Type information without an associated value, used for type inferencing and definition. +// see type_name_to_type_info_opt for known types strings to this enum #[derive(Debug, Clone, Default)] pub enum TypeInfo { #[default] @@ -433,6 +434,7 @@ impl DisplayWithEngines for TypeInfo { IntegerBits::Sixteen => "u16", IntegerBits::ThirtyTwo => "u32", IntegerBits::SixtyFour => "u64", + IntegerBits::V256 => "u256", } .into(), Boolean => "bool".into(), @@ -498,6 +500,7 @@ impl DebugWithEngines for TypeInfo { IntegerBits::Sixteen => "u16", IntegerBits::ThirtyTwo => "u32", IntegerBits::SixtyFour => "u64", + IntegerBits::V256 => "u256", } .into(), Boolean => "bool".into(), @@ -614,6 +617,7 @@ impl TypeInfo { Sixteen => "u16", ThirtyTwo => "u32", SixtyFour => "u64", + V256 => todo!(), } .into() } diff --git a/sway-ir/Cargo.toml b/sway-ir/Cargo.toml index 934ddbcac46..04fce507bcd 100644 --- a/sway-ir/Cargo.toml +++ b/sway-ir/Cargo.toml @@ -13,7 +13,10 @@ anyhow = "1.0" downcast-rs = "1.2.0" filecheck = "0.5" generational-arena = "0.2" +num-bigint = "0.4.3" +num-traits = "0.2.16" peg = "0.7" +prettydiff = "0.6.4" rustc-hash = "1.1.0" sway-ir-macros = { version = "0.42.1", path = "sway-ir-macros" } sway-types = { version = "0.42.1", path = "../sway-types" } diff --git a/sway-ir/src/constant.rs b/sway-ir/src/constant.rs index 7bb29383366..de9f4ab62fd 100644 --- a/sway-ir/src/constant.rs +++ b/sway-ir/src/constant.rs @@ -1,5 +1,7 @@ //! [`Constant`] is a typed constant value. +use num_bigint::BigUint; + use crate::{context::Context, irtype::Type, pretty::DebugWithContext, value::Value}; /// A [`Type`] and constant value, including [`ConstantValue::Undef`] for uninitialized constants. @@ -15,7 +17,7 @@ pub enum ConstantValue { Undef, Unit, Bool(bool), - Uint(u64), + Uint(BigUint), B256([u8; 32]), String(Vec), Array(Vec), @@ -37,7 +39,7 @@ impl Constant { } } - pub fn new_uint(context: &mut Context, nbits: u8, n: u64) -> Self { + pub fn new_uint(context: &mut Context, nbits: u16, n: BigUint) -> Self { Constant { ty: Type::new_uint(context, nbits), value: ConstantValue::Uint(n), @@ -89,7 +91,7 @@ impl Constant { Value::new_constant(context, new_const) } - pub fn get_uint(context: &mut Context, nbits: u8, value: u64) -> Value { + pub fn get_uint(context: &mut Context, nbits: u16, value: BigUint) -> Value { let new_const = Constant::new_uint(context, nbits, value); Value::new_constant(context, new_const) } diff --git a/sway-ir/src/instruction.rs b/sway-ir/src/instruction.rs index f53001048ca..0e2956606ca 100644 --- a/sway-ir/src/instruction.rs +++ b/sway-ir/src/instruction.rs @@ -788,14 +788,14 @@ impl<'a, 'eng> InstructionInserter<'a, 'eng> { } pub fn get_elem_ptr_with_idx(self, base: Value, elem_ty: Type, index: u64) -> Value { - let idx_val = Constant::get_uint(self.context, 64, index); + let idx_val = Constant::get_uint(self.context, 64, index.into()); self.get_elem_ptr(base, elem_ty, vec![idx_val]) } pub fn get_elem_ptr_with_idcs(self, base: Value, elem_ty: Type, indices: &[u64]) -> Value { let idx_vals = indices .iter() - .map(|idx| Constant::get_uint(self.context, 64, *idx)) + .map(|idx| Constant::get_uint(self.context, 64, (*idx).into())) .collect(); self.get_elem_ptr(base, elem_ty, idx_vals) } diff --git a/sway-ir/src/irtype.rs b/sway-ir/src/irtype.rs index 3289003fd3a..633515e12dc 100644 --- a/sway-ir/src/irtype.rs +++ b/sway-ir/src/irtype.rs @@ -18,7 +18,7 @@ pub struct Type(pub generational_arena::Index); pub enum TypeContent { Unit, Bool, - Uint(u8), // XXX u256 is not unreasonable and can't fit in a `u8`. + Uint(u16), B256, String(u64), Array(Type, u64), @@ -51,6 +51,7 @@ impl Type { Self::get_or_create_unique_type(context, TypeContent::Bool); Self::get_or_create_unique_type(context, TypeContent::Uint(8)); Self::get_or_create_unique_type(context, TypeContent::Uint(64)); + Self::get_or_create_unique_type(context, TypeContent::Uint(256)); Self::get_or_create_unique_type(context, TypeContent::B256); Self::get_or_create_unique_type(context, TypeContent::Slice); } @@ -71,7 +72,7 @@ impl Type { } /// New unsigned integer type - pub fn new_uint(context: &mut Context, width: u8) -> Type { + pub fn new_uint(context: &mut Context, width: u16) -> Type { Self::get_or_create_unique_type(context, TypeContent::Uint(width)) } @@ -86,8 +87,8 @@ impl Type { } /// Get unsigned integer type - pub fn get_uint(context: &Context, width: u8) -> Option { - Self::get_type(context, &TypeContent::Uint(width)) + pub fn get_uint(context: &Context, width: u16) -> Type { + Self::get_type(context, &TypeContent::Uint(width)).expect("create_basic_types not called") } /// Get B256 type @@ -213,7 +214,7 @@ impl Type { } /// Is unsigned integer type of specific width - pub fn is_uint_of(&self, context: &Context, width: u8) -> bool { + pub fn is_uint_of(&self, context: &Context, width: u16) -> bool { matches!(*self.get_content(context), TypeContent::Uint(width_) if width == width_) } @@ -267,7 +268,7 @@ impl Type { } /// Get width of an integer type. - pub fn get_uint_width(&self, context: &Context) -> Option { + pub fn get_uint_width(&self, context: &Context) -> Option { if let TypeContent::Uint(width) = self.get_content(context) { Some(*width) } else { @@ -302,17 +303,20 @@ impl Type { else { return None; }; + + let idx = u64::try_from(idx).ok()?; + ty.and_then(|(ty, accum_offset)| { if ty.is_struct(context) { // Sum up all sizes of all previous fields. - let prev_idxs_offset = (0..(*idx)).try_fold(0, |accum, pre_idx| { + let prev_idxs_offset = (0..idx).try_fold(0, |accum, pre_idx| { ty.get_field_type(context, pre_idx) .map(|field_ty| field_ty.size_in_bytes(context) + accum) })?; - ty.get_field_type(context, *idx) + ty.get_field_type(context, idx) .map(|field_ty| (field_ty, accum_offset + prev_idxs_offset)) } else if ty.is_union(context) { - ty.get_field_type(context, *idx) + ty.get_field_type(context, idx) .map(|field_ty| (field_ty, accum_offset)) } else { assert!( @@ -382,10 +386,9 @@ impl Type { pub fn size_in_bytes(&self, context: &Context) -> u64 { match self.get_content(context) { - TypeContent::Unit - | TypeContent::Bool - | TypeContent::Uint(_) - | TypeContent::Pointer(_) => 8, + TypeContent::Uint(256) => 32, + TypeContent::Uint(width) => (*width as u64).max(64) / 8, + TypeContent::Unit | TypeContent::Bool | TypeContent::Pointer(_) => 8, TypeContent::Slice => 16, TypeContent::B256 => 32, TypeContent::String(n) => super::size_bytes_round_up_to_word_alignment!(*n), diff --git a/sway-ir/src/optimize.rs b/sway-ir/src/optimize.rs index ba4c7c8c728..74ff7baa4dd 100644 --- a/sway-ir/src/optimize.rs +++ b/sway-ir/src/optimize.rs @@ -67,17 +67,19 @@ pub mod tests { expected: Option>, ) { let source_engine = SourceEngine::default(); - let mut context = crate::parse( - &format!( - "script {{ - {body} - }} - - !0 = \"a.sw\"" - ), - &source_engine, - ) - .unwrap(); + let input = format!( + "script {{ + {body} + }} + + !0 = \"a.sw\"" + ); + let mut context = match crate::parse(&input, &source_engine) { + Ok(context) => context, + Err(err) => { + panic!("Err: {err:?}\nInput: {input}"); + } + }; let mut pass_manager = PassManager::default(); crate::register_known_passes(&mut pass_manager); @@ -94,8 +96,11 @@ pub mod tests { return; }; - let actual = context - .to_string() + let actual = context.to_string(); + + // println!("{}", prettydiff::diff_lines(&input, &actual)); + + let actual = actual .lines() .filter_map(|x| { if x.contains(", !0") { diff --git a/sway-ir/src/optimize/constants.rs b/sway-ir/src/optimize/constants.rs index e371530df74..79040be1064 100644 --- a/sway-ir/src/optimize/constants.rs +++ b/sway-ir/src/optimize/constants.rs @@ -13,6 +13,8 @@ use crate::{ value::ValueDatum, AnalysisResults, BranchToWithArgs, Pass, PassMutability, Predicate, ScopedPass, }; +use num_traits::ops::checked::{CheckedAdd, CheckedDiv, CheckedMul, CheckedSub}; +use std::ops::{Shl, Shr}; pub const CONSTCOMBINE_NAME: &str = "constcombine"; @@ -168,19 +170,19 @@ fn combine_binary_op(context: &mut Context, function: &Function) -> bool { let val2 = arg2.get_constant(context).unwrap(); let v = match op { crate::BinaryOpKind::Add => match (&val1.value, &val2.value) { - (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_add(*r), + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_add(r), _ => None, }, crate::BinaryOpKind::Sub => match (&val1.value, &val2.value) { - (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_sub(*r), + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_sub(r), _ => None, }, crate::BinaryOpKind::Mul => match (&val1.value, &val2.value) { - (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_mul(*r), + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_mul(r), _ => None, }, crate::BinaryOpKind::Div => match (&val1.value, &val2.value) { - (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_div(*r), + (ConstantValue::Uint(l), ConstantValue::Uint(r)) => l.checked_div(r), _ => None, }, crate::BinaryOpKind::And => match (&val1.value, &val2.value) { @@ -201,27 +203,52 @@ fn combine_binary_op(context: &mut Context, function: &Function) -> bool { }, crate::BinaryOpKind::Rsh => match (&val1.value, &val2.value) { (ConstantValue::Uint(l), ConstantValue::Uint(r)) => { - u32::try_from(*r).ok().and_then(|r| l.checked_shr(r)) + u32::try_from(r).ok().and_then(|r| { + // Copy rust checked_shr behaviour + let width = val1.ty.get_uint_width(context)? as u32; + if r >= width { + None + } else { + Some(l.shr(r)) + } + }) } _ => None, }, crate::BinaryOpKind::Lsh => match (&val1.value, &val2.value) { (ConstantValue::Uint(l), ConstantValue::Uint(r)) => { - u32::try_from(*r).ok().and_then(|r| l.checked_shl(r)) + u32::try_from(r).ok().and_then(|r| { + // Copy rust checked_shr behaviour + let width = val1.ty.get_uint_width(context)? as u32; + if r >= width { + None + } else { + let mut new_value = l.shl(r); + for i in width..(new_value.bits() as u32) { + new_value.set_bit(i as u64, false); + } + Some(new_value) + } + }) } _ => None, }, }; - v.map(|v| { - ( - inst_val, - block, - Constant { - ty: val1.ty, - value: ConstantValue::Uint(v), - }, - ) + v.and_then(|v| { + let width = val1.ty.get_uint_width(&context).unwrap() as u64; + if v.bits() > width { + None + } else { + Some(( + inst_val, + block, + Constant { + ty: val1.ty, + value: ConstantValue::Uint(v), + }, + )) + } }) } _ => None, @@ -254,14 +281,17 @@ fn combine_unary_op(context: &mut Context, function: &Function) -> bool { 16 => u16::MAX as u64, 32 => u32::MAX as u64, 64 => u64::MAX, + 256 => todo!(), _ => return None, }; + let v = u64::try_from(v).ok()?; + let new_value = (!v) & max; Some(( inst_val, block, Constant { ty: val.ty, - value: ConstantValue::Uint((!v) & max), + value: ConstantValue::Uint(new_value.into()), }, )) }) @@ -286,20 +316,20 @@ fn combine_unary_op(context: &mut Context, function: &Function) -> bool { mod tests { use crate::optimize::tests::*; - fn assert_operator(opcode: &str, l: &str, r: Option<&str>, result: Option<&str>) { - let expected = result.map(|result| format!("v0 = const u64 {result}")); + fn assert_operator(opcode: &str, t: &str, l: &str, r: Option<&str>, result: Option<&str>) { + let expected = result.map(|result| format!("v0 = const {t} {result}")); let expected = expected.as_ref().map(|x| vec![x.as_str()]); let body = format!( " - entry fn main() -> u64 {{ + entry fn main() -> {t} {{ entry(): - l = const u64 {l} + l = const {t} {l} {r_inst} result = {opcode} l, {result_inst} !0 - ret u64 result + ret {t} result }} ", - r_inst = r.map_or("".into(), |r| format!("r = const u64 {r}")), + r_inst = r.map_or("".into(), |r| format!("r = const {t} {r}")), result_inst = r.map_or("", |_| " r,") ); assert_optimization(&["constcombine"], &body, expected); @@ -307,27 +337,29 @@ mod tests { #[test] fn unary_op_are_optimized() { - assert_operator("not", &u64::MAX.to_string(), None, Some("0")); + assert_operator("not", "u64", &u64::MAX.to_string(), None, Some("0")); } #[test] fn binary_op_are_optimized() { - assert_operator("add", "1", Some("1"), Some("2")); - assert_operator("sub", "1", Some("1"), Some("0")); - assert_operator("mul", "2", Some("2"), Some("4")); - assert_operator("div", "10", Some("5"), Some("2")); - assert_operator("mod", "12", Some("5"), Some("2")); - assert_operator("rsh", "16", Some("1"), Some("8")); - assert_operator("lsh", "16", Some("1"), Some("32")); + assert_operator("add", "u64", "1", Some("1"), Some("2")); + assert_operator("sub", "u64", "1", Some("1"), Some("0")); + assert_operator("mul", "u64", "2", Some("2"), Some("4")); + assert_operator("div", "u64", "10", Some("5"), Some("2")); + assert_operator("mod", "u64", "12", Some("5"), Some("2")); + assert_operator("rsh", "u64", "16", Some("1"), Some("8")); + assert_operator("lsh", "u64", "16", Some("1"), Some("32")); assert_operator( "and", + "u64", &0x00FFF.to_string(), Some(&0xFFF00.to_string()), Some(&0xF00.to_string()), ); assert_operator( "or", + "u64", &0x00FFF.to_string(), Some(&0xFFF00.to_string()), Some(&0xFFFFF.to_string()), @@ -335,21 +367,31 @@ mod tests { assert_operator( "xor", + "u64", &0x00FFF.to_string(), Some(&0xFFF00.to_string()), Some(&0xFF0FF.to_string()), ); + + // u256 + assert_operator("add", "u256", "1", Some("1"), Some("2")); + assert_operator("sub", "u256", "1", Some("1"), Some("0")); + assert_operator("mul", "u256", "2", Some("2"), Some("4")); + assert_operator("div", "u256", "10", Some("5"), Some("2")); + // assert_operator("mod", "u64", "12", Some("5"), Some("2")); + assert_operator("rsh", "u256", "16", Some("1"), Some("8")); + assert_operator("lsh", "u256", "16", Some("1"), Some("32")); } #[test] fn binary_op_are_not_optimized() { - assert_operator("add", &u64::MAX.to_string(), Some("1"), None); - assert_operator("sub", "0", Some("1"), None); - assert_operator("mul", &u64::MAX.to_string(), Some("2"), None); - assert_operator("div", "1", Some("0"), None); + assert_operator("add", "u64", &u64::MAX.to_string(), Some("1"), None); + assert_operator("sub", "u64", "0", Some("1"), None); + assert_operator("mul", "u64", &u64::MAX.to_string(), Some("2"), None); + assert_operator("div", "u64", "1", Some("0"), None); - assert_operator("rsh", "1", Some("64"), None); - assert_operator("lsh", "1", Some("64"), None); + assert_operator("rsh", "u64", "1", Some("64"), None); + assert_operator("lsh", "u64", "1", Some("64"), None); } #[test] diff --git a/sway-ir/src/parser.rs b/sway-ir/src/parser.rs index 17071441d47..aa8bb368762 100644 --- a/sway-ir/src/parser.rs +++ b/sway-ir/src/parser.rs @@ -24,6 +24,7 @@ pub fn parse<'eng>( // ------------------------------------------------------------------------------------------------- mod ir_builder { + use num_bigint::BigUint; use sway_types::{ident::Ident, span::Span, SourceEngine}; type MdIdxRef = u64; @@ -437,7 +438,7 @@ mod ir_builder { / "0x" s:$(hex_digit()*<64>) _ { IrAstConstValue::B256(string_to_hex::<32>(s)) } - / n:decimal() { IrAstConstValue::Number(n) } + / n:big_decimal() { IrAstConstValue::Number(n) } / string_const() / array_const() / struct_const() @@ -494,6 +495,7 @@ mod ir_builder { = ("unit" / "()") _ { IrAstTy::Unit } / "bool" _ { IrAstTy::Bool } / "u64" _ { IrAstTy::U64 } + / "u256" _ { IrAstTy::U256 } / "b256" _ { IrAstTy::B256 } / "string" _ "<" _ sz:decimal() ">" _ { IrAstTy::String(sz) } / array_ty() @@ -575,6 +577,11 @@ mod ir_builder { rule id_char() = quiet!{ id_char0() / ['0'..='9'] } + rule big_decimal() -> BigUint + = ds:$("0" / ['1'..='9'] ['0'..='9']*) { + ds.parse::().unwrap() + } + rule decimal() -> u64 = d:dec_digits() _ { d @@ -724,7 +731,7 @@ mod ir_builder { Unit, Bool(bool), B256([u8; 32]), - Number(u64), + Number(BigUint), String(Vec), Array(IrAstTy, Vec), Struct(Vec<(IrAstTy, IrAstConst)>), @@ -751,7 +758,7 @@ mod ir_builder { IrAstConstValue::Unit => ConstantValue::Unit, IrAstConstValue::Bool(b) => ConstantValue::Bool(*b), IrAstConstValue::B256(bs) => ConstantValue::B256(*bs), - IrAstConstValue::Number(n) => ConstantValue::Uint(*n), + IrAstConstValue::Number(n) => ConstantValue::Uint(n.clone()), IrAstConstValue::String(bs) => ConstantValue::String(bs.clone()), IrAstConstValue::Array(el_ty, els) => { let els: Vec<_> = els @@ -783,7 +790,11 @@ mod ir_builder { IrAstConstValue::Unit => Constant::get_unit(context), IrAstConstValue::Bool(b) => Constant::get_bool(context, *b), IrAstConstValue::B256(bs) => Constant::get_b256(context, *bs), - IrAstConstValue::Number(n) => Constant::get_uint(context, 64, *n), + IrAstConstValue::Number(n) => match val_ty { + IrAstTy::U64 => Constant::get_uint(context, 64, n.clone()), + IrAstTy::U256 => Constant::get_uint(context, 256, n.clone()), + _ => todo!(), + }, IrAstConstValue::String(s) => Constant::get_string(context, s.clone()), IrAstConstValue::Array(..) => { let array_const = self.as_constant(context, val_ty); @@ -802,6 +813,7 @@ mod ir_builder { Unit, Bool, U64, + U256, B256, String(u64), Array(Box, u64), @@ -816,6 +828,7 @@ mod ir_builder { IrAstTy::Unit => Type::get_unit(context), IrAstTy::Bool => Type::get_bool(context), IrAstTy::U64 => Type::get_uint64(context), + IrAstTy::U256 => Type::get_uint(context, 256), IrAstTy::B256 => Type::get_b256(context), IrAstTy::String(n) => Type::new_string(context, *n), IrAstTy::Array(el_ty, count) => { diff --git a/sway-ir/src/pass_manager.rs b/sway-ir/src/pass_manager.rs index ea226186ede..616c16a1d79 100644 --- a/sway-ir/src/pass_manager.rs +++ b/sway-ir/src/pass_manager.rs @@ -235,7 +235,11 @@ impl PassManager { pub fn run(&mut self, ir: &mut Context, passes: &PassGroup) -> Result { let mut modified = false; for pass in passes.flatten_pass_group() { + // println!("######################################################### Pass: {pass}"); + // let before = ir.to_string(); modified |= self.actually_run(ir, pass)?; + // let after = ir.to_string(); + // println!("{}", prettydiff::diff_lines(&before, &after)); } Ok(modified) } diff --git a/sway-ir/src/verify.rs b/sway-ir/src/verify.rs index 94da9de6d9a..712b52ae4d8 100644 --- a/sway-ir/src/verify.rs +++ b/sway-ir/src/verify.rs @@ -308,7 +308,7 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { fn verify_binary_op( &self, - _op: &BinaryOpKind, + op: &BinaryOpKind, arg1: &Value, arg2: &Value, ) -> Result<(), IrError> { @@ -318,8 +318,30 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { let arg2_ty = arg2 .get_type(self.context) .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?; - if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) { - return Err(IrError::VerifyBinaryOpIncorrectArgType); + + match op { + // left and right must have the same type and must be uint. + BinaryOpKind::Add + | BinaryOpKind::Sub + | BinaryOpKind::Mul + | BinaryOpKind::Div + | BinaryOpKind::And + | BinaryOpKind::Or + | BinaryOpKind::Xor + | BinaryOpKind::Mod => { + if !arg1_ty.eq(self.context, &arg2_ty) || !arg1_ty.is_uint(self.context) { + return Err(IrError::VerifyBinaryOpIncorrectArgType); + } + } + // left and right must be uint, but + // they dont need to be the same type + BinaryOpKind::Rsh | BinaryOpKind::Lsh => { + let is_arg1_nok = !arg1_ty.is_uint(self.context); + let is_arg2_nok = !arg2_ty.is_uint(self.context); + if is_arg1_nok || is_arg2_nok { + return Err(IrError::VerifyBinaryOpIncorrectArgType); + } + } } Ok(()) @@ -370,6 +392,8 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { let caller_arg_type = opt_caller_arg_type.as_ref().unwrap(); if !caller_arg_type.eq(self.context, callee_arg_type) { + dbg!(caller_arg_type.as_string(self.context)); + dbg!(callee_arg_type.as_string(self.context)); return Err(IrError::VerifyCallArgTypeMismatch( callee_content.name.clone(), caller_arg_type.as_string(self.context), @@ -551,14 +575,13 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { ty.and_then(|ty| { idx_val .get_constant(self.context) - .and_then(|const_ref| { - if let ConstantValue::Uint(n) = const_ref.value { - Some(n) - } else { - None + .and_then(|const_ref| match &const_ref.value { + ConstantValue::Uint(idx) => { + let idx = u64::try_from(idx).ok()?; + ty.get_field_type(self.context, idx) } + _ => None, }) - .and_then(|idx| ty.get_field_type(self.context, idx)) .or_else(|| ty.get_array_elem_type(self.context)) }) }); diff --git a/sway-lib-core/src/ops.sw b/sway-lib-core/src/ops.sw index 6cd744a1f21..226c315d503 100644 --- a/sway-lib-core/src/ops.sw +++ b/sway-lib-core/src/ops.sw @@ -6,6 +6,12 @@ pub trait Add { fn add(self, other: Self) -> Self; } +impl Add for u256 { + fn add(self, other: Self) -> Self { + __add(self, other) + } +} + impl Add for u64 { fn add(self, other: Self) -> Self { __add(self, other) @@ -54,6 +60,12 @@ pub trait Subtract { fn subtract(self, other: Self) -> Self; } +impl Subtract for u256 { + fn subtract(self, other: Self) -> Self { + __sub(self, other) + } +} + impl Subtract for u64 { fn subtract(self, other: Self) -> Self { __sub(self, other) @@ -84,6 +96,12 @@ pub trait Multiply { fn multiply(self, other: Self) -> Self; } +impl Multiply for u256 { + fn multiply(self, other: Self) -> Self { + __mul(self, other) + } +} + impl Multiply for u64 { fn multiply(self, other: Self) -> Self { __mul(self, other) @@ -132,6 +150,12 @@ pub trait Divide { fn divide(self, other: Self) -> Self; } +impl Divide for u256 { + fn divide(self, other: Self) -> Self { + __div(self, other) + } +} + impl Divide for u64 { fn divide(self, other: Self) -> Self { __div(self, other) @@ -164,6 +188,12 @@ pub trait Mod { fn modulo(self, other: Self) -> Self; } +impl Mod for u256 { + fn modulo(self, other: Self) -> Self { + __mod(self, other) + } +} + impl Mod for u64 { fn modulo(self, other: Self) -> Self { __mod(self, other) @@ -198,6 +228,34 @@ impl Not for bool { } } + +impl Not for u64 { + fn not(self) -> Self { + __not(self) + } +} + +impl Not for u32 { + fn not(self) -> Self { + let v = __not(self); + __and(v, u32::max()) + } +} + +impl Not for u16 { + fn not(self) -> Self { + let v = __not(self); + __and(v, u16::max()) + } +} + +impl Not for u8 { + fn not(self) -> Self { + let v = __not(self); + __and(v, u8::max()) + } +} + pub trait Eq { fn eq(self, other: Self) -> bool; } { @@ -212,6 +270,12 @@ impl Eq for bool { } } +impl Eq for u256 { + fn eq(self, other: Self) -> bool { + __eq(self, other) + } +} + impl Eq for u64 { fn eq(self, other: Self) -> bool { __eq(self, other) @@ -258,6 +322,15 @@ pub trait Ord { fn lt(self, other: Self) -> bool; } +impl Ord for u256 { + fn gt(self, other: Self) -> bool { + __gt(self, other) + } + fn lt(self, other: Self) -> bool { + __lt(self, other) + } +} + impl Ord for u64 { fn gt(self, other: Self) -> bool { __gt(self, other) @@ -334,6 +407,12 @@ pub trait BitwiseAnd { fn binary_and(self, other: Self) -> Self; } +impl BitwiseAnd for u256 { + fn binary_and(self, other: Self) -> Self { + __and(self, other) + } +} + impl BitwiseAnd for u64 { fn binary_and(self, other: Self) -> Self { __and(self, other) @@ -358,10 +437,29 @@ impl BitwiseAnd for u8 { } } +impl BitwiseAnd for b256 { + fn binary_and(val: self, other: Self) -> Self { + let (value_word_1, value_word_2, value_word_3, value_word_4) = decompose(val); + let (other_word_1, other_word_2, other_word_3, other_word_4) = decompose(other); + let word_1 = value_word_1.binary_and(other_word_1); + let word_2 = value_word_2.binary_and(other_word_2); + let word_3 = value_word_3.binary_and(other_word_3); + let word_4 = value_word_4.binary_and(other_word_4); + let rebuilt = compose((word_1, word_2, word_3, word_4)); + rebuilt + } +} + pub trait BitwiseOr { fn binary_or(self, other: Self) -> Self; } +impl BitwiseOr for u256 { + fn binary_or(self, other: Self) -> Self { + __or(self, other) + } +} + impl BitwiseOr for u64 { fn binary_or(self, other: Self) -> Self { __or(self, other) @@ -386,10 +484,29 @@ impl BitwiseOr for u8 { } } +impl BitwiseOr for b256 { + fn binary_or(val: self, other: Self) -> Self { + let (value_word_1, value_word_2, value_word_3, value_word_4) = decompose(val); + let (other_word_1, other_word_2, other_word_3, other_word_4) = decompose(other); + let word_1 = value_word_1.binary_or(other_word_1); + let word_2 = value_word_2.binary_or(other_word_2); + let word_3 = value_word_3.binary_or(other_word_3); + let word_4 = value_word_4.binary_or(other_word_4); + let rebuilt = compose((word_1, word_2, word_3, word_4)); + rebuilt + } +} + pub trait BitwiseXor { fn binary_xor(self, other: Self) -> Self; } +impl BitwiseXor for u256 { + fn binary_xor(self, other: Self) -> Self { + __xor(self, other) + } +} + impl BitwiseXor for u64 { fn binary_xor(self, other: Self) -> Self { __xor(self, other) @@ -414,59 +531,6 @@ impl BitwiseXor for u8 { } } -impl Not for u64 { - fn not(self) -> Self { - __not(self) - } -} - -impl Not for u32 { - fn not(self) -> Self { - let v = __not(self); - __and(v, u32::max()) - } -} - -impl Not for u16 { - fn not(self) -> Self { - let v = __not(self); - __and(v, u16::max()) - } -} - -impl Not for u8 { - fn not(self) -> Self { - let v = __not(self); - __and(v, u8::max()) - } -} - -impl BitwiseAnd for b256 { - fn binary_and(val: self, other: Self) -> Self { - let (value_word_1, value_word_2, value_word_3, value_word_4) = decompose(val); - let (other_word_1, other_word_2, other_word_3, other_word_4) = decompose(other); - let word_1 = value_word_1.binary_and(other_word_1); - let word_2 = value_word_2.binary_and(other_word_2); - let word_3 = value_word_3.binary_and(other_word_3); - let word_4 = value_word_4.binary_and(other_word_4); - let rebuilt = compose((word_1, word_2, word_3, word_4)); - rebuilt - } -} - -impl BitwiseOr for b256 { - fn binary_or(val: self, other: Self) -> Self { - let (value_word_1, value_word_2, value_word_3, value_word_4) = decompose(val); - let (other_word_1, other_word_2, other_word_3, other_word_4) = decompose(other); - let word_1 = value_word_1.binary_or(other_word_1); - let word_2 = value_word_2.binary_or(other_word_2); - let word_3 = value_word_3.binary_or(other_word_3); - let word_4 = value_word_4.binary_or(other_word_4); - let rebuilt = compose((word_1, word_2, word_3, word_4)); - rebuilt - } -} - impl BitwiseXor for b256 { fn binary_xor(val: self, other: Self) -> Self { let (value_word_1, value_word_2, value_word_3, value_word_4) = decompose(val); @@ -490,6 +554,7 @@ trait OrdEq: Ord + Eq { } } +impl OrdEq for u256 {} impl OrdEq for u64 {} impl OrdEq for u32 {} impl OrdEq for u16 {} @@ -501,6 +566,15 @@ pub trait Shift { fn rsh(self, other: u64) -> Self; } +impl Shift for u256 { + fn lsh(self, other: u64) -> Self { + __lsh(self, other) + } + fn rsh(self, other: u64) -> Self { + __rsh(self, other) + } +} + impl Shift for u64 { fn lsh(self, other: u64) -> Self { __lsh(self, other) diff --git a/sway-lib-core/src/primitive_conversions.sw b/sway-lib-core/src/primitive_conversions.sw index 3b302aff3d8..f4d56d05ff4 100644 --- a/sway-lib-core/src/primitive_conversions.sw +++ b/sway-lib-core/src/primitive_conversions.sw @@ -1,6 +1,12 @@ library; impl u64 { + pub fn as_u256(self) -> u256 { + asm(input: self) { + input: u256 + } + } + pub fn to_le_bytes(self) -> [u8; 8] { let output = [0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8, 0_u8]; diff --git a/sway-lsp/src/traverse/parsed_tree.rs b/sway-lsp/src/traverse/parsed_tree.rs index 69eae13661d..8f463e16eca 100644 --- a/sway-lsp/src/traverse/parsed_tree.rs +++ b/sway-lsp/src/traverse/parsed_tree.rs @@ -1087,6 +1087,7 @@ fn literal_to_symbol_kind(value: &Literal) -> SymbolKind { | Literal::U16(..) | Literal::U32(..) | Literal::U64(..) + | Literal::U256(..) | Literal::Numeric(..) => SymbolKind::NumericLiteral, Literal::String(..) => SymbolKind::StringLiteral, Literal::B256(..) => SymbolKind::ByteLiteral, diff --git a/sway-lsp/src/utils/debug.rs b/sway-lsp/src/utils/debug.rs index 7baf327709e..d4579947037 100644 --- a/sway-lsp/src/utils/debug.rs +++ b/sway-lsp/src/utils/debug.rs @@ -51,6 +51,7 @@ fn literal_to_string(literal: &Literal) -> String { Literal::U16(_) => "u16".into(), Literal::U32(_) => "u32".into(), Literal::U64(_) => "u64".into(), + Literal::U256(_) => "u256".into(), Literal::Numeric(_) => "u64".into(), Literal::String(len) => format!("str[{}]", len.as_str().len()), Literal::Boolean(_) => "bool".into(), diff --git a/sway-parse/src/token.rs b/sway-parse/src/token.rs index 846539ba2a5..4f09d02ed0e 100644 --- a/sway-parse/src/token.rs +++ b/sway-parse/src/token.rs @@ -703,6 +703,7 @@ fn lex_int_ty_opt(l: &mut Lexer<'_>) -> Result> { None => break l.src.len(), } }; + // Parse the suffix to a known one, or if unknown, recover by throwing it away. let ty = match parse_int_suffix(&suffix) { Some(s) => s, @@ -726,6 +727,8 @@ pub fn parse_int_suffix(suffix: &str) -> Option { "u16" => LitIntType::U16, "u32" => LitIntType::U32, "u64" => LitIntType::U64, + "u128" => todo!(), + "u256" => LitIntType::U256, "i8" => LitIntType::I8, "i16" => LitIntType::I16, "i32" => LitIntType::I32, diff --git a/sway-types/src/integer_bits.rs b/sway-types/src/integer_bits.rs index 0b2f30dc7b9..0f405136fb2 100644 --- a/sway-types/src/integer_bits.rs +++ b/sway-types/src/integer_bits.rs @@ -6,6 +6,7 @@ pub enum IntegerBits { Sixteen, ThirtyTwo, SixtyFour, + V256, } impl fmt::Display for IntegerBits { @@ -16,6 +17,7 @@ impl fmt::Display for IntegerBits { Sixteen => "sixteen", ThirtyTwo => "thirty two", SixtyFour => "sixty four", + V256 => "256", }; write!(f, "{s}") } diff --git a/test/Cargo.toml b/test/Cargo.toml index 1feb637d9ea..7fee3d922fa 100644 --- a/test/Cargo.toml +++ b/test/Cargo.toml @@ -17,11 +17,12 @@ forc-client = { path = "../forc-plugins/forc-client" } forc-pkg = { path = "../forc-pkg" } forc-test = { path = "../forc-test" } forc-tracing = { path = "../forc-tracing" } -fuel-vm = { workspace = true, features = ["random"] } +fuel-vm = { workspace = true, features = ["random", "debug"] } futures = "0.3.24" gag = "1.0" hex = "0.4.3" miden = "0.3.0" +num-bigint = "0.4.3" prettydiff = "0.6" rand = "0.8" regex = "1.7" diff --git a/test/src/e2e_vm_tests/harness.rs b/test/src/e2e_vm_tests/harness.rs index 999e9528dda..33f83a11fe5 100644 --- a/test/src/e2e_vm_tests/harness.rs +++ b/test/src/e2e_vm_tests/harness.rs @@ -6,11 +6,12 @@ use forc_client::{ }; use forc_pkg::{Built, BuiltPackage}; use fuel_tx::TransactionBuilder; -use fuel_vm::checked_transaction::builder::TransactionBuilderExt; +use fuel_vm::checked_transaction::{builder::TransactionBuilderExt, Checked}; use fuel_vm::fuel_tx; use fuel_vm::interpreter::Interpreter; use fuel_vm::prelude::*; use futures::Future; +use num_bigint::BigUint; use rand::rngs::StdRng; use rand::{Rng, SeedableRng}; use regex::{Captures, Regex}; @@ -131,10 +132,111 @@ pub(crate) enum VMExecutionResult { MidenVM(miden::ExecutionTrace), } +fn current_instruction(memory: &[u8], registers: &[u64]) -> Instruction { + let [hi, _] = memory[registers[fuel_vm::fuel_asm::RegId::PC] as usize..] + .chunks_exact(fuel_vm::consts::WORD_SIZE) + .next() + .map(|b| b.try_into().expect("Has to be correct size slice")) + .map(Word::from_be_bytes) + .map(fuel_vm::fuel_asm::raw_instructions_from_word) + .ok_or(InterpreterError::Panic(PanicReason::MemoryOverflow)) + .unwrap(); + + Instruction::try_from(hi).unwrap() +} + +fn print_register_values(instruction: Instruction, registers: &[u64]) -> String { + let used: [Option; 4] = instruction.reg_ids(); + format!( + "{}{}{}{}", + used[0] + .map(|x| format!("{x:?}={} ", registers[x])) + .unwrap_or("".into()), + used[1] + .map(|x| format!("{x:?}={} ", registers[x])) + .unwrap_or("".into()), + used[2] + .map(|x| format!("{x:?}={} ", registers[x])) + .unwrap_or("".into()), + used[3] + .map(|x| format!("{x:?}={}", registers[x])) + .unwrap_or("".into()), + ) +} + +fn as_biguint(memory: &[u8], addr: usize, len_bytes: usize) -> BigUint { + BigUint::from_bytes_be(&memory[addr..(addr + len_bytes)]) +} + +fn print_memory(instruction: Instruction, memory: &[u8], registers: &[u64]) -> String { + match instruction { + Instruction::WQOP(_) => { + let regs = instruction.reg_ids(); + let a_addr = registers[regs[0].unwrap()] as usize; + let a = as_biguint(memory, a_addr, 32); + + let b_addr = registers[regs[1].unwrap()] as usize; + let b = as_biguint(memory, b_addr, 32); + + let c_addr = registers[regs[2].unwrap()] as usize; + let c = as_biguint(memory, c_addr, 32); + format!("u256@{a_addr} = {a}, u256@{b_addr} = {b}, u256@{c_addr} = {c}") + } + _ => { + format!("") + } + } +} + +fn debug_exec( + i: &mut Interpreter, + tx: Checked