From 974bc2f3e78cbdcb20bca3b9fee2efa1ef7b1e68 Mon Sep 17 00:00:00 2001 From: Daniel Frederico Lins Leite Date: Tue, 8 Aug 2023 12:42:49 +0100 Subject: [PATCH] u256 not operator (#4924) ## Description This PR is part of https://github.com/FuelLabs/sway/pull/4794. It implements the `not` operator for u256 and allows values bigger than `u64`. To support that it implements `U256` inside `sway-types`. For now, it is a bare minimum wrapper around `BigUint`. We may use fuel macro in the future, which implements all necessary functions. ## Checklist - [x] I have linked to any relevant issues. - [x] I have commented my code, particularly in hard-to-understand areas. - [x] I have updated the documentation where relevant (API docs, the reference, and the Sway book). - [x] I have added tests that prove my fix is effective or that my feature works. - [x] I have added (or requested a maintainer to add) the necessary `Breaking*` or `New Feature` labels where relevant. - [x] I have done my best to ensure that my PR adheres to [the Fuel Labs Code Review Standards](https://github.com/FuelLabs/rfcs/blob/master/text/code-standards/external-contributors.md). - [x] I have requested a review from the relevant team or maintainers. --------- Co-authored-by: Anton Trunov --- Cargo.lock | 2 + forc-plugins/forc-client/src/util/encode.rs | 8 +- .../src/asm_generation/fuel/data_section.rs | 8 +- .../asm_generation/fuel/fuel_asm_builder.rs | 33 ++++ .../miden_vm/miden_vm_asm_builder.rs | 6 +- sway-core/src/asm_lang/virtual_immediate.rs | 1 + sway-core/src/ir_generation/convert.rs | 4 +- sway-core/src/language/literal.rs | 4 +- .../match_expression/analysis/pattern.rs | 4 +- .../ast_node/expression/typed_expression.rs | 16 +- .../to_parsed_lang/convert_parse_tree.rs | 12 +- sway-ir/src/constant.rs | 18 ++- sway-ir/src/instruction.rs | 21 +++ sway-ir/src/optimize/dce.rs | 4 + sway-ir/src/optimize/inline.rs | 3 + sway-ir/src/optimize/misc_demotion.rs | 145 +++++++++++++++++- sway-ir/src/parser.rs | 52 ++++--- sway-ir/src/printer.rs | 18 ++- sway-ir/src/verify.rs | 23 +++ .../demote_misc/demote_wide_not_constants.ir | 18 +++ sway-ir/tests/serialize/wide_ops.ir | 3 + sway-lib-core/src/ops.sw | 6 + sway-types/Cargo.toml | 2 + sway-types/src/lib.rs | 1 + sway-types/src/u256.rs | 63 ++++++++ .../should_pass/language/u256/src/main.sw | 9 +- .../should_pass/language/u256/test.toml | 2 +- 27 files changed, 412 insertions(+), 74 deletions(-) create mode 100644 sway-ir/tests/demote_misc/demote_wide_not_constants.ir create mode 100644 sway-types/src/u256.rs diff --git a/Cargo.lock b/Cargo.lock index 7d4bd7064b0..1d58ad2406a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5699,7 +5699,9 @@ dependencies = [ "fuel-crypto", "fuel-tx", "lazy_static", + "num-bigint", "serde", + "thiserror", ] [[package]] diff --git a/forc-plugins/forc-client/src/util/encode.rs b/forc-plugins/forc-client/src/util/encode.rs index 81fd9c7fad2..7f3217b9b48 100644 --- a/forc-plugins/forc-client/src/util/encode.rs +++ b/forc-plugins/forc-client/src/util/encode.rs @@ -1,7 +1,9 @@ use std::str::FromStr; +use anyhow::Context; use fuel_abi_types::abi::full_program::FullTypeApplication; use serde::{Deserialize, Deserializer, Serialize}; +use sway_types::u256::U256; /// A wrapper around fuels_core::types::Token, which enables serde de/serialization. #[derive(Debug, PartialEq)] @@ -87,10 +89,10 @@ impl Token { let u64_val = value.parse::()?; Ok(Token(fuels_core::types::Token::U64(u64_val))) } - //TODO u256 limited to u64 value Type::U256 => { - let u64_val = value.parse::()?; - Ok(Token(fuels_core::types::Token::U256(u64_val.into()))) + let v = value.parse::().context("u256 literal out of range")?; + let bytes = v.to_be_bytes(); + Ok(Token(fuels_core::types::Token::U256(bytes.into()))) } Type::Bool => { let bool_val = value.parse::()?; diff --git a/sway-core/src/asm_generation/fuel/data_section.rs b/sway-core/src/asm_generation/fuel/data_section.rs index 8706aac2cbc..3ee28de5cb0 100644 --- a/sway-core/src/asm_generation/fuel/data_section.rs +++ b/sway-core/src/asm_generation/fuel/data_section.rs @@ -100,13 +100,7 @@ impl Entry { 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::U256(u) => { - // TODO u256 limited to u64 - let mut bytes = vec![0u8; 24]; - bytes.extend(&u.to_be_bytes()); - assert!(bytes.len() == 32); - Entry::new_byte_array(bytes.to_vec(), size, name) - } + ConstantValue::U256(u) => Entry::new_byte_array(u.to_be_bytes().to_vec(), size, name), 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 aabfa2dc8dc..10e3f8c7215 100644 --- a/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs +++ b/sway-core/src/asm_generation/fuel/fuel_asm_builder.rs @@ -284,6 +284,11 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { FuelVmInstruction::StateStoreWord { stored_val, key } => { self.compile_state_store_word(instr_val, stored_val, key) } + + // Wide operations + FuelVmInstruction::WideUnaryOp { op, result, arg } => { + self.compile_wide_unary_op(instr_val, op, arg, result) + } FuelVmInstruction::WideBinaryOp { op, result, @@ -530,6 +535,34 @@ impl<'ir, 'eng> FuelAsmBuilder<'ir, 'eng> { Ok(()) } + fn compile_wide_unary_op( + &mut self, + instr_val: &Value, + op: &UnaryOpKind, + arg: &Value, + result: &Value, + ) -> Result<(), CompileError> { + let result_reg = self.value_to_register(result)?; + let val1_reg = self.value_to_register(arg)?; + + let opcode = match op { + UnaryOpKind::Not => VirtualOp::WQOP( + result_reg, + val1_reg, + VirtualRegister::Constant(ConstantRegister::Zero), + VirtualImmediate06::wide_op(crate::asm_lang::WideOperations::Not, false), + ), + }; + + self.cur_bytecode.push(Op { + opcode: Either::Left(opcode), + comment: String::new(), + owning_span: self.md_mgr.val_to_span(self.context, *instr_val), + }); + + Ok(()) + } + fn compile_wide_binary_op( &mut self, instr_val: &Value, 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 08254a79d31..07a1c1f2749 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 @@ -648,11 +648,11 @@ 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) => vec![DirectOp::push(*x)], U256(x) => todo!(), B256(_) => todo!(), String(_) => todo!(), diff --git a/sway-core/src/asm_lang/virtual_immediate.rs b/sway-core/src/asm_lang/virtual_immediate.rs index 7aa19b27de0..393c8d29f19 100644 --- a/sway-core/src/asm_lang/virtual_immediate.rs +++ b/sway-core/src/asm_lang/virtual_immediate.rs @@ -10,6 +10,7 @@ use std::fmt; pub enum WideOperations { Add = 0, Sub = 1, + Not = 2, } #[repr(u8)] diff --git a/sway-core/src/ir_generation/convert.rs b/sway-core/src/ir_generation/convert.rs index 6dfe2955c7c..86d3b72788b 100644 --- a/sway-core/src/ir_generation/convert.rs +++ b/sway-core/src/ir_generation/convert.rs @@ -25,7 +25,7 @@ pub(super) fn convert_literal_to_value(context: &mut Context, ast_literal: &Lite 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::U256(n) => Constant::get_uint(context, 256, *n), + Literal::U256(n) => Constant::get_uint256(context, n.clone()), Literal::Numeric(n) => Constant::get_uint(context, 64, *n), Literal::String(s) => Constant::get_string(context, s.as_str().as_bytes().to_vec()), Literal::Boolean(b) => Constant::get_bool(context, *b), @@ -43,7 +43,7 @@ pub(super) fn convert_literal_to_constant( 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::U256(n) => Constant::new_uint(context, 256, *n), + Literal::U256(n) => Constant::new_uint256(context, n.clone()), Literal::Numeric(n) => Constant::new_uint(context, 64, *n), Literal::String(s) => Constant::new_string(context, s.as_str().as_bytes().to_vec()), Literal::Boolean(b) => Constant::new_bool(context, *b), diff --git a/sway-core/src/language/literal.rs b/sway-core/src/language/literal.rs index 8f198511195..21bdc2f5089 100644 --- a/sway-core/src/language/literal.rs +++ b/sway-core/src/language/literal.rs @@ -1,7 +1,7 @@ use crate::{type_system::*, Engines}; use sway_error::error::CompileError; -use sway_types::{integer_bits::IntegerBits, span}; +use sway_types::{integer_bits::IntegerBits, span, u256::U256}; use std::{ fmt, @@ -15,7 +15,7 @@ pub enum Literal { U16(u16), U32(u32), U64(u64), - U256(u64), + U256(U256), String(span::Span), Numeric(u64), Boolean(bool), 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 59f99521ae4..da741ddd2fb 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 @@ -173,7 +173,9 @@ 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(x) => Pattern::U64(Range::from_single(x)), + Literal::U256(x) => Pattern::U64(Range::from_single( + x.try_into().expect("pattern only works with 64 bits"), + )), Literal::B256(x) => Pattern::B256(x), Literal::Boolean(b) => Pattern::Boolean(b), Literal::Numeric(x) => Pattern::Numeric(Range::from_single(x)), 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 629d598345b..d7f113d27b3 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 @@ -38,7 +38,7 @@ use sway_error::{ handler::{ErrorEmitted, Handler}, warning::{CompileWarning, Warning}, }; -use sway_types::{integer_bits::IntegerBits, Ident, Named, Span, Spanned}; +use sway_types::{integer_bits::IntegerBits, u256::U256, Ident, Named, Span, Spanned}; use rustc_hash::FxHashSet; @@ -1836,18 +1836,8 @@ impl ty::TyExpression { }), new_type, ), - //TODO u256 limited to u64 literals - IntegerBits::V256 => ( - num.to_string().parse().map(Literal::U256).map_err(|e| { - Literal::handle_parse_int_error( - engines, - e, - TypeInfo::UnsignedInteger(IntegerBits::V256), - span.clone(), - ) - }), - new_type, - ), + // Numerics are limited to u64 for now + IntegerBits::V256 => (Ok(Literal::U256(U256::from(num))), new_type), }, TypeInfo::Numeric => ( num.to_string().parse().map(Literal::Numeric).map_err(|e| { 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 e7be318f2c2..d14a7a556f5 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 @@ -2888,17 +2888,7 @@ fn literal_to_literal( }; Literal::U64(value) } - // TODO u256 are limited to u64 literals for the moment - LitIntType::U256 => { - let value = match u64::try_from(parsed) { - Ok(value) => value, - Err(..) => { - let error = ConvertParseTreeError::U64LiteralOutOfRange { span }; - return Err(handler.emit_err(error.into())); - } - }; - Literal::U256(value) - } + LitIntType::U256 => Literal::U256(parsed.into()), LitIntType::I8 | LitIntType::I16 | LitIntType::I32 | LitIntType::I64 => { let error = ConvertParseTreeError::SignedIntegersNotSupported { span }; return Err(handler.emit_err(error.into())); diff --git a/sway-ir/src/constant.rs b/sway-ir/src/constant.rs index 33955a808ed..a8fda819a63 100644 --- a/sway-ir/src/constant.rs +++ b/sway-ir/src/constant.rs @@ -1,6 +1,7 @@ //! [`Constant`] is a typed constant value. use crate::{context::Context, irtype::Type, pretty::DebugWithContext, value::Value}; +use sway_types::u256::U256; /// A [`Type`] and constant value, including [`ConstantValue::Undef`] for uninitialized constants. #[derive(Debug, Clone, DebugWithContext)] @@ -16,7 +17,7 @@ pub enum ConstantValue { Unit, Bool(bool), Uint(u64), - U256(u64), // TODO u256 limited to u64 values + U256(U256), B256([u8; 32]), String(Vec), Array(Vec), @@ -38,16 +39,24 @@ impl Constant { } } + /// For numbers bigger than u64 see `new_uint256`. pub fn new_uint(context: &mut Context, nbits: u16, n: u64) -> Self { Constant { ty: Type::new_uint(context, nbits), value: match nbits { - 256 => ConstantValue::U256(n), + 256 => ConstantValue::U256(n.into()), _ => ConstantValue::Uint(n), }, } } + pub fn new_uint256(context: &mut Context, n: U256) -> Self { + Constant { + ty: Type::new_uint(context, 256), + value: ConstantValue::U256(n), + } + } + pub fn new_b256(context: &Context, bytes: [u8; 32]) -> Self { Constant { ty: Type::get_b256(context), @@ -98,6 +107,11 @@ impl Constant { Value::new_constant(context, new_const) } + pub fn get_uint256(context: &mut Context, value: U256) -> Value { + let new_const = Constant::new_uint256(context, value); + Value::new_constant(context, new_const) + } + pub fn get_b256(context: &mut Context, value: [u8; 32]) -> Value { let new_const = Constant::new_b256(context, value); Value::new_constant(context, new_const) diff --git a/sway-ir/src/instruction.rs b/sway-ir/src/instruction.rs index 24dc43e83f9..c27d2d70794 100644 --- a/sway-ir/src/instruction.rs +++ b/sway-ir/src/instruction.rs @@ -155,6 +155,11 @@ pub enum FuelVmInstruction { stored_val: Value, key: Value, }, + WideUnaryOp { + op: UnaryOpKind, + result: Value, + arg: Value, + }, WideBinaryOp { op: BinaryOpKind, result: Value, @@ -302,6 +307,9 @@ impl Instruction { | Instruction::Store { .. } => Some(Type::get_unit(context)), // Wide Operations + Instruction::FuelVm(FuelVmInstruction::WideUnaryOp { result, .. }) => { + result.get_type(context) + } Instruction::FuelVm(FuelVmInstruction::WideBinaryOp { result, .. }) => { result.get_type(context) } @@ -414,6 +422,7 @@ impl Instruction { vec![*stored_val, *key, *number_of_slots] } FuelVmInstruction::StateStoreWord { stored_val, key } => vec![*stored_val, *key], + FuelVmInstruction::WideUnaryOp { arg, result, .. } => vec![*result, *arg], FuelVmInstruction::WideBinaryOp { arg1, arg2, result, .. } => vec![*result, *arg1, *arg2], @@ -571,6 +580,10 @@ impl Instruction { replace(key); replace(stored_val); } + FuelVmInstruction::WideUnaryOp { arg, result, .. } => { + replace(arg); + replace(result); + } FuelVmInstruction::WideBinaryOp { arg1, arg2, result, .. } => { @@ -614,6 +627,7 @@ impl Instruction { | Instruction::MemCopyVal { .. } | Instruction::Store { .. } | Instruction::Ret(..) + | Instruction::FuelVm(FuelVmInstruction::WideUnaryOp { .. }) | Instruction::FuelVm(FuelVmInstruction::WideBinaryOp { .. }) | Instruction::FuelVm(FuelVmInstruction::WideCmpOp { .. }) | Instruction::FuelVm(FuelVmInstruction::WideModularOp { .. }) => true, @@ -753,6 +767,13 @@ impl<'a, 'eng> InstructionInserter<'a, 'eng> { make_instruction!(self, Instruction::UnaryOp { op, arg }) } + pub fn wide_unary_op(self, op: UnaryOpKind, arg: Value, result: Value) -> Value { + make_instruction!( + self, + Instruction::FuelVm(FuelVmInstruction::WideUnaryOp { op, arg, result }) + ) + } + pub fn wide_binary_op( self, op: BinaryOpKind, diff --git a/sway-ir/src/optimize/dce.rs b/sway-ir/src/optimize/dce.rs index 255db2a88da..14a19c72bd4 100644 --- a/sway-ir/src/optimize/dce.rs +++ b/sway-ir/src/optimize/dce.rs @@ -119,6 +119,9 @@ fn get_loaded_symbols(context: &Context, val: Value) -> Vec { Instruction::FuelVm(FuelVmInstruction::Gtf { .. }) | Instruction::FuelVm(FuelVmInstruction::ReadRegister(_)) | Instruction::FuelVm(FuelVmInstruction::Revert(_)) => vec![], + Instruction::FuelVm(FuelVmInstruction::WideUnaryOp { arg, .. }) => { + get_symbols(context, *arg).to_vec() + } Instruction::FuelVm(FuelVmInstruction::WideBinaryOp { arg1, arg2, .. }) | Instruction::FuelVm(FuelVmInstruction::WideCmpOp { arg1, arg2, .. }) => { get_symbols(context, *arg1) @@ -184,6 +187,7 @@ fn get_stored_symbols(context: &Context, val: Value) -> Vec { vec![] } FuelVmInstruction::StateStoreQuadWord { stored_val: _, .. } => vec![], + FuelVmInstruction::WideUnaryOp { result, .. } => get_symbols(context, *result).to_vec(), FuelVmInstruction::WideBinaryOp { result, .. } => { get_symbols(context, *result).to_vec() } diff --git a/sway-ir/src/optimize/inline.rs b/sway-ir/src/optimize/inline.rs index 3194a86113d..1a2f64cc863 100644 --- a/sway-ir/src/optimize/inline.rs +++ b/sway-ir/src/optimize/inline.rs @@ -585,6 +585,9 @@ fn inline_instruction( FuelVmInstruction::StateStoreWord { stored_val, key } => new_block .ins(context) .state_store_word(map_value(stored_val), map_value(key)), + FuelVmInstruction::WideUnaryOp { op, arg, result } => new_block + .ins(context) + .wide_unary_op(op, map_value(arg), map_value(result)), FuelVmInstruction::WideBinaryOp { op, arg1, diff --git a/sway-ir/src/optimize/misc_demotion.rs b/sway-ir/src/optimize/misc_demotion.rs index 2ae2143b947..0b7909821ea 100644 --- a/sway-ir/src/optimize/misc_demotion.rs +++ b/sway-ir/src/optimize/misc_demotion.rs @@ -13,7 +13,7 @@ use std::ops::Not; /// - Fuel WIde binary operators: Demote binary operands bigger than 64 bits. use crate::{ asm::AsmArg, AnalysisResults, BinaryOpKind, Constant, Context, FuelVmInstruction, Function, - Instruction, IrError, Pass, PassMutability, Predicate, ScopedPass, Type, Value, + Instruction, IrError, Pass, PassMutability, Predicate, ScopedPass, Type, UnaryOpKind, Value, }; use rustc_hash::FxHashMap; @@ -38,10 +38,18 @@ pub fn misc_demotion( let asm_arg_res = asm_block_arg_demotion(context, function)?; let asm_ret_res = asm_block_ret_demotion(context, function)?; let addrof_res = ptr_to_int_demotion(context, function)?; + let wide_binary_op_res = wide_binary_op_demotion(context, function)?; let wide_cmp_res = wide_cmp_demotion(context, function)?; - - Ok(log_res || asm_arg_res || asm_ret_res || addrof_res || wide_binary_op_res || wide_cmp_res) + let wide_unary_op_res = wide_unary_op_demotion(context, function)?; + + Ok(log_res + || asm_arg_res + || asm_ret_res + || addrof_res + || wide_unary_op_res + || wide_binary_op_res + || wide_cmp_res) } fn log_demotion(context: &mut Context, function: Function) -> Result { @@ -712,3 +720,134 @@ fn wide_cmp_demotion(context: &mut Context, function: Function) -> Result Result { + // Find all intrinsics on wide operators + let candidates = function + .instruction_iter(context) + .filter_map(|(block, instr_val)| { + let instr = instr_val.get_instruction(context)?; + + if let Instruction::UnaryOp { op, arg } = instr { + let arg_type = arg + .get_type(context) + .and_then(|x| x.get_uint_width(context)); + + match arg_type { + Some(256) => { + use UnaryOpKind::*; + match op { + Not => Some((block, instr_val)), + } + } + _ => None, + } + } else { + None + } + }) + .collect::>(); + + if candidates.is_empty() { + return Ok(false); + } + + // Now create a local for the result + // get ptr to each arg + // and store the result after + for (block, binary_op_instr_val) in candidates { + let Instruction::UnaryOp { arg, .. } = binary_op_instr_val + .get_instruction(context) + .cloned() + .unwrap() else { + continue; + }; + + let unary_op_metadata = binary_op_instr_val.get_metadata(context); + + let arg_ty = arg.get_type(context).unwrap(); + let arg_metadata = arg.get_metadata(context); + + let result_local = + function.new_unique_local_var(context, "__wide_result".to_owned(), arg_ty, None, true); + let get_result_local = Value::new_instruction(context, Instruction::GetLocal(result_local)) + .add_metadatum(context, unary_op_metadata); + let load_result_local = + Value::new_instruction(context, Instruction::Load(get_result_local)) + .add_metadatum(context, unary_op_metadata); + + // If arg1 is not a pointer, store it to a local + let lhs_store = arg_ty.is_ptr(context).not().then(|| { + let lhs_local = function.new_unique_local_var( + context, + "__wide_lhs".to_owned(), + arg_ty, + None, + false, + ); + let get_lhs_local = Value::new_instruction(context, Instruction::GetLocal(lhs_local)) + .add_metadatum(context, arg_metadata); + let store_lhs_local = Value::new_instruction( + context, + Instruction::Store { + dst_val_ptr: get_lhs_local, + stored_val: arg, + }, + ) + .add_metadatum(context, arg_metadata); + (get_lhs_local, store_lhs_local) + }); + + let (arg1_needs_insert, get_arg) = if let Some((lhs_local, _)) = &lhs_store { + (false, *lhs_local) + } else { + (true, arg) + }; + + // Assert all operands are pointers + assert!(get_arg.get_type(context).unwrap().is_ptr(context)); + assert!(get_result_local.get_type(context).unwrap().is_ptr(context)); + + let wide_op = Value::new_instruction( + context, + Instruction::FuelVm(FuelVmInstruction::WideUnaryOp { + op: UnaryOpKind::Not, + arg: get_arg, + result: get_result_local, + }), + ) + .add_metadatum(context, unary_op_metadata); + + // We don't have an actual instruction _inserter_ yet, just an appender, so we need to find + // the ptr_to_int instruction index and insert instructions manually. + let block_instrs = &mut context.blocks[block.0].instructions; + let idx = block_instrs + .iter() + .position(|&instr_val| instr_val == binary_op_instr_val) + .unwrap(); + + block + .replace_instruction(context, binary_op_instr_val, load_result_local) + .unwrap(); + + let block_instrs = &mut context.blocks[block.0].instructions; + + block_instrs.insert(idx, wide_op); + block_instrs.insert(idx, get_result_local); + + if arg1_needs_insert { + block_instrs.insert(idx, get_arg); + } + + // lhs + if let Some((get_lhs_local, store_lhs_local)) = lhs_store { + block_instrs.insert(idx, store_lhs_local); + block_instrs.insert(idx, get_lhs_local); + } + } + + Ok(true) +} diff --git a/sway-ir/src/parser.rs b/sway-ir/src/parser.rs index ad1092bc64f..cf783597bc9 100644 --- a/sway-ir/src/parser.rs +++ b/sway-ir/src/parser.rs @@ -24,7 +24,7 @@ pub fn parse<'eng>( // ------------------------------------------------------------------------------------------------- mod ir_builder { - use sway_types::{ident::Ident, span::Span, SourceEngine}; + use sway_types::{ident::Ident, span::Span, u256::U256, SourceEngine}; type MdIdxRef = u64; @@ -176,6 +176,7 @@ mod ir_builder { rule operation() -> IrAstOperation = op_asm() + / op_wide_unary() / op_wide_binary() / op_wide_cmp() / op_branch() @@ -236,6 +237,11 @@ mod ir_builder { IrAstOperation::WideModularOp(op, arg1, arg2, arg3, result) } + rule op_wide_unary() -> IrAstOperation + = "wide" _ op:unary_op_kind() arg:id() "to" _ result:id() { + IrAstOperation::WideUnaryOp(op, arg, result) + } + rule op_wide_binary() -> IrAstOperation = "wide" _ op:binary_op_kind() arg1:id() comma() arg2:id() "to" _ result:id() { IrAstOperation::WideBinaryOp(op, arg1, arg2, result) @@ -720,6 +726,7 @@ mod ir_builder { StateStoreQuadWord(String, String, String), StateStoreWord(String, String), Store(String, String), + WideUnaryOp(UnaryOpKind, String, String), WideBinaryOp(BinaryOpKind, String, String, String), WideCmp(Predicate, String, String), WideModularOp(BinaryOpKind, String, String, String, String), @@ -771,17 +778,14 @@ mod ir_builder { IrAstConstValue::Undef(_) => ConstantValue::Undef, IrAstConstValue::Unit => ConstantValue::Unit, IrAstConstValue::Bool(b) => ConstantValue::Bool(*b), - IrAstConstValue::Hex256(bs) => { - match val_ty { - IrAstTy::U256 => { - // TODO u256 limited to u64 - let n = u64::from_be_bytes(bs[24..].try_into().unwrap()); - ConstantValue::U256(n) - } - IrAstTy::B256 => ConstantValue::B256(*bs), - _ => unreachable!("invalid type for hex number"), + IrAstConstValue::Hex256(bs) => match val_ty { + IrAstTy::U256 => { + let n = U256::from_be_bytes(bs); + ConstantValue::U256(n) } - } + IrAstTy::B256 => ConstantValue::B256(*bs), + _ => unreachable!("invalid type for hex number"), + }, IrAstConstValue::Number(n) => ConstantValue::Uint(*n), IrAstConstValue::String(bs) => ConstantValue::String(bs.clone()), IrAstConstValue::Array(el_ty, els) => { @@ -813,17 +817,14 @@ mod ir_builder { IrAstConstValue::Undef(_) => unreachable!("Can't convert 'undef' to a value."), IrAstConstValue::Unit => Constant::get_unit(context), IrAstConstValue::Bool(b) => Constant::get_bool(context, *b), - IrAstConstValue::Hex256(bs) => { - match val_ty { - IrAstTy::U256 => { - // TODO u256 limited to u64 - let n = u64::from_be_bytes(bs[24..].try_into().unwrap()); - Constant::get_uint(context, 256, n) - } - IrAstTy::B256 => Constant::get_b256(context, *bs), - _ => unreachable!("invalid type for hex number"), + IrAstConstValue::Hex256(bs) => match val_ty { + IrAstTy::U256 => { + let n = U256::from_be_bytes(bs); + Constant::get_uint256(context, n) } - } + IrAstTy::B256 => Constant::get_b256(context, *bs), + _ => unreachable!("invalid type for hex number"), + }, IrAstConstValue::Number(n) => Constant::get_uint(context, 64, *n), IrAstConstValue::String(s) => Constant::get_string(context, s.clone()), IrAstConstValue::Array(..) => { @@ -1093,6 +1094,15 @@ mod ir_builder { .ins(context) .unary_op(op, *val_map.get(&arg).unwrap()) .add_metadatum(context, opt_metadata), + // Wide Operations + IrAstOperation::WideUnaryOp(op, arg, result) => block + .ins(context) + .wide_unary_op( + op, + *val_map.get(&arg).unwrap(), + *val_map.get(&result).unwrap(), + ) + .add_metadatum(context, opt_metadata), IrAstOperation::WideBinaryOp(op, arg1, arg2, result) => block .ins(context) .wide_binary_op( diff --git a/sway-ir/src/printer.rs b/sway-ir/src/printer.rs index 2ed471bab85..8d262b6457c 100644 --- a/sway-ir/src/printer.rs +++ b/sway-ir/src/printer.rs @@ -715,6 +715,19 @@ fn instruction_to_doc<'a>( .append(md_namer.md_idx_to_doc(context, metadata)), )) } + FuelVmInstruction::WideUnaryOp { op, arg, result } => { + let op_str = match op { + UnaryOpKind::Not => "not", + }; + maybe_constant_to_doc(context, md_namer, namer, arg).append(Doc::line( + Doc::text(format!( + "wide {op_str} {} to {}", + namer.name(context, arg), + namer.name(context, result), + )) + .append(md_namer.md_idx_to_doc(context, metadata)), + )) + } FuelVmInstruction::WideBinaryOp { op, arg1, @@ -1004,10 +1017,7 @@ impl Constant { ConstantValue::Bool(b) => format!("bool {}", if *b { "true" } else { "false" }), ConstantValue::Uint(v) => format!("{} {}", self.ty.as_string(context), v), ConstantValue::U256(v) => { - // TODO u256 limited to u64 - let mut bytes = vec![0u8; 24]; - bytes.extend(&v.to_be_bytes()); - assert!(bytes.len() == 32); + let bytes = v.to_be_bytes(); format!( "u256 0x{}", bytes diff --git a/sway-ir/src/verify.rs b/sway-ir/src/verify.rs index aaf4390db35..8e614d6f148 100644 --- a/sway-ir/src/verify.rs +++ b/sway-ir/src/verify.rs @@ -236,6 +236,9 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { stored_val: dst_val, key, } => self.verify_state_store_word(dst_val, key)?, + FuelVmInstruction::WideUnaryOp { op, result, arg } => { + self.verify_wide_unary_op(op, result, arg)? + } FuelVmInstruction::WideBinaryOp { op, result, @@ -396,6 +399,26 @@ impl<'a, 'eng> InstructionVerifier<'a, 'eng> { Ok(()) } + fn verify_wide_unary_op( + &self, + _op: &UnaryOpKind, + result: &Value, + arg: &Value, + ) -> Result<(), IrError> { + let result_ty = result + .get_type(self.context) + .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?; + let arg_ty = arg + .get_type(self.context) + .ok_or(IrError::VerifyBinaryOpIncorrectArgType)?; + + if !arg_ty.is_ptr(self.context) || !result_ty.is_ptr(self.context) { + return Err(IrError::VerifyBinaryOpIncorrectArgType); + } + + Ok(()) + } + fn verify_binary_op( &self, _op: &BinaryOpKind, diff --git a/sway-ir/tests/demote_misc/demote_wide_not_constants.ir b/sway-ir/tests/demote_misc/demote_wide_not_constants.ir new file mode 100644 index 00000000000..46727c2dbe2 --- /dev/null +++ b/sway-ir/tests/demote_misc/demote_wide_not_constants.ir @@ -0,0 +1,18 @@ +script { + entry fn main() -> bool { + entry(): + v0 = const u256 0x0000000000000000000000000000000000000000000000000000000000000001 + v1 = not v0 + v2 = const bool true + ret bool v2 + } +} + +// regex: VAL=v\d+ +// regex: ID=[[:alpha:]0-9_]+ + +// check: v0 = get_local ptr u256, __wide_lhs +// check: v1 = const u256 0x0000000000000000000000000000000000000000000000000000000000000001 +// check: store v1 to v0 +// check: v2 = get_local ptr u256, __wide_result +// check: wide not v0 to v2 diff --git a/sway-ir/tests/serialize/wide_ops.ir b/sway-ir/tests/serialize/wide_ops.ir index 911fb6951a8..68bedb1c920 100644 --- a/sway-ir/tests/serialize/wide_ops.ir +++ b/sway-ir/tests/serialize/wide_ops.ir @@ -19,6 +19,9 @@ script { wide mod v0, v0 to v0 // check: wide mod v0, v0 to v0 + wide not v0 to v0 +// check: wide not v0 to v0 + v5 = const unit () ret () v5 } diff --git a/sway-lib-core/src/ops.sw b/sway-lib-core/src/ops.sw index 3b53207c951..176dc70de44 100644 --- a/sway-lib-core/src/ops.sw +++ b/sway-lib-core/src/ops.sw @@ -228,6 +228,12 @@ impl Not for bool { } } +impl Not for u256 { + fn not(self) -> Self { + __not(self) + } +} + pub trait Eq { fn eq(self, other: Self) -> bool; } { diff --git a/sway-types/Cargo.toml b/sway-types/Cargo.toml index 907ea45e596..6f192a5c5e4 100644 --- a/sway-types/Cargo.toml +++ b/sway-types/Cargo.toml @@ -13,7 +13,9 @@ fuel-asm = { workspace = true } fuel-crypto = { workspace = true } fuel-tx = { workspace = true } lazy_static = "1.4" +num-bigint = "0.4.3" serde = { version = "1.0", features = ["derive"] } +thiserror = "1" [features] no-span-debug = [] diff --git a/sway-types/src/lib.rs b/sway-types/src/lib.rs index 32dcd979e60..4e6b9d6a42d 100644 --- a/sway-types/src/lib.rs +++ b/sway-types/src/lib.rs @@ -9,6 +9,7 @@ use std::{io, iter, slice}; pub mod constants; pub mod ident; +pub mod u256; pub use ident::*; pub mod integer_bits; diff --git a/sway-types/src/u256.rs b/sway-types/src/u256.rs new file mode 100644 index 00000000000..072fb1dc42b --- /dev/null +++ b/sway-types/src/u256.rs @@ -0,0 +1,63 @@ +use num_bigint::{BigUint, ParseBigIntError, TryFromBigIntError}; +use thiserror::Error; + +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Hash)] +pub struct U256(BigUint); + +impl U256 { + pub fn from_be_bytes(bytes: &[u8; 32]) -> Self { + let v = BigUint::from_bytes_be(bytes.as_slice()); + Self(v) + } + + pub fn to_be_bytes(&self) -> [u8; 32] { + let mut v = self.0.to_bytes_be(); + let mut bytes = vec![0u8; 32 - v.len()]; + bytes.append(&mut v); + assert!(bytes.len() == 32); + bytes.try_into().expect("unexpected vector size") + } +} + +impl std::fmt::Display for U256 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl From for U256 { + fn from(value: BigUint) -> Self { + Self(value) + } +} + +impl From for U256 { + fn from(value: u64) -> Self { + Self(BigUint::from(value)) + } +} + +impl TryFrom for u64 { + type Error = Error; + + fn try_from(value: U256) -> Result { + value.0.try_into().map_err(Error::TryIntoBigIntError) + } +} + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + ParseBigIntError(ParseBigIntError), + #[error("{0}")] + TryIntoBigIntError(TryFromBigIntError), +} + +impl std::str::FromStr for U256 { + type Err = Error; + + fn from_str(s: &str) -> Result { + let v = BigUint::from_str(s).map_err(Error::ParseBigIntError)?; + Ok(Self(v)) + } +} diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/src/main.sw b/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/src/main.sw index 5cc5d3a96bc..b02a61dc2a5 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/src/main.sw +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/src/main.sw @@ -19,6 +19,13 @@ fn locals() -> u256 { result } +// returns 2 +fn not_operator() -> u256 { + let a = 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFu256; + let b = 0x0000000000000000000000000000000000000000000000000000000000000002u256; + !(a - b) +} + fn main() -> u256 { - constants() + locals() + constants() + locals() + not_operator() } \ No newline at end of file diff --git a/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/test.toml b/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/test.toml index be2623a6bec..ef9c8a729f8 100644 --- a/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/test.toml +++ b/test/src/e2e_vm_tests/test_programs/should_pass/language/u256/test.toml @@ -1,3 +1,3 @@ category = "run" -expected_result = { action = "return_data", value = "0000000000000000000000000000000000000000000000000000000000000004" } +expected_result = { action = "return_data", value = "0000000000000000000000000000000000000000000000000000000000000006" } validate_abi = true