From 224f64fc200632cf0210513c6b6d017d37ae5874 Mon Sep 17 00:00:00 2001 From: mitchmindtree Date: Tue, 7 Feb 2023 16:53:35 +1000 Subject: [PATCH] `fuel-asm` - refactor for improved type-safety around instructions, opcodes (#283) --- fuel-asm/README.md | 73 +- fuel-asm/src/{opcode => }/args.rs | 0 fuel-asm/src/instruction.rs | 368 ------- fuel-asm/src/instruction_result.rs | 60 ++ fuel-asm/src/lib.rs | 650 +++++++++++- fuel-asm/src/macros.rs | 686 ++++++++++++- fuel-asm/src/op.rs | 34 + fuel-asm/src/opcode.rs | 936 ------------------ fuel-asm/src/opcode/consts.rs | 599 ----------- fuel-asm/src/pack.rs | 84 ++ fuel-asm/src/panic_reason.rs | 99 +- fuel-asm/src/unpack.rs | 89 ++ fuel-asm/tests/encoding.rs | 425 ++++---- fuel-tx/src/transaction/types/script.rs | 3 +- fuel-tx/tests/bytes.rs | 72 +- fuel-vm/src/call.rs | 13 +- fuel-vm/src/checked_transaction.rs | 3 +- fuel-vm/src/consts.rs | 56 -- fuel-vm/src/error.rs | 19 +- fuel-vm/src/interpreter.rs | 13 +- fuel-vm/src/interpreter/alu.rs | 27 +- fuel-vm/src/interpreter/balances.rs | 4 +- fuel-vm/src/interpreter/blockchain.rs | 20 +- fuel-vm/src/interpreter/contract.rs | 10 +- fuel-vm/src/interpreter/debug.rs | 27 +- fuel-vm/src/interpreter/diff/tests.rs | 9 +- .../src/interpreter/executors/instruction.rs | 503 ++++++---- .../executors/instruction/tests.rs | 2 +- .../instruction/tests/reserved_registers.rs | 362 ++++--- fuel-vm/src/interpreter/executors/main.rs | 14 +- .../src/interpreter/executors/predicate.rs | 9 +- fuel-vm/src/interpreter/flow.rs | 101 +- fuel-vm/src/interpreter/gas.rs | 22 +- fuel-vm/src/interpreter/initialization.rs | 9 +- fuel-vm/src/interpreter/internal.rs | 36 +- fuel-vm/src/interpreter/log.rs | 10 +- fuel-vm/src/interpreter/memory.rs | 74 +- fuel-vm/src/interpreter/metadata.rs | 4 +- fuel-vm/src/lib.rs | 2 +- fuel-vm/src/predicate.rs | 7 +- fuel-vm/src/state/debug.rs | 4 +- fuel-vm/src/util.rs | 58 +- fuel-vm/tests/alu.rs | 317 +++--- fuel-vm/tests/backtrace.rs | 40 +- fuel-vm/tests/blockchain.rs | 757 +++++++------- fuel-vm/tests/cgas.rs | 85 +- fuel-vm/tests/code_coverage.rs | 13 +- fuel-vm/tests/contract.rs | 123 +-- fuel-vm/tests/crypto.rs | 176 ++-- fuel-vm/tests/flow.rs | 185 ++-- fuel-vm/tests/gas_factor.rs | 5 +- fuel-vm/tests/memory.rs | 35 +- fuel-vm/tests/metadata.rs | 835 ++++++++-------- fuel-vm/tests/outputs.rs | 110 +- fuel-vm/tests/predicate.rs | 93 +- fuel-vm/tests/profile_gas.rs | 15 +- fuel-vm/tests/validation.rs | 3 +- 57 files changed, 3938 insertions(+), 4450 deletions(-) rename fuel-asm/src/{opcode => }/args.rs (100%) delete mode 100644 fuel-asm/src/instruction.rs create mode 100644 fuel-asm/src/instruction_result.rs create mode 100644 fuel-asm/src/op.rs delete mode 100644 fuel-asm/src/opcode.rs delete mode 100644 fuel-asm/src/opcode/consts.rs create mode 100644 fuel-asm/src/pack.rs create mode 100644 fuel-asm/src/unpack.rs diff --git a/fuel-asm/README.md b/fuel-asm/README.md index dad584ab51..6b17a03ff4 100644 --- a/fuel-asm/README.md +++ b/fuel-asm/README.md @@ -16,63 +16,46 @@ Instruction set for the [FuelVM](https://github.com/FuelLabs/fuel-specs). ```rust use fuel_asm::*; -use Opcode::*; // A sample program to perform ecrecover let program = vec![ - MOVE(0x10, 0x01), // set r[0x10] := $one - SLLI(0x20, 0x10, 5), // set r[0x20] := `r[0x10] << 5 == 32` - SLLI(0x21, 0x10, 6), // set r[0x21] := `r[0x10] << 6 == 64` - ALOC(0x21), // alloc `r[0x21] == 64` to the heap - ADDI(0x10, 0x07, 1), // set r[0x10] := `$hp + 1` (allocated heap) - MOVE(0x11, 0x04), // set r[0x11] := $ssp - ADD(0x12, 0x04, 0x20), // set r[0x12] := `$ssp + r[0x20]` - ECR(0x10, 0x11, 0x12), // recover public key in memory[r[0x10], 64] - RET(0x01), // return `1` + op::move_(0x10, 0x01), // set r[0x10] := $one + op::slli(0x20, 0x10, 5), // set r[0x20] := `r[0x10] << 5 == 32` + op::slli(0x21, 0x10, 6), // set r[0x21] := `r[0x10] << 6 == 64` + op::aloc(0x21), // alloc `r[0x21] == 64` to the heap + op::addi(0x10, 0x07, 1), // set r[0x10] := `$hp + 1` (allocated heap) + op::move_(0x11, 0x04), // set r[0x11] := $ssp + op::add(0x12, 0x04, 0x20), // set r[0x12] := `$ssp + r[0x20]` + op::ecr(0x10, 0x11, 0x12), // recover public key in memory[r[0x10], 64] + op::ret(0x01), // return `1` ]; // Convert program to bytes representation let bytes: Vec = program.iter().copied().collect(); // A program can be reconstructed from an iterator of bytes -let restored = Opcode::from_bytes_iter(bytes.iter().copied()); - -assert_eq!(program, restored); +let restored: Result, _> = fuel_asm::from_bytes(bytes).collect(); +assert_eq!(program, restored.unwrap()); // Every instruction can be described as `u32` big-endian bytes -let halfwords: Vec = program.iter().copied().map(u32::from).collect(); +let halfwords: Vec = program.iter().copied().collect(); let bytes = halfwords.iter().copied().map(u32::to_be_bytes).flatten(); -let restored = Opcode::from_bytes_iter(bytes); - -assert_eq!(program, restored); +let restored: Result, _> = fuel_asm::from_bytes(bytes).collect(); +assert_eq!(program, restored.unwrap()); // We can also reconstruct the instructions individually -let restored: Vec = halfwords.iter().copied().map(Opcode::from).collect(); - -assert_eq!(program, restored); - -// We have an unchecked variant for optimal performance -let restored: Vec = halfwords - .iter() - .copied() - .map(|w| unsafe { Opcode::from_bytes_unchecked(&w.to_be_bytes()) }) - .collect(); - -assert_eq!(program, restored); - -// Finally, we have [`Instruction`] to allow optimal runtime parsing of the components of the -// opcode -// -// `Opcode` itself is only but an abstraction/helper to facilitate visualization, but the VM is -// expected to use raw instructions -let instrs: Vec = program.iter().copied().map(Instruction::from).collect(); -let restored: Vec = instrs.iter().copied().map(Opcode::from).collect(); - -assert_eq!(program, restored); - -// An instruction is composed by the opcode representation registers Id and immediate values -assert_eq!(instrs[1].op(), OpcodeRepr::SLLI as u8); -assert_eq!(instrs[1].ra(), 0x20); -assert_eq!(instrs[1].rb(), 0x10); -assert_eq!(instrs[1].imm12(), 5); +let restored: Result, _> = fuel_asm::from_u32s(halfwords).collect(); +assert_eq!(program, restored.unwrap()); + +// An instruction is composed by the opcode representation, register IDs and immediate value. +let instruction = program[1]; +assert_eq!(instruction.opcode(), Opcode::SLLI); +let slli = match instruction { + Instruction::SLLI(slli) => slli, + _ => panic!("unexpected instruction"), +}; +let (ra, rb, imm) = slli.unpack(); +assert_eq!(u8::from(ra), 0x20); +assert_eq!(u8::from(rb), 0x10); +assert_eq!(u32::from(imm), 5); ``` diff --git a/fuel-asm/src/opcode/args.rs b/fuel-asm/src/args.rs similarity index 100% rename from fuel-asm/src/opcode/args.rs rename to fuel-asm/src/args.rs diff --git a/fuel-asm/src/instruction.rs b/fuel-asm/src/instruction.rs deleted file mode 100644 index e6f36df113..0000000000 --- a/fuel-asm/src/instruction.rs +++ /dev/null @@ -1,368 +0,0 @@ -use crate::opcode::{Opcode, OpcodeRepr}; - -use fuel_types::{Immediate06, Immediate12, Immediate18, Immediate24, RegisterId, Word}; - -#[cfg(feature = "std")] -use std::{io, iter}; - -/// A version of Opcode that can be used without unnecessary branching -#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -pub struct Instruction { - /// Opcode - op: u8, - /// Register A - ra: RegisterId, - /// Register B - rb: RegisterId, - /// Register C - rc: RegisterId, - /// Register D - rd: RegisterId, - /// Immediate with 6 bits - imm06: Immediate06, - /// Immediate with 12 bits - imm12: Immediate12, - /// Immediate with 18 bits - imm18: Immediate18, - /// Immediate with 24 bits - imm24: Immediate24, -} - -impl Instruction { - /// Size of an opcode in bytes - pub const LEN: usize = 4; - - /// Extracts fields from a raw instruction - pub const fn new(instruction: u32) -> Self { - // TODO Optimize with native architecture (eg SIMD?) or facilitate - // auto-vectorization - - let op = (instruction >> 24) as u8; - - let ra = ((instruction >> 18) & 0x3f) as RegisterId; - let rb = ((instruction >> 12) & 0x3f) as RegisterId; - let rc = ((instruction >> 6) & 0x3f) as RegisterId; - let rd = (instruction & 0x3f) as RegisterId; - - let imm06 = (instruction & 0xff) as Immediate06; - let imm12 = (instruction & 0x0fff) as Immediate12; - let imm18 = (instruction & 0x3ffff) as Immediate18; - let imm24 = (instruction & 0xffffff) as Immediate24; - - Self { - op, - ra, - rb, - rc, - rd, - imm06, - imm12, - imm18, - imm24, - } - } - - /// Opcode - pub const fn op(&self) -> u8 { - self.op - } - - /// Register A - pub const fn ra(&self) -> RegisterId { - self.ra - } - - /// Register B - pub const fn rb(&self) -> RegisterId { - self.rb - } - - /// Register C - pub const fn rc(&self) -> RegisterId { - self.rc - } - - /// Register D - pub const fn rd(&self) -> RegisterId { - self.rd - } - - /// Immediate with 6 bits - pub const fn imm06(&self) -> Immediate06 { - self.imm06 - } - - /// Immediate with 12 bits - pub const fn imm12(&self) -> Immediate12 { - self.imm12 - } - - /// Immediate with 18 bits - pub const fn imm18(&self) -> Immediate18 { - self.imm18 - } - - /// Immediate with 24 bits - pub const fn imm24(&self) -> Immediate24 { - self.imm24 - } - - /// Create a `Instruction` from a slice of bytes - /// - /// # Safety - /// - /// The caller must ensure that the slice is has at least `Self::LEN` bytes. - pub unsafe fn from_slice_unchecked(buf: &[u8]) -> Self { - debug_assert!(buf.len() >= Self::LEN); - - let instr = fuel_types::bytes::from_slice_unchecked(buf); - let instr = u32::from_be_bytes(instr); - - Self::from(instr) - } - - /// Convert the opcode to bytes representation - pub fn to_bytes(self) -> [u8; Self::LEN] { - u32::from(self).to_be_bytes() - } - - /// Splits a Word into two [`Instruction`] that can be used to construct [`crate::Opcode`] - pub const fn parse_word(word: Word) -> (Instruction, Instruction) { - // Assumes Word is u64 - // https://doc.rust-lang.org/nightly/reference/expressions/operator-expr.html#numeric-cast4 - let lo = word as u32; // truncates, see link above - let hi = (word >> 32) as u32; - - (Instruction::new(hi), Instruction::new(lo)) - } - - /// Convert the instruction into its internal representation - /// - /// `(repr, $ra, $rb, $rc, $rd, immediate)` - pub const fn into_inner(self) -> (OpcodeRepr, RegisterId, RegisterId, RegisterId, RegisterId, Word) { - let Self { - op, - ra, - rb, - rc, - rd, - imm06, - imm12, - imm18, - imm24, - } = self; - - let repr = OpcodeRepr::from_u8(op); - - let _ = imm06; - let imm12 = imm12 as Word; - let imm18 = imm18 as Word; - let imm24 = imm24 as Word; - - let imm12_mask = (op & 0xf0 == 0x50) || (op & 0xf0 == 0x60); - let imm18_mask = (op & 0xf0 == 0x70) || (op & 0xf0 == 0x80); - let imm24_mask = (op & 0xf0 == 0x90) || (op & 0xf0 == 0xa0); - - let imm12_mask = imm12_mask as Word; - let imm18_mask = imm18_mask as Word; - let imm24_mask = imm24_mask as Word; - - let imm = imm12 * imm12_mask + imm18 * imm18_mask + imm24 * imm24_mask; - - (repr, ra, rb, rc, rd, imm) - } -} - -impl From for Instruction { - fn from(instruction: u32) -> Self { - Self::new(instruction) - } -} - -impl From<[u8; Instruction::LEN]> for Instruction { - fn from(instruction: [u8; Instruction::LEN]) -> Self { - u32::from_be_bytes(instruction).into() - } -} - -impl From for Instruction { - fn from(op: Opcode) -> Self { - u32::from(op).into() - } -} - -impl From for u32 { - fn from(parsed: Instruction) -> u32 { - // Convert all fields to u32 with correct shifting to just OR together - // This truncates the field if they are too large - - let a = (parsed.ra as u32) << 18; - let b = (parsed.rb as u32) << 12; - let c = (parsed.rc as u32) << 6; - let d = parsed.rd as u32; - - let imm12 = parsed.imm12 as u32; - let imm18 = parsed.imm18; - let imm24 = parsed.imm24; - - let repr = OpcodeRepr::from_u8(parsed.op); - - let args = match repr { - OpcodeRepr::ADD - | OpcodeRepr::AND - | OpcodeRepr::DIV - | OpcodeRepr::EQ - | OpcodeRepr::EXP - | OpcodeRepr::GT - | OpcodeRepr::LT - | OpcodeRepr::MLOG - | OpcodeRepr::MROO - | OpcodeRepr::MOD - | OpcodeRepr::MUL - | OpcodeRepr::OR - | OpcodeRepr::SLL - | OpcodeRepr::SRL - | OpcodeRepr::SUB - | OpcodeRepr::XOR - | OpcodeRepr::MCP - | OpcodeRepr::LDC - | OpcodeRepr::TR - | OpcodeRepr::ECR - | OpcodeRepr::JNE - | OpcodeRepr::K256 - | OpcodeRepr::S256 - | OpcodeRepr::SCWQ - | OpcodeRepr::SRW - | OpcodeRepr::SWW => a | b | c, - - OpcodeRepr::ADDI - | OpcodeRepr::ANDI - | OpcodeRepr::DIVI - | OpcodeRepr::EXPI - | OpcodeRepr::MODI - | OpcodeRepr::MULI - | OpcodeRepr::ORI - | OpcodeRepr::SLLI - | OpcodeRepr::SRLI - | OpcodeRepr::SUBI - | OpcodeRepr::XORI - | OpcodeRepr::JNEI - | OpcodeRepr::LB - | OpcodeRepr::LW - | OpcodeRepr::SB - | OpcodeRepr::SW - | OpcodeRepr::GTF => a | b | imm12, - - OpcodeRepr::MOVE - | OpcodeRepr::NOT - | OpcodeRepr::RETD - | OpcodeRepr::MCL - | OpcodeRepr::BHSH - | OpcodeRepr::CROO - | OpcodeRepr::CSIZ - | OpcodeRepr::TIME => a | b, - - OpcodeRepr::RET - | OpcodeRepr::ALOC - | OpcodeRepr::BHEI - | OpcodeRepr::BURN - | OpcodeRepr::CB - | OpcodeRepr::MINT - | OpcodeRepr::JMP - | OpcodeRepr::RVRT - | OpcodeRepr::FLAG => a, - - OpcodeRepr::JI | OpcodeRepr::CFEI | OpcodeRepr::CFSI => imm24, - - OpcodeRepr::MCLI | OpcodeRepr::GM | OpcodeRepr::JNZI | OpcodeRepr::MOVI => a | imm18, - - OpcodeRepr::MEQ - | OpcodeRepr::CALL - | OpcodeRepr::CCP - | OpcodeRepr::LOG - | OpcodeRepr::LOGD - | OpcodeRepr::SMO - | OpcodeRepr::TRO - | OpcodeRepr::SRWQ - | OpcodeRepr::SWWQ => a | b | c | d, - - _ => 0, - }; - - ((parsed.op as u32) << 24) | args - } -} - -#[cfg(feature = "std")] -impl Instruction { - /// Create a `Instruction` from a slice of bytes - /// - /// This function will fail if the length of the bytes is smaller than - /// [`Instruction::LEN`]. - pub fn from_bytes(bytes: &[u8]) -> io::Result { - if bytes.len() < Self::LEN { - Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "The provided buffer is not big enough!", - )) - } else { - // Safety: we check the length above - unsafe { Ok(Self::from_slice_unchecked(bytes)) } - } - } - - /// Create a set of `Instruction` from an iterator of bytes - /// - /// If not padded to [`Self::LEN`], will consume the unaligned bytes but won't try to parse an - /// instruction from them. - pub fn from_bytes_iter(bytes: I) -> Vec - where - I: IntoIterator, - { - let mut bytes = bytes.into_iter(); - let mut buf = [0u8; Self::LEN]; - let mut ret = Vec::with_capacity(bytes.size_hint().0 / Self::LEN); - - loop { - let n = bytes - .by_ref() - .take(Self::LEN) - .zip(buf.as_mut().iter_mut()) - .fold(0, |n, (x, b)| { - *b = x; - - n + 1 - }); - - if n < Self::LEN { - break; - } - - ret.push(Self::from(buf)); - } - - ret - } -} - -#[cfg(feature = "std")] -impl iter::FromIterator for Vec { - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - iter.into_iter().flat_map(Instruction::to_bytes).collect() - } -} - -#[cfg(feature = "std")] -impl iter::FromIterator for Vec { - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - iter.into_iter().map(Instruction::from).collect() - } -} diff --git a/fuel-asm/src/instruction_result.rs b/fuel-asm/src/instruction_result.rs new file mode 100644 index 0000000000..58dc9c5775 --- /dev/null +++ b/fuel-asm/src/instruction_result.rs @@ -0,0 +1,60 @@ +use crate::{Instruction, PanicReason, RawInstruction, Word}; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] +/// Describe a panic reason with the instruction that generated it +pub struct InstructionResult { + reason: PanicReason, + instruction: RawInstruction, +} + +impl InstructionResult { + /// Represents an error described by a reason and an instruction. + pub const fn error(reason: PanicReason, instruction: RawInstruction) -> Self { + Self { reason, instruction } + } + + /// Underlying panic reason + pub const fn reason(&self) -> &PanicReason { + &self.reason + } + + /// Underlying instruction + pub const fn instruction(&self) -> &RawInstruction { + &self.instruction + } + + /// This result represents success? + pub const fn is_success(&self) -> bool { + matches!(self.reason, PanicReason::Success) + } + + /// This result represents error? + pub const fn is_error(&self) -> bool { + !self.is_success() + } +} + +const WORD_SIZE: usize = core::mem::size_of::(); +const REASON_OFFSET: Word = (WORD_SIZE * 8 - 8) as Word; +const INSTR_OFFSET: Word = REASON_OFFSET - (Instruction::SIZE * 8) as Word; + +impl From for Word { + fn from(r: InstructionResult) -> Word { + let reason = Word::from(r.reason as u8); + let instruction = Word::from(r.instruction); + (reason << REASON_OFFSET) | (instruction << INSTR_OFFSET) + } +} + +impl From for InstructionResult { + fn from(val: Word) -> Self { + // Safe to cast as we've shifted the 8 MSB. + let reason_u8 = (val >> REASON_OFFSET) as u8; + // Cast to truncate in order to remove the `reason` bits. + let instruction = (val >> INSTR_OFFSET) as u32; + let reason = PanicReason::from(reason_u8); + Self { reason, instruction } + } +} diff --git a/fuel-asm/src/lib.rs b/fuel-asm/src/lib.rs index 891fec3a1e..c4a7dc3433 100644 --- a/fuel-asm/src/lib.rs +++ b/fuel-asm/src/lib.rs @@ -1,26 +1,646 @@ -//! FuelVM opcodes representation +//! FuelVM instruction and opcodes representation. #![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(not(feature = "std"), no_std)] #![cfg_attr(feature = "std", doc = include_str!("../README.md"))] #![warn(missing_docs)] -mod instruction; -mod opcode; +mod args; +mod instruction_result; +// This is `pub` to make documentation for the private `impl_instructions!` macro more accessible. +#[macro_use] +pub mod macros; +pub mod op; +mod pack; mod panic_reason; - -mod macros; +mod unpack; #[doc(no_inline)] -pub use fuel_types::{self, Immediate06, Immediate12, Immediate18, Immediate24, RegisterId, Word}; -pub use instruction::Instruction; -pub use opcode::{GMArgs, GTFArgs, Opcode, OpcodeRepr}; -pub use panic_reason::{InstructionResult, PanicReason}; +pub use args::{GMArgs, GTFArgs}; +pub use fuel_types::{RegisterId, Word}; +pub use instruction_result::InstructionResult; +pub use panic_reason::PanicReason; -#[cfg(feature = "serde")] -#[doc(no_inline)] -pub use serde; +/// Represents a 6-bit register ID, guaranteed to be masked by construction. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct RegId(u8); -#[cfg(feature = "arbitrary")] -#[doc(no_inline)] -pub use arbitrary; +/// Represents a 12-bit immediate value, guaranteed to be masked by construction. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Imm12(u16); + +/// Represents a 18-bit immediate value, guaranteed to be masked by construction. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Imm18(u32); + +/// Represents a 24-bit immediate value, guaranteed to be masked by construction. +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub struct Imm24(u32); + +/// An instruction in its raw, packed, unparsed representation. +pub type RawInstruction = u32; + +/// Failed to parse a `u8` as a valid or non-reserved opcode. +#[derive(Debug, Eq, PartialEq)] +pub struct InvalidOpcode; + +/// Type is convertible to a [`RegId`] +pub trait CheckRegId { + /// Convert to a [`RegId`], or panic + fn check(self) -> RegId; +} + +impl CheckRegId for RegId { + fn check(self) -> RegId { + self + } +} + +impl CheckRegId for u8 { + fn check(self) -> RegId { + RegId::new_checked(self).expect("CheckRegId was given invalid RegId") + } +} + +// Defines the `Instruction` and `Opcode` types, along with an `op` module declaring a unique type +// for each opcode's instruction variant. For a detailed explanation of how this works, see the +// `fuel_asm::macros` module level documentation. +impl_instructions! { + "Adds two registers." + 0x10 ADD add [RegId RegId RegId] + "Bitwise ANDs two registers." + 0x11 AND and [RegId RegId RegId] + "Divides two registers." + 0x12 DIV div [RegId RegId RegId] + "Compares two registers for equality." + 0x13 EQ eq [RegId RegId RegId] + "Raises one register to the power of another." + 0x14 EXP exp [RegId RegId RegId] + "Compares two registers for greater-than." + 0x15 GT gt [RegId RegId RegId] + "Compares two registers for less-than." + 0x16 LT lt [RegId RegId RegId] + "The integer logarithm of a register." + 0x17 MLOG mlog [RegId RegId RegId] + "The integer root of a register." + 0x18 MROO mroo [RegId RegId RegId] + "Modulo remainder of two registers." + 0x19 MOD mod_ [RegId RegId RegId] + "Copy from one register to another." + 0x1A MOVE move_ [RegId RegId] + "Multiplies two registers." + 0x1B MUL mul [RegId RegId RegId] + "Bitwise NOT a register." + 0x1C NOT not [RegId RegId] + "Bitwise ORs two registers." + 0x1D OR or [RegId RegId RegId] + "Left shifts a register by a register." + 0x1E SLL sll [RegId RegId RegId] + "Right shifts a register by a register." + 0x1F SRL srl [RegId RegId RegId] + "Subtracts two registers." + 0x20 SUB sub [RegId RegId RegId] + "Bitwise XORs two registers." + 0x21 XOR xor [RegId RegId RegId] + + "Return from context." + 0x24 RET ret [RegId] + "Return from context with data." + 0x25 RETD retd [RegId RegId] + "Allocate a number of bytes from the heap." + 0x26 ALOC aloc [RegId] + "Clear a variable number of bytes in memory." + 0x27 MCL mcl [RegId RegId] + "Copy a variable number of bytes in memory." + 0x28 MCP mcp [RegId RegId RegId] + "Compare bytes in memory." + 0x29 MEQ meq [RegId RegId RegId RegId] + "Get block header hash for height." + 0x2A BHSH bhsh [RegId RegId] + "Get current block height." + 0x2B BHEI bhei [RegId] + "Burn coins of the current contract's asset ID." + 0x2C BURN burn [RegId] + "Call a contract." + 0x2D CALL call [RegId RegId RegId RegId] + "Copy contract code for a contract." + 0x2E CCP ccp [RegId RegId RegId RegId] + "Get code root of a contract." + 0x2F CROO croo [RegId RegId] + "Get code size of a contract." + 0x30 CSIZ csiz [RegId RegId] + "Get current block proposer's address." + 0x31 CB cb [RegId] + "Load a contract's code as executable." + 0x32 LDC ldc [RegId RegId RegId] + "Log an event." + 0x33 LOG log [RegId RegId RegId RegId] + "Log data." + 0x34 LOGD logd [RegId RegId RegId RegId] + "Mint coins of the current contract's asset ID." + 0x35 MINT mint [RegId] + "Halt execution, reverting state changes and returning a value." + 0x36 RVRT rvrt [RegId] + "Clear a series of slots from contract storage." + 0x37 SCWQ scwq [RegId RegId RegId] + "Load a word from contract storage." + 0x38 SRW srw [RegId RegId RegId] + "Load a series of 32 byte slots from contract storage." + 0x39 SRWQ srwq [RegId RegId RegId RegId] + "Store a word in contract storage." + 0x3A SWW sww [RegId RegId RegId] + "Store a series of 32 byte slots in contract storage." + 0x3B SWWQ swwq [RegId RegId RegId RegId] + "Transfer coins to a contract unconditionally." + 0x3C TR tr [RegId RegId RegId] + "Transfer coins to a variable output." + 0x3D TRO tro [RegId RegId RegId RegId] + "The 64-byte public key (x, y) recovered from 64-byte signature on 32-byte message." + 0x3E ECR ecr [RegId RegId RegId] + "The keccak-256 hash of a slice." + 0x3F K256 k256 [RegId RegId RegId] + "The SHA-2-256 hash of a slice." + 0x40 S256 s256 [RegId RegId RegId] + "Get timestamp of block at given height." + 0x41 TIME time [RegId RegId] + + "Performs no operation." + 0x47 NOOP noop [] + "Set flag register to a register." + 0x48 FLAG flag [RegId] + "Get the balance of contract of an asset ID." + 0x49 BAL bal [RegId RegId RegId] + "Dynamic jump." + 0x4A JMP jmp [RegId] + "Conditional dynamic jump." + 0x4B JNE jne [RegId RegId RegId] + "Send a message to recipient address with call abi, coins, and output." + 0x4C SMO smo [RegId RegId RegId RegId] + + "Adds a register and an immediate value." + 0x50 ADDI addi [RegId RegId Imm12] + "Bitwise ANDs a register and an immediate value." + 0x51 ANDI andi [RegId RegId Imm12] + "Divides a register and an immediate value." + 0x52 DIVI divi [RegId RegId Imm12] + "Raises one register to the power of an immediate value." + 0x53 EXPI expi [RegId RegId Imm12] + "Modulo remainder of a register and an immediate value." + 0x54 MODI modi [RegId RegId Imm12] + "Multiplies a register and an immediate value." + 0x55 MULI muli [RegId RegId Imm12] + "Bitwise ORs a register and an immediate value." + 0x56 ORI ori [RegId RegId Imm12] + "Left shifts a register by an immediate value." + 0x57 SLLI slli [RegId RegId Imm12] + "Right shifts a register by an immediate value." + 0x58 SRLI srli [RegId RegId Imm12] + "Subtracts a register and an immediate value." + 0x59 SUBI subi [RegId RegId Imm12] + "Bitwise XORs a register and an immediate value." + 0x5A XORI xori [RegId RegId Imm12] + "Conditional jump." + 0x5B JNEI jnei [RegId RegId Imm12] + "A byte is loaded from the specified address offset by an immediate value." + 0x5C LB lb [RegId RegId Imm12] + "A word is loaded from the specified address offset by an immediate value." + 0x5D LW lw [RegId RegId Imm12] + "Write the least significant byte of a register to memory." + 0x5E SB sb [RegId RegId Imm12] + "Write a register to memory." + 0x5F SW sw [RegId RegId Imm12] + "Copy an immediate number of bytes in memory." + 0x60 MCPI mcpi [RegId RegId Imm12] + "Get transaction fields." + 0x61 GTF gtf [RegId RegId Imm12] + + "Clear an immediate number of bytes in memory." + 0x70 MCLI mcli [RegId Imm18] + "Get metadata from memory." + 0x71 GM gm [RegId Imm18] + "Copy immediate value into a register" + 0x72 MOVI movi [RegId Imm18] + "Conditional jump against zero." + 0x73 JNZI jnzi [RegId Imm18] + + "Jump." + 0x90 JI ji [Imm24] + "Extend the current call frame's stack by an immediate value." + 0x91 CFEI cfei [Imm24] + "Shrink the current call frame's stack by an immediate value." + 0x92 CFSI cfsi [Imm24] +} + +impl Instruction { + /// Size of an instruction in bytes + pub const SIZE: usize = core::mem::size_of::(); + + /// Convenience method for converting to bytes + pub fn to_bytes(self) -> [u8; 4] { + self.into() + } +} + +impl RegId { + /// Contains zero (0), for convenience. + pub const ZERO: Self = Self(0x00); + /// Contains one (1), for convenience. + pub const ONE: Self = Self(0x01); + /// Contains overflow/underflow of addition, subtraction, and multiplication. + pub const OF: Self = Self(0x02); + /// The program counter. Memory address of the current instruction. + pub const PC: Self = Self(0x03); + /// Stack start pointer. Memory address of bottom of current writable stack area. + pub const SSP: Self = Self(0x04); + /// Stack pointer. Memory address on top of current writable stack area (points to free memory). + pub const SP: Self = Self(0x05); + /// Frame pointer. Memory address of beginning of current call frame. + pub const FP: Self = Self(0x06); + /// Heap pointer. Memory address below the current bottom of the heap (points to free memory). + pub const HP: Self = Self(0x07); + /// Error codes for particular operations. + pub const ERR: Self = Self(0x08); + /// Remaining gas globally. + pub const GGAS: Self = Self(0x09); + /// Remaining gas in the context. + pub const CGAS: Self = Self(0x0A); + /// Received balance for this context. + pub const BAL: Self = Self(0x0B); + /// Instructions start. Pointer to the start of the currently-executing code. + pub const IS: Self = Self(0x0C); + /// Return value or pointer. + pub const RET: Self = Self(0x0D); + /// Return value length in bytes. + pub const RETL: Self = Self(0x0E); + /// Flags register. + pub const FLAG: Self = Self(0x0F); + /// Smallest writable register. + pub const WRITABLE: Self = Self(0x10); + + /// Construct a register ID from the given value. + /// + /// The given value will be masked to 6 bits. + pub const fn new(u: u8) -> Self { + Self(u & 0b_0011_1111) + } + + /// Construct a register ID from the given value. + /// + /// Returns `None` if the value is outside the 6-bit value range. + pub fn new_checked(u: u8) -> Option { + let r = Self::new(u); + (r.0 == u).then_some(r) + } + + /// A const alternative to the `Into` implementation. + pub const fn to_u8(self) -> u8 { + self.0 + } +} + +impl Imm12 { + /// Construct an immediate value. + /// + /// The given value will be masked to 12 bits. + pub const fn new(u: u16) -> Self { + Self(u & 0b_0000_1111_1111_1111) + } + + /// Construct an immediate value. + /// + /// Returns `None` if the value is outside the 12-bit value range. + pub fn new_checked(u: u16) -> Option { + let imm = Self::new(u); + (imm.0 == u).then_some(imm) + } + + /// A const alternative to the `Into` implementation. + pub const fn to_u16(self) -> u16 { + self.0 + } +} + +impl Imm18 { + /// Construct an immediate value. + /// + /// The given value will be masked to 18 bits. + pub const fn new(u: u32) -> Self { + Self(u & 0b_0000_0000_0000_0011_1111_1111_1111_1111) + } + + /// Construct an immediate value. + /// + /// Returns `None` if the value is outside the 18-bit value range. + pub fn new_checked(u: u32) -> Option { + let imm = Self::new(u); + (imm.0 == u).then_some(imm) + } + + /// A const alternative to the `Into` implementation. + pub const fn to_u32(self) -> u32 { + self.0 + } +} + +impl Imm24 { + /// Construct an immediate value. + /// + /// The given value will be masked to 24 bits. + pub const fn new(u: u32) -> Self { + Self(u & 0b_0000_0000_1111_1111_1111_1111_1111_1111) + } + + /// Construct an immediate value. + /// + /// Returns `None` if the value is outside the 24-bit value range. + pub fn new_checked(u: u32) -> Option { + let imm = Self::new(u); + (imm.0 == u).then_some(imm) + } + + /// A const alternative to the `Into` implementation. + pub const fn to_u32(self) -> u32 { + self.0 + } +} + +impl Opcode { + /// Check if the opcode is allowed for predicates. + /// + /// + /// + #[allow(clippy::match_like_matches_macro)] + pub fn is_predicate_allowed(&self) -> bool { + use Opcode::*; + match self { + ADD | AND | DIV | EQ | EXP | GT | LT | MLOG | MROO | MOD | MOVE | MUL | NOT | OR | SLL | SRL | SUB + | XOR | RET | ALOC | MCL | MCP | MEQ | ECR | K256 | S256 | NOOP | FLAG | ADDI | ANDI | DIVI | EXPI + | MODI | MULI | ORI | SLLI | SRLI | SUBI | XORI | JNEI | LB | LW | SB | SW | MCPI | MCLI | GM | MOVI + | JNZI | JI | JMP | JNE | CFEI | CFSI | GTF => true, + _ => false, + } + } +} + +// Direct conversions + +impl From for RegId { + fn from(u: u8) -> Self { + RegId::new(u) + } +} + +impl From for Imm12 { + fn from(u: u16) -> Self { + Imm12::new(u) + } +} + +impl From for Imm18 { + fn from(u: u32) -> Self { + Imm18::new(u) + } +} + +impl From for Imm24 { + fn from(u: u32) -> Self { + Imm24::new(u) + } +} + +impl From for u8 { + fn from(RegId(u): RegId) -> Self { + u + } +} + +impl From for u16 { + fn from(Imm12(u): Imm12) -> Self { + u + } +} + +impl From for u32 { + fn from(Imm18(u): Imm18) -> Self { + u + } +} + +impl From for u32 { + fn from(Imm24(u): Imm24) -> Self { + u + } +} + +// Lossless, convenience conversions + +impl From for usize { + fn from(r: RegId) -> usize { + u8::from(r).into() + } +} + +impl From for u32 { + fn from(imm: Imm12) -> Self { + u16::from(imm).into() + } +} + +impl From for u64 { + fn from(imm: Imm12) -> Self { + u16::from(imm).into() + } +} + +impl From for u128 { + fn from(imm: Imm12) -> Self { + u16::from(imm).into() + } +} + +impl From for u64 { + fn from(imm: Imm18) -> Self { + u32::from(imm).into() + } +} + +impl From for u128 { + fn from(imm: Imm18) -> Self { + u32::from(imm).into() + } +} + +impl From for u64 { + fn from(imm: Imm24) -> Self { + u32::from(imm).into() + } +} + +impl From for u128 { + fn from(imm: Imm24) -> Self { + u32::from(imm).into() + } +} + +impl From for u8 { + fn from(op: Opcode) -> Self { + op as u8 + } +} + +impl From for RawInstruction { + fn from(inst: Instruction) -> Self { + RawInstruction::from_be_bytes(inst.into()) + } +} + +impl core::convert::TryFrom for Instruction { + type Error = InvalidOpcode; + fn try_from(u: RawInstruction) -> Result { + Self::try_from(u.to_be_bytes()) + } +} + +// Index slices with `RegId` + +impl core::ops::Index for [T] +where + [T]: core::ops::Index, +{ + type Output = T; + fn index(&self, ix: RegId) -> &Self::Output { + &self[usize::from(ix)] + } +} + +impl core::ops::IndexMut for [T] +where + [T]: core::ops::IndexMut, +{ + fn index_mut(&mut self, ix: RegId) -> &mut Self::Output { + &mut self[usize::from(ix)] + } +} + +// Collect instructions into bytes or halfwords + +#[cfg(feature = "std")] +impl core::iter::FromIterator for Vec { + fn from_iter>(iter: I) -> Self { + iter.into_iter().flat_map(Instruction::to_bytes).collect() + } +} + +#[cfg(feature = "std")] +impl core::iter::FromIterator for Vec { + fn from_iter>(iter: I) -> Self { + iter.into_iter().map(u32::from).collect() + } +} + +/// Produce two raw instructions from a word's hi and lo parts. +pub fn raw_instructions_from_word(word: Word) -> [RawInstruction; 2] { + let hi = (word >> 32) as RawInstruction; + let lo = word as RawInstruction; + [hi, lo] +} + +/// Given an iterator yielding bytes, produces an iterator yielding `Instruction`s. +/// +/// This function assumes each consecutive 4 bytes aligns with an instruction. +/// +/// The produced iterator yields an `Err` in the case that an instruction fails to parse from 4 +/// consecutive bytes. +pub fn from_bytes(bs: I) -> impl Iterator> +where + I: IntoIterator, +{ + let mut iter = bs.into_iter(); + core::iter::from_fn(move || { + let a = iter.next()?; + let b = iter.next()?; + let c = iter.next()?; + let d = iter.next()?; + Some(Instruction::try_from([a, b, c, d])) + }) +} + +/// Given an iterator yielding u32s (i.e. "half words" or "raw instructions"), produces an iterator +/// yielding `Instruction`s. +/// +/// This function assumes each consecutive 4 bytes aligns with an instruction. +/// +/// The produced iterator yields an `Err` in the case that an instruction fails to parse. +pub fn from_u32s(us: I) -> impl Iterator> +where + I: IntoIterator, +{ + us.into_iter().map(Instruction::try_from) +} + +// Short-hand, `panic!`ing constructors for the short-hand instruction construtors (e.g op::add). + +fn check_imm12(u: u16) -> Imm12 { + Imm12::new_checked(u).unwrap_or_else(|| panic!("Value `{u}` out of range for 12-bit immediate")) +} + +fn check_imm18(u: u32) -> Imm18 { + Imm18::new_checked(u).unwrap_or_else(|| panic!("Value `{u}` out of range for 18-bit immediate")) +} + +fn check_imm24(u: u32) -> Imm24 { + Imm24::new_checked(u).unwrap_or_else(|| panic!("Value `{u}` out of range for 24-bit immediate")) +} + +// -------------------------------------------------------- + +// The size of the instruction isn't larger than necessary. +// 1 byte for the opcode, 3 bytes for registers and immediates. +#[test] +fn test_instruction_size() { + // NOTE: Throughout `fuel-vm`, we use the `Instruction::SIZE` associated + // const to refer to offsets within raw instruction data. As a result, it's + // *essential* that this equivalence remains the same. If you've added + // a new field or changed the size of `Instruction` somehow and have + // arrived at this assertion, ensure that you also revisit all sites where + // `Instruction::SIZE` is used and make sure we're using the right value + // (in most cases, the right value is `core::mem::size_of::()`). + assert_eq!( + core::mem::size_of::(), + core::mem::size_of::() + ); + + assert_eq!(core::mem::size_of::(), Instruction::SIZE); +} + +// The size of the opcode is exactly one byte. +#[test] +fn test_opcode_size() { + assert_eq!(core::mem::size_of::(), 1); +} + +#[test] +#[allow(clippy::match_like_matches_macro)] +fn check_predicate_allowed() { + use Opcode::*; + for byte in 0..u8::MAX { + if let Ok(repr) = Opcode::try_from(byte) { + let should_allow = match repr { + BAL | BHEI | BHSH | BURN | CALL | CB | CCP | CROO | CSIZ | LDC | LOG | LOGD | MINT | RETD | RVRT + | SMO | SCWQ | SRW | SRWQ | SWW | SWWQ | TIME | TR | TRO => false, + _ => true, + }; + assert_eq!(should_allow, repr.is_predicate_allowed()); + } + } +} + +// Test roundtrip conversion for all valid opcodes. +#[test] +fn test_opcode_u8_conv() { + for u in 0..=u8::MAX { + if let Ok(op) = Opcode::try_from(u) { + assert_eq!(op as u8, u); + } + } +} diff --git a/fuel-asm/src/macros.rs b/fuel-asm/src/macros.rs index 59d7cb38cf..17cfbdd2ec 100644 --- a/fuel-asm/src/macros.rs +++ b/fuel-asm/src/macros.rs @@ -1,42 +1,678 @@ -use crate::{OpcodeRepr, PanicReason}; +//! # The `impl_instructions!` macro +//! +//! The heart of this crate's implementation is the private `impl_instructions!` macro. This macro +//! is used to generate the `Instruction` and `Opcode` types along with their implementations. +//! +//! The intention is to allow for having a single source of truth from which each of the +//! instruction-related types and implementations are derived. +//! +//! Its usage looks like this: +//! +//! ```rust,ignore +//! impl_instructions! { +//! "Adds two registers." +//! 0x10 ADD add [RegId RegId RegId] +//! "Bitwise ANDs two registers." +//! 0x11 AND and [RegId RegId RegId] +//! // ... +//! } +//! ``` +//! +//! Each instruction's row includes: +//! +//! - A short docstring. +//! - The Opcode byte value. +//! - An uppercase identifier (for generating variants and types). +//! - A lowercase identifier (for generating the shorthand instruction constructor). +//! - The instruction layout (for the `new` and `unpack` functions). +//! +//! The following sections describe each of the items that are derived from the +//! `impl_instructions!` table in more detail. +//! +//! ## The `Opcode` enum +//! +//! Represents the bytecode portion of an instruction. +//! +//! ```rust,ignore +//! /// Solely the opcode portion of an instruction represented as a single byte. +//! #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] +//! #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +//! #[repr(u8)] +//! pub enum Opcode { +//! /// Adds two registers. +//! ADD = 0x10, +//! /// Bitwise ANDs two registers. +//! AND = 0x11, +//! // ... +//! } +//! ``` +//! +//! A `TryFrom` implementation is also provided, producing an `Err(InvalidOpcode)` in the case +//! that the byte represents a reserved or undefined value. +//! +//! ```rust +//! # use fuel_asm::{InvalidOpcode, Opcode}; +//! assert_eq!(Opcode::try_from(0x10), Ok(Opcode::ADD)); +//! assert_eq!(Opcode::try_from(0x11), Ok(Opcode::AND)); +//! assert_eq!(Opcode::try_from(0), Err(InvalidOpcode)); +//! ``` +//! +//! ## The `Instruction` enum +//! +//! Represents a single, full instruction, discriminated by its `Opcode`. +//! +//! ```rust,ignore +//! /// Representation of a single instruction for the interpreter. +//! /// +//! /// The opcode is represented in the tag (variant), or may be retrieved in the form of an +//! /// `Opcode` byte using the `opcode` method. +//! /// +//! /// The register and immediate data associated with the instruction is represented within +//! /// an inner unit type wrapper around the 3 remaining bytes. +//! #[derive(Clone, Copy, Eq, Hash, PartialEq)] +//! #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +//! pub enum Instruction { +//! /// Adds two registers. +//! ADD(op::ADD), +//! /// Bitwise ANDs two registers. +//! AND(op::AND), +//! // ... +//! } +//! ``` +//! +//! The `From for u32` (aka `RawInstruction`) and `TryFrom for Instruction` +//! implementations can be found in the crate root. +//! +//! ## A unique unit type per operation +//! +//! In order to reduce the likelihood of misusing unrelated register IDs or immediate values, we +//! generate a unique unit type for each type of operation (i.e instruction variant) and guard +//! access to the relevant register IDs and immediate values behind each type's unique methods. +//! +//! These unique operation types are generated as follows within a dedicated `op` module: +//! +//! ```rust,ignore +//! pub mod op { +//! //! Definitions and implementations for each unique instruction type, one for each +//! //! unique `Opcode` variant. +//! +//! // A unique type for each operation. +//! +//! /// Adds two registers. +//! pub struct ADD([u8; 3]); +//! +//! /// Bitwise ANDs two registers. +//! pub struct AND([u8; 3]); +//! +//! // ... +//! +//! // An implementation for each unique type. +//! +//! impl ADD { +//! pub const OPCODE: Opcode = Opcode::ADD; +//! +//! /// Construct the instruction from its parts. +//! pub fn new(ra: RegId, rb: RegId, rc: RegId) -> Self { +//! Self(pack::bytes_from_ra_rb_rc(ra, rb, rc)) +//! } +//! +//! /// Convert the instruction into its parts. +//! pub fn unpack(self) -> (RegId, RegId, RegId) { +//! unpack::ra_rb_rc_from_bytes(self.0) +//! } +//! } +//! +//! impl AND { +//! // ... +//! } +//! +//! // ... +//! +//! // A short-hand `Instruction` constructor for each operation to make it easier to +//! // hand-write assembly for tests and benchmarking. As these constructors are public and +//! // accept literal values, we check that the values are within range. +//! +//! /// Adds two registers. +//! pub fn add(ra: u8, rb: u8, rc: u8) -> Instruction { +//! ADD::new(check_reg_id(ra), check_reg_id(rb), check_reg_id(rc)).into() +//! } +//! +//! /// Bitwise ANDs two registers. +//! pub fn and(ra: u8, rb: u8, rc: u8) -> Instruction { +//! AND::new(check_reg_id(ra), check_reg_id(rb), check_reg_id(rc)).into() +//! } +//! +//! // ... +//! }; +//! ``` +//! +//! ### Instruction Layout +//! +//! The function signatures of the `new` and `unpack` functions are derived from the instruction's +//! data layout described in the `impl_instructions!` table. +//! +//! For example, the `unpack` method for `ADD` looks like this: +//! +//! ```rust,ignore +//! // 0x10 ADD add [RegId RegId RegId] +//! pub fn unpack(self) -> (RegId, RegId, RegId) +//! ``` +//! +//! While the `unpack` method for `ADDI` looks like this: +//! +//! ```rust,ignore +//! // 0x50 ADDI addi [RegId RegId Imm12] +//! pub fn unpack(self) -> (RegId, RegId, Imm12) +//! ``` +//! +//! ### Shorthand Constructors +//! +//! The shorthand instruction constructors (e.g. `add`, `and`, etc) are specifically designed to +//! make it easier to handwrite assembly for tests or benchmarking. Unlike the `$OP::new` +//! constructors which require typed register ID or immediate inputs, the instruction constructors +//! allow for constructing `Instruction`s from convenient literal value inputs. E.g. +//! +//! ```rust +//! use fuel_asm::{op, Instruction}; +//! +//! // A sample program to perform ecrecover +//! let program: Vec = vec![ +//! op::move_(0x10, 0x01), // set r[0x10] := $one +//! op::slli(0x20, 0x10, 5), // set r[0x20] := `r[0x10] << 5 == 32` +//! op::slli(0x21, 0x10, 6), // set r[0x21] := `r[0x10] << 6 == 64` +//! op::aloc(0x21), // alloc `r[0x21] == 64` to the heap +//! op::addi(0x10, 0x07, 1), // set r[0x10] := `$hp + 1` (allocated heap) +//! op::move_(0x11, 0x04), // set r[0x11] := $ssp +//! op::add(0x12, 0x04, 0x20), // set r[0x12] := `$ssp + r[0x20]` +//! op::ecr(0x10, 0x11, 0x12), // recover public key in memory[r[0x10], 64] +//! op::ret(0x01), // return `1` +//! ]; +//! ``` -macro_rules! from_u8 { - ($i:ident) => { - impl $i { - /// Const fn implementation of `From` - pub const fn from_u8(b: u8) -> Self { - // Currently, the language doesn't support customized type coercion - // - // Safety: all possible values of `b` are either allocated or reserved - unsafe { core::mem::transmute::(b) } +/// This macro is intentionaly private. See the module-level documentation for a thorough +/// explanation of how this macro works. +macro_rules! impl_instructions { + // Recursively declares a unique struct for each opcode. + (decl_op_struct $doc:literal $ix:literal $Op:ident $op:ident [$($field:ident)*] $($rest:tt)*) => { + #[doc = $doc] + #[derive(Clone, Copy, Eq, Hash, PartialEq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub struct $Op(pub (super) [u8; 3]); + impl_instructions!(decl_op_struct $($rest)*); + }; + (decl_op_struct) => {}; + + // Define the `Opcode` enum. + (decl_opcode_enum $($doc:literal $ix:literal $Op:ident $op:ident [$($field:ident)*])*) => { + /// Solely the opcode portion of an instruction represented as a single byte. + #[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + #[repr(u8)] + pub enum Opcode { + $( + #[doc = $doc] + $Op = $ix, + )* + } + }; + + // Define the `Instruction` enum. + (decl_instruction_enum $($doc:literal $ix:literal $Op:ident $op:ident [$($field:ident)*])*) => { + /// Representation of a single instruction for the interpreter. + /// + /// The opcode is represented in the tag (variant), or may be retrieved in the form of an + /// `Opcode` byte using the `opcode` method. + /// + /// The register and immediate data associated with the instruction is represented within + /// an inner unit type wrapper around the 3 remaining bytes. + #[derive(Clone, Copy, Eq, Hash, PartialEq)] + #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] + pub enum Instruction { + $( + #[doc = $doc] + $Op(op::$Op), + )* + } + }; + + // Generate a constructor based on the field layout. + (impl_op_new [RegId]) => { + /// Construct the instruction from its parts. + pub fn new(ra: RegId) -> Self { + Self(pack::bytes_from_ra(ra)) + } + }; + (impl_op_new [RegId RegId]) => { + /// Construct the instruction from its parts. + pub fn new(ra: RegId, rb: RegId) -> Self { + Self(pack::bytes_from_ra_rb(ra, rb)) + } + }; + (impl_op_new [RegId RegId RegId]) => { + /// Construct the instruction from its parts. + pub fn new(ra: RegId, rb: RegId, rc: RegId) -> Self { + Self(pack::bytes_from_ra_rb_rc(ra, rb, rc)) + } + }; + (impl_op_new [RegId RegId RegId RegId]) => { + /// Construct the instruction from its parts. + pub fn new(ra: RegId, rb: RegId, rc: RegId, rd: RegId) -> Self { + Self(pack::bytes_from_ra_rb_rc_rd(ra, rb, rc, rd)) + } + }; + (impl_op_new [RegId RegId Imm12]) => { + /// Construct the instruction from its parts. + pub fn new(ra: RegId, rb: RegId, imm: Imm12) -> Self { + Self(pack::bytes_from_ra_rb_imm12(ra, rb, imm)) + } + }; + (impl_op_new [RegId Imm18]) => { + /// Construct the instruction from its parts. + pub fn new(ra: RegId, imm: Imm18) -> Self { + Self(pack::bytes_from_ra_imm18(ra, imm)) + } + }; + (impl_op_new [Imm24]) => { + /// Construct the instruction from its parts. + pub fn new(imm: Imm24) -> Self { + Self(pack::bytes_from_imm24(imm)) + } + }; + (impl_op_new []) => { + /// Construct the instruction. + #[allow(clippy::new_without_default)] + pub fn new() -> Self { + Self([0; 3]) + } + }; + + // Generate an accessor method for each field. Recurse based on layout. + (impl_op_accessors [RegId]) => { + /// Access the ID for register A. + pub fn ra(&self) -> RegId { + unpack::ra_from_bytes(self.0) + } + }; + (impl_op_accessors [RegId RegId]) => { + impl_instructions!(impl_op_accessors [RegId]); + /// Access the ID for register B. + pub fn rb(&self) -> RegId { + unpack::rb_from_bytes(self.0) + } + }; + (impl_op_accessors [RegId RegId RegId]) => { + impl_instructions!(impl_op_accessors [RegId RegId]); + /// Access the ID for register C. + pub fn rc(&self) -> RegId { + unpack::rc_from_bytes(self.0) + } + }; + (impl_op_accessors [RegId RegId RegId RegId]) => { + impl_instructions!(impl_op_accessors [RegId RegId RegId]); + /// Access the ID for register D. + pub fn rd(&self) -> RegId { + unpack::rd_from_bytes(self.0) + } + }; + (impl_op_accessors [RegId RegId Imm12]) => { + impl_instructions!(impl_op_accessors [RegId RegId]); + /// Access the 12-bit immediate value. + pub fn imm12(&self) -> Imm12 { + unpack::imm12_from_bytes(self.0) + } + }; + (impl_op_accessors [RegId Imm18]) => { + impl_instructions!(impl_op_accessors [RegId]); + /// Access the 18-bit immediate value. + pub fn imm18(&self) -> Imm18 { + unpack::imm18_from_bytes(self.0) + } + }; + (impl_op_accessors [Imm24]) => { + /// Access the 24-bit immediate value. + pub fn imm24(&self) -> Imm24 { + unpack::imm24_from_bytes(self.0) + } + }; + (impl_op_accessors []) => {}; + + // Generate a method for converting the instruction into its parts. + (impl_op_unpack [RegId]) => { + /// Convert the instruction into its parts. + pub fn unpack(self) -> RegId { + unpack::ra_from_bytes(self.0) + } + }; + (impl_op_unpack [RegId RegId]) => { + /// Convert the instruction into its parts. + pub fn unpack(self) -> (RegId, RegId) { + unpack::ra_rb_from_bytes(self.0) + } + }; + (impl_op_unpack [RegId RegId RegId]) => { + /// Convert the instruction into its parts. + pub fn unpack(self) -> (RegId, RegId, RegId) { + unpack::ra_rb_rc_from_bytes(self.0) + } + }; + (impl_op_unpack [RegId RegId RegId RegId]) => { + /// Convert the instruction into its parts. + pub fn unpack(self) -> (RegId, RegId, RegId, RegId) { + unpack::ra_rb_rc_rd_from_bytes(self.0) + } + }; + (impl_op_unpack [RegId RegId Imm12]) => { + /// Convert the instruction into its parts. + pub fn unpack(self) -> (RegId, RegId, Imm12) { + unpack::ra_rb_imm12_from_bytes(self.0) + } + }; + (impl_op_unpack [RegId Imm18]) => { + /// Convert the instruction into its parts. + pub fn unpack(self) -> (RegId, Imm18) { + unpack::ra_imm18_from_bytes(self.0) + } + }; + (impl_op_unpack [Imm24]) => { + /// Convert the instruction into its parts. + pub fn unpack(self) -> Imm24 { + unpack::imm24_from_bytes(self.0) + } + }; + (impl_op_unpack []) => {}; + + // Generate a shorthand free function named after the $op for constructing an `Instruction`. + (impl_op_constructor $doc:literal $Op:ident $op:ident [RegId]) => { + #[doc = $doc] + pub fn $op(ra: A) -> Instruction { + $Op::new(ra.check()).into() + } + }; + (impl_op_constructor $doc:literal $Op:ident $op:ident [RegId RegId]) => { + #[doc = $doc] + pub fn $op(ra: A, rb: B) -> Instruction { + $Op::new(ra.check(), rb.check()).into() + } + }; + (impl_op_constructor $doc:literal $Op:ident $op:ident [RegId RegId RegId]) => { + #[doc = $doc] + pub fn $op(ra: A, rb: B, rc: C) -> Instruction { + $Op::new(ra.check(), rb.check(), rc.check()).into() + } + }; + (impl_op_constructor $doc:literal $Op:ident $op:ident [RegId RegId RegId RegId]) => { + #[doc = $doc] + pub fn $op(ra: A, rb: B, rc: C, rd: D) -> Instruction { + $Op::new(ra.check(), rb.check(), rc.check(), rd.check()).into() + } + }; + (impl_op_constructor $doc:literal $Op:ident $op:ident [RegId RegId Imm12]) => { + #[doc = $doc] + pub fn $op(ra: A, rb: B, imm: u16) -> Instruction { + $Op::new(ra.check(), rb.check(), check_imm12(imm)).into() + } + }; + (impl_op_constructor $doc:literal $Op:ident $op:ident [RegId Imm18]) => { + #[doc = $doc] + pub fn $op(ra: A, imm: u32) -> Instruction { + $Op::new(ra.check(), check_imm18(imm)).into() + } + }; + (impl_op_constructor $doc:literal $Op:ident $op:ident [Imm24]) => { + #[doc = $doc] + pub fn $op(imm: u32) -> Instruction { + $Op::new(check_imm24(imm)).into() + } + }; + (impl_op_constructor $doc:literal $Op:ident $op:ident []) => { + #[doc = $doc] + pub fn $op() -> Instruction { + $Op::new().into() + } + }; + + // Generate a private fn for use within the `Instruction::reg_ids` implementation. + (impl_op_reg_ids [RegId]) => { + pub(super) fn reg_ids(&self) -> [Option; 4] { + let ra = self.unpack(); + [Some(ra), None, None, None] + } + }; + (impl_op_reg_ids [RegId RegId]) => { + pub(super) fn reg_ids(&self) -> [Option; 4] { + let (ra, rb) = self.unpack(); + [Some(ra), Some(rb), None, None] + } + }; + (impl_op_reg_ids [RegId RegId RegId]) => { + pub(super) fn reg_ids(&self) -> [Option; 4] { + let (ra, rb, rc) = self.unpack(); + [Some(ra), Some(rb), Some(rc), None] + } + }; + (impl_op_reg_ids [RegId RegId RegId RegId]) => { + pub(super) fn reg_ids(&self) -> [Option; 4] { + let (ra, rb, rc, rd) = self.unpack(); + [Some(ra), Some(rb), Some(rc), Some(rd)] + } + }; + (impl_op_reg_ids [RegId RegId Imm12]) => { + pub(super) fn reg_ids(&self) -> [Option; 4] { + let (ra, rb, _) = self.unpack(); + [Some(ra), Some(rb), None, None] + } + }; + (impl_op_reg_ids [RegId Imm18]) => { + pub(super) fn reg_ids(&self) -> [Option; 4] { + let (ra, _) = self.unpack(); + [Some(ra), None, None, None] + } + }; + (impl_op_reg_ids [$($rest:tt)*]) => { + pub(super) fn reg_ids(&self) -> [Option; 4] { + [None; 4] + } + }; + + // Debug implementations for each instruction. + (impl_op_debug_fmt $Op:ident [RegId]) => { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let ra = self.unpack(); + f.debug_struct(stringify!($Op)) + .field("ra", &u8::from(ra)) + .finish() + } + }; + (impl_op_debug_fmt $Op:ident [RegId RegId]) => { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let (ra, rb) = self.unpack(); + f.debug_struct(stringify!($Op)) + .field("ra", &u8::from(ra)) + .field("rb", &u8::from(rb)) + .finish() + } + }; + (impl_op_debug_fmt $Op:ident [RegId RegId RegId]) => { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let (ra, rb, rc) = self.unpack(); + f.debug_struct(stringify!($Op)) + .field("ra", &u8::from(ra)) + .field("rb", &u8::from(rb)) + .field("rc", &u8::from(rc)) + .finish() + } + }; + (impl_op_debug_fmt $Op:ident [RegId RegId RegId RegId]) => { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let (ra, rb, rc, rd) = self.unpack(); + f.debug_struct(stringify!($Op)) + .field("ra", &u8::from(ra)) + .field("rb", &u8::from(rb)) + .field("rc", &u8::from(rc)) + .field("rd", &u8::from(rd)) + .finish() + } + }; + (impl_op_debug_fmt $Op:ident [RegId RegId Imm12]) => { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let (ra, rb, imm) = self.unpack(); + f.debug_struct(stringify!($Op)) + .field("ra", &u8::from(ra)) + .field("rb", &u8::from(rb)) + .field("imm", &u16::from(imm)) + .finish() + } + }; + (impl_op_debug_fmt $Op:ident [RegId Imm18]) => { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let (ra, imm) = self.unpack(); + f.debug_struct(stringify!($Op)) + .field("ra", &u8::from(ra)) + .field("imm", &u32::from(imm)) + .finish() + } + }; + (impl_op_debug_fmt $Op:ident [Imm24]) => { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + let imm = self.unpack(); + f.debug_struct(stringify!($Op)) + .field("imm", &u32::from(imm)) + .finish() + } + }; + (impl_op_debug_fmt $Op:ident []) => { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + f.debug_struct(stringify!($Op)) + .finish() + } + }; + + // Implement constructors and accessors for register and immediate values. + (impl_op $doc:literal $ix:literal $Op:ident $op:ident [$($field:ident)*] $($rest:tt)*) => { + impl $Op { + /// The associated 8-bit Opcode value. + pub const OPCODE: Opcode = Opcode::$Op; + + impl_instructions!(impl_op_new [$($field)*]); + impl_instructions!(impl_op_accessors [$($field)*]); + impl_instructions!(impl_op_unpack [$($field)*]); + impl_instructions!(impl_op_reg_ids [$($field)*]); + } + + impl_instructions!(impl_op_constructor $doc $Op $op [$($field)*]); + + impl From<$Op> for [u8; 3] { + fn from($Op(arr): $Op) -> Self { + arr } } - impl From for $i { - fn from(b: u8) -> Self { - Self::from_u8(b) + impl From<$Op> for [u8; 4] { + fn from($Op([a, b, c]): $Op) -> Self { + [$Op::OPCODE as u8, a, b, c] } } - impl From<$i> for u8 { - fn from(i: $i) -> u8 { - i as u8 + impl From<$Op> for u32 { + fn from(op: $Op) -> Self { + u32::from_be_bytes(op.into()) } } - impl From for $i { - fn from(w: fuel_types::Word) -> Self { - Self::from_u8(w as u8) + impl From<$Op> for Instruction { + fn from(op: $Op) -> Self { + Instruction::$Op(op) } } - impl From<$i> for fuel_types::Word { - fn from(i: $i) -> fuel_types::Word { - (i as u8) as fuel_types::Word + impl core::fmt::Debug for $Op { + impl_instructions!(impl_op_debug_fmt $Op [$($field)*]); + } + + impl_instructions!(impl_op $($rest)*); + }; + (impl_op) => {}; + + // Implement `TryFrom` for `Opcode`. + (impl_opcode $($doc:literal $ix:literal $Op:ident $op:ident [$($field:ident)*])*) => { + impl core::convert::TryFrom for Opcode { + type Error = InvalidOpcode; + fn try_from(u: u8) -> Result { + match u { + $( + $ix => Ok(Opcode::$Op), + )* + _ => Err(InvalidOpcode), + } } } }; -} -from_u8!(OpcodeRepr); -from_u8!(PanicReason); + // Implement accessors for register and immediate values. + (impl_instruction $($doc:literal $ix:literal $Op:ident $op:ident [$($field:ident)*])*) => { + impl Instruction { + /// This instruction's opcode. + pub fn opcode(&self) -> Opcode { + match self { + $( + Self::$Op(_) => Opcode::$Op, + )* + } + } + + /// Unpacks all register IDs into a slice of options. + pub fn reg_ids(&self) -> [Option; 4] { + match self { + $( + Self::$Op(op) => op.reg_ids(), + )* + } + } + } + + impl From for [u8; 4] { + fn from(inst: Instruction) -> Self { + match inst { + $( + Instruction::$Op(op) => op.into(), + )* + } + } + } + + impl core::convert::TryFrom<[u8; 4]> for Instruction { + type Error = InvalidOpcode; + fn try_from([op, a, b, c]: [u8; 4]) -> Result { + match Opcode::try_from(op)? { + $( + Opcode::$Op => Ok(Self::$Op(op::$Op([a, b, c]))), + )* + } + } + } + + impl core::fmt::Debug for Instruction { + fn fmt(&self, f: &mut core::fmt::Formatter) -> core::fmt::Result { + match self { + $( + Self::$Op(op) => op.fmt(f), + )* + } + } + } + }; + + // Entrypoint to the macro, generates structs, methods, opcode enum and instruction enum + // separately. + ($($tts:tt)*) => { + mod _op { + use super::*; + impl_instructions!(decl_op_struct $($tts)*); + impl_instructions!(impl_op $($tts)*); + } + impl_instructions!(decl_opcode_enum $($tts)*); + impl_instructions!(decl_instruction_enum $($tts)*); + impl_instructions!(impl_opcode $($tts)*); + impl_instructions!(impl_instruction $($tts)*); + }; +} diff --git a/fuel-asm/src/op.rs b/fuel-asm/src/op.rs new file mode 100644 index 0000000000..369c1e666a --- /dev/null +++ b/fuel-asm/src/op.rs @@ -0,0 +1,34 @@ +//! Definitions and implementations for each unique instruction type, one for each +//! unique `Opcode` variant. + +use super::{CheckRegId, GMArgs, GTFArgs, Imm12, Imm18, Instruction, RegId}; + +// Here we re-export the generated instruction types and constructors, but extend them with +// `gm_args` and `gtf_args` short-hand constructors below to take their `GMArgs` and `GTFArgs` +// values respectively. +#[doc(inline)] +pub use super::_op::*; + +impl GM { + /// Construct a `GM` instruction from its arguments. + pub fn from_args(ra: RegId, args: GMArgs) -> Self { + Self::new(ra, Imm18::new(args as _)) + } +} + +impl GTF { + /// Construct a `GTF` instruction from its arguments. + pub fn from_args(ra: RegId, rb: RegId, args: GTFArgs) -> Self { + Self::new(ra, rb, Imm12::new(args as _)) + } +} + +/// Construct a `GM` instruction from its arguments. +pub fn gm_args(ra: A, args: GMArgs) -> Instruction { + Instruction::GM(GM::from_args(ra.check(), args)) +} + +/// Construct a `GM` instruction from its arguments. +pub fn gtf_args(ra: A, rb: B, args: GTFArgs) -> Instruction { + Instruction::GTF(GTF::from_args(ra.check(), rb.check(), args)) +} diff --git a/fuel-asm/src/opcode.rs b/fuel-asm/src/opcode.rs deleted file mode 100644 index 293be213bc..0000000000 --- a/fuel-asm/src/opcode.rs +++ /dev/null @@ -1,936 +0,0 @@ -use fuel_types::{bytes, Immediate12, Immediate18, Immediate24, RegisterId, Word}; - -#[cfg(feature = "std")] -use std::{io, iter}; - -use crate::Instruction; - -mod args; -mod consts; - -pub use args::{GMArgs, GTFArgs}; -pub use consts::OpcodeRepr; - -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -/// Instruction representation for the interpreter. -/// -/// ## Memory Opcodes -/// -/// All these opcodes advance the program counter `$pc` by `4` after performing -/// their operation. Every instruction is guaranteed to fit in `u32` -/// representation. -/// -/// ## Arithmetic/Logic (ALU) Opcodes -/// -/// All these opcodes advance the program counter `$pc` by `4` after performing -/// their operation. -/// -/// If the [`F_UNSAFEMATH`](./main.md#flags) flag is unset, an operation that -/// would have set `$err` to `true` is instead a panic. -/// -/// If the [`F_WRAPPING`](./main.md#flags) flag is unset, an operation that -/// would have set `$of` to a non-zero value is instead a panic. ## Contract -/// Opcodes -/// -/// All these opcodes advance the program counter `$pc` by `4` after performing -/// their operation, except for [CALL](#call-call-contract) and -/// [REVERT](#revert-revert). -/// -/// ## Cryptographic Opcodes -/// -/// All these opcodes advance the program counter `$pc` by `4` after performing -/// their operation. -pub enum Opcode { - /// Adds two registers. - ADD(RegisterId, RegisterId, RegisterId), - - /// Adds a register and an immediate value. - ADDI(RegisterId, RegisterId, Immediate12), - - /// Bitwise ANDs two registers. - AND(RegisterId, RegisterId, RegisterId), - - /// Bitwise ANDs a register and an immediate value. - ANDI(RegisterId, RegisterId, Immediate12), - - /// Divides two registers. - DIV(RegisterId, RegisterId, RegisterId), - - /// Divides a register and an immediate value. - DIVI(RegisterId, RegisterId, Immediate12), - - /// Compares two registers for equality. - EQ(RegisterId, RegisterId, RegisterId), - - /// Raises one register to the power of another. - EXP(RegisterId, RegisterId, RegisterId), - - /// Raises one register to the power of an immediate value. - EXPI(RegisterId, RegisterId, Immediate12), - - /// Compares two registers for greater-than. - GT(RegisterId, RegisterId, RegisterId), - - /// Compares two registers for less-than. - LT(RegisterId, RegisterId, RegisterId), - - /// The integer logarithm of a register. - MLOG(RegisterId, RegisterId, RegisterId), - - /// The integer root of a register. - MROO(RegisterId, RegisterId, RegisterId), - - /// Modulo remainder of two registers. - MOD(RegisterId, RegisterId, RegisterId), - - /// Modulo remainder of a register and an immediate value. - MODI(RegisterId, RegisterId, Immediate12), - - /// Copy from one register to another. - MOVE(RegisterId, RegisterId), - - /// Copy immediate value into a register - MOVI(RegisterId, Immediate18), - - /// Multiplies two registers. - MUL(RegisterId, RegisterId, RegisterId), - - /// Multiplies a register and an immediate value. - MULI(RegisterId, RegisterId, Immediate12), - - /// Bitwise NOT a register. - NOT(RegisterId, RegisterId), - - /// Bitwise ORs two registers. - OR(RegisterId, RegisterId, RegisterId), - - /// Bitwise ORs a register and an immediate value. - ORI(RegisterId, RegisterId, Immediate12), - - /// Left shifts a register by a register. - SLL(RegisterId, RegisterId, RegisterId), - - /// Left shifts a register by an immediate value. - SLLI(RegisterId, RegisterId, Immediate12), - - /// Right shifts a register by a register. - SRL(RegisterId, RegisterId, RegisterId), - - /// Right shifts a register by an immediate value. - SRLI(RegisterId, RegisterId, Immediate12), - - /// Subtracts two registers. - SUB(RegisterId, RegisterId, RegisterId), - - /// Subtracts a register and an immediate value. - SUBI(RegisterId, RegisterId, Immediate12), - - /// Bitwise XORs two registers. - XOR(RegisterId, RegisterId, RegisterId), - - /// Bitwise XORs a register and an immediate value. - XORI(RegisterId, RegisterId, Immediate12), - - /// Jump. - JI(Immediate24), - - /// Conditional jump. - JNEI(RegisterId, RegisterId, Immediate12), - - /// Conditional jump against zero. - JNZI(RegisterId, Immediate18), - - /// Dynamic jump. - JMP(RegisterId), - - /// Conditional dynamic jump. - JNE(RegisterId, RegisterId, RegisterId), - - /// Return from context. - RET(RegisterId), - - /// Return from context with data. - RETD(RegisterId, RegisterId), - - /// Extend the current call frame's stack by an immediate value. - CFEI(Immediate24), - - /// Shrink the current call frame's stack by an immediate value. - CFSI(Immediate24), - - /// A byte is loaded from the specified address offset by an immediate value. - LB(RegisterId, RegisterId, Immediate12), - - /// A word is loaded from the specified address offset by an immediate value. - LW(RegisterId, RegisterId, Immediate12), - - /// Allocate a number of bytes from the heap. - ALOC(RegisterId), - - /// Clear a variable number of bytes in memory. - MCL(RegisterId, RegisterId), - - /// Clear an immediate number of bytes in memory. - MCLI(RegisterId, Immediate18), - - /// Copy a variable number of bytes in memory. - MCP(RegisterId, RegisterId, RegisterId), - - /// Copy an immediate number of bytes in memory. - MCPI(RegisterId, RegisterId, Immediate12), - - /// Compare bytes in memory. - MEQ(RegisterId, RegisterId, RegisterId, RegisterId), - - /// Write the least significant byte of a register to memory. - SB(RegisterId, RegisterId, Immediate12), - - /// Write a register to memory. - SW(RegisterId, RegisterId, Immediate12), - - /// Get the balance of contract of an asset ID. - BAL(RegisterId, RegisterId, RegisterId), - - /// Get block header hash for height. - BHSH(RegisterId, RegisterId), - - /// Get current block height. - BHEI(RegisterId), - - /// Burn coins of the current contract's asset ID. - BURN(RegisterId), - - /// Call a contract. - CALL(RegisterId, RegisterId, RegisterId, RegisterId), - - /// Copy contract code for a contract. - CCP(RegisterId, RegisterId, RegisterId, RegisterId), - - /// Get code root of a contract. - CROO(RegisterId, RegisterId), - - /// Get code size of a contract. - CSIZ(RegisterId, RegisterId), - - /// Get current block proposer's address. - CB(RegisterId), - - /// Load a contract's code as executable. - LDC(RegisterId, RegisterId, RegisterId), - - /// Log an event. - LOG(RegisterId, RegisterId, RegisterId, RegisterId), - - /// Log data. - LOGD(RegisterId, RegisterId, RegisterId, RegisterId), - - /// Mint coins of the current contract's asset ID. - MINT(RegisterId), - - /// Halt execution, reverting state changes and returning a value. - RVRT(RegisterId), - - /// Send a message to recipient address with call abi, coins, and output. - SMO(RegisterId, RegisterId, RegisterId, RegisterId), - - /// Clear a series of slots from contract storage. - SCWQ(RegisterId, RegisterId, RegisterId), - - /// Load a word from contract storage. - SRW(RegisterId, RegisterId, RegisterId), - - /// Load a series of 32 byte slots from contract storage. - SRWQ(RegisterId, RegisterId, RegisterId, RegisterId), - - /// Store a word in contract storage. - SWW(RegisterId, RegisterId, RegisterId), - - /// Store a series of 32 byte slots in contract storage. - SWWQ(RegisterId, RegisterId, RegisterId, RegisterId), - - /// Get timestamp of block at given height. - TIME(RegisterId, RegisterId), - - /// Transfer coins to a contract unconditionally. - TR(RegisterId, RegisterId, RegisterId), - - /// Transfer coins to a variable output. - TRO(RegisterId, RegisterId, RegisterId, RegisterId), - - /// The 64-byte public key (x, y) recovered from 64-byte - /// signature on 32-byte message. - ECR(RegisterId, RegisterId, RegisterId), - - /// The keccak-256 hash of a slice. - K256(RegisterId, RegisterId, RegisterId), - - /// The SHA-2-256 hash of a slice. - S256(RegisterId, RegisterId, RegisterId), - - /// Performs no operation. - NOOP, - - /// Set flag register to a register. - FLAG(RegisterId), - - /// Get metadata from memory. - GM(RegisterId, Immediate18), - - /// Get transaction fields. - GTF(RegisterId, RegisterId, Immediate12), - - /// Undefined opcode, potentially from inconsistent serialization. - Undefined, -} - -impl Opcode { - /// Size of the struct when serialized into bytes - pub const LEN: usize = 4; - - /// Create a new [`Opcode`] given the internal attributes - pub const fn new(instruction: Instruction) -> Self { - let op = instruction.op(); - let ra = instruction.ra(); - let rb = instruction.rb(); - let rc = instruction.rc(); - let rd = instruction.rd(); - let imm12 = instruction.imm12(); - let imm18 = instruction.imm18(); - let imm24 = instruction.imm24(); - - let repr = OpcodeRepr::from_u8(op); - - match repr { - OpcodeRepr::ADD => Opcode::ADD(ra, rb, rc), - OpcodeRepr::ADDI => Opcode::ADDI(ra, rb, imm12), - OpcodeRepr::AND => Opcode::AND(ra, rb, rc), - OpcodeRepr::ANDI => Opcode::ANDI(ra, rb, imm12), - OpcodeRepr::DIV => Opcode::DIV(ra, rb, rc), - OpcodeRepr::DIVI => Opcode::DIVI(ra, rb, imm12), - OpcodeRepr::EQ => Opcode::EQ(ra, rb, rc), - OpcodeRepr::EXP => Opcode::EXP(ra, rb, rc), - OpcodeRepr::EXPI => Opcode::EXPI(ra, rb, imm12), - OpcodeRepr::GT => Opcode::GT(ra, rb, rc), - OpcodeRepr::LT => Opcode::LT(ra, rb, rc), - OpcodeRepr::MLOG => Opcode::MLOG(ra, rb, rc), - OpcodeRepr::MROO => Opcode::MROO(ra, rb, rc), - OpcodeRepr::MOD => Opcode::MOD(ra, rb, rc), - OpcodeRepr::MODI => Opcode::MODI(ra, rb, imm12), - OpcodeRepr::MOVE => Opcode::MOVE(ra, rb), - OpcodeRepr::MOVI => Opcode::MOVI(ra, imm18), - OpcodeRepr::MUL => Opcode::MUL(ra, rb, rc), - OpcodeRepr::MULI => Opcode::MULI(ra, rb, imm12), - OpcodeRepr::NOT => Opcode::NOT(ra, rb), - OpcodeRepr::OR => Opcode::OR(ra, rb, rc), - OpcodeRepr::ORI => Opcode::ORI(ra, rb, imm12), - OpcodeRepr::SLL => Opcode::SLL(ra, rb, rc), - OpcodeRepr::SLLI => Opcode::SLLI(ra, rb, imm12), - OpcodeRepr::SRL => Opcode::SRL(ra, rb, rc), - OpcodeRepr::SRLI => Opcode::SRLI(ra, rb, imm12), - OpcodeRepr::SUB => Opcode::SUB(ra, rb, rc), - OpcodeRepr::SUBI => Opcode::SUBI(ra, rb, imm12), - OpcodeRepr::XOR => Opcode::XOR(ra, rb, rc), - OpcodeRepr::XORI => Opcode::XORI(ra, rb, imm12), - OpcodeRepr::JI => Opcode::JI(imm24), - OpcodeRepr::JNEI => Opcode::JNEI(ra, rb, imm12), - OpcodeRepr::JNZI => Opcode::JNZI(ra, imm18), - OpcodeRepr::JMP => Opcode::JMP(ra), - OpcodeRepr::JNE => Opcode::JNE(ra, rb, rc), - OpcodeRepr::RET => Opcode::RET(ra), - OpcodeRepr::RETD => Opcode::RETD(ra, rb), - OpcodeRepr::CFEI => Opcode::CFEI(imm24), - OpcodeRepr::CFSI => Opcode::CFSI(imm24), - OpcodeRepr::LB => Opcode::LB(ra, rb, imm12), - OpcodeRepr::LW => Opcode::LW(ra, rb, imm12), - OpcodeRepr::ALOC => Opcode::ALOC(ra), - OpcodeRepr::MCL => Opcode::MCL(ra, rb), - OpcodeRepr::MCLI => Opcode::MCLI(ra, imm18), - OpcodeRepr::MCP => Opcode::MCP(ra, rb, rc), - OpcodeRepr::MCPI => Opcode::MCPI(ra, rb, imm12), - OpcodeRepr::MEQ => Opcode::MEQ(ra, rb, rc, rd), - OpcodeRepr::SB => Opcode::SB(ra, rb, imm12), - OpcodeRepr::SW => Opcode::SW(ra, rb, imm12), - OpcodeRepr::BAL => Opcode::BAL(ra, rb, rc), - OpcodeRepr::BHSH => Opcode::BHSH(ra, rb), - OpcodeRepr::BHEI => Opcode::BHEI(ra), - OpcodeRepr::BURN => Opcode::BURN(ra), - OpcodeRepr::CALL => Opcode::CALL(ra, rb, rc, rd), - OpcodeRepr::CCP => Opcode::CCP(ra, rb, rc, rd), - OpcodeRepr::CROO => Opcode::CROO(ra, rb), - OpcodeRepr::CSIZ => Opcode::CSIZ(ra, rb), - OpcodeRepr::CB => Opcode::CB(ra), - OpcodeRepr::LDC => Opcode::LDC(ra, rb, rc), - OpcodeRepr::LOG => Opcode::LOG(ra, rb, rc, rd), - OpcodeRepr::LOGD => Opcode::LOGD(ra, rb, rc, rd), - OpcodeRepr::MINT => Opcode::MINT(ra), - OpcodeRepr::RVRT => Opcode::RVRT(ra), - OpcodeRepr::SMO => Opcode::SMO(ra, rb, rc, rd), - OpcodeRepr::SCWQ => Opcode::SCWQ(ra, rb, rc), - OpcodeRepr::SRW => Opcode::SRW(ra, rb, rc), - OpcodeRepr::SRWQ => Opcode::SRWQ(ra, rb, rc, rd), - OpcodeRepr::SWW => Opcode::SWW(ra, rb, rc), - OpcodeRepr::SWWQ => Opcode::SWWQ(ra, rb, rc, rd), - OpcodeRepr::TIME => Opcode::TIME(ra, rb), - OpcodeRepr::TR => Opcode::TR(ra, rb, rc), - OpcodeRepr::TRO => Opcode::TRO(ra, rb, rc, rd), - OpcodeRepr::ECR => Opcode::ECR(ra, rb, rc), - OpcodeRepr::K256 => Opcode::K256(ra, rb, rc), - OpcodeRepr::S256 => Opcode::S256(ra, rb, rc), - OpcodeRepr::NOOP => Opcode::NOOP, - OpcodeRepr::FLAG => Opcode::FLAG(ra), - OpcodeRepr::GM => Opcode::GM(ra, imm18), - OpcodeRepr::GTF => Opcode::GTF(ra, rb, imm12), - _ => Opcode::Undefined, - } - } - - /// Create a `Opcode` from a slice of bytes - /// - /// # Safety - /// - /// Reflects the requirements of [`bytes::from_slice_unchecked`] - pub unsafe fn from_bytes_unchecked(bytes: &[u8]) -> Self { - debug_assert!(Self::LEN <= bytes.len()); - - let op = bytes::from_slice_unchecked(bytes); - let op = u32::from_be_bytes(op); - - op.into() - } - - /// Convert the opcode to bytes representation - pub fn to_bytes(self) -> [u8; Self::LEN] { - u32::from(self).to_be_bytes() - } - - /// Transform the [`Opcode`] into an optional array of 4 register - /// identifiers - pub const fn registers(&self) -> [Option; 4] { - match self { - Self::ADD(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::ADDI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::AND(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::ANDI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::DIV(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::DIVI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::EQ(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::EXP(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::EXPI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::GT(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::LT(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::MLOG(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::MROO(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::MOD(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::MODI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::MOVE(ra, rb) => [Some(*ra), Some(*rb), None, None], - Self::MOVI(ra, _) => [Some(*ra), None, None, None], - Self::MUL(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::MULI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::NOT(ra, rb) => [Some(*ra), Some(*rb), None, None], - Self::OR(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::ORI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::SLL(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::SLLI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::SRL(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::SRLI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::SUB(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::SUBI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::XOR(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::XORI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::JI(_) => [None; 4], - Self::JNEI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::JNZI(ra, _) => [Some(*ra), None, None, None], - Self::JMP(ra) => [Some(*ra), None, None, None], - Self::JNE(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::RET(ra) => [Some(*ra), None, None, None], - Self::RETD(ra, rb) => [Some(*ra), Some(*rb), None, None], - Self::CFEI(_) => [None; 4], - Self::CFSI(_) => [None; 4], - Self::LB(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::LW(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::ALOC(ra) => [Some(*ra), None, None, None], - Self::MCL(ra, rb) => [Some(*ra), Some(*rb), None, None], - Self::MCLI(ra, _) => [Some(*ra), None, None, None], - Self::MCP(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::MCPI(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::MEQ(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::SB(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::SW(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::BAL(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::BHSH(ra, rb) => [Some(*ra), Some(*rb), None, None], - Self::BHEI(ra) => [Some(*ra), None, None, None], - Self::BURN(ra) => [Some(*ra), None, None, None], - Self::CALL(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::CCP(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::CROO(ra, rb) => [Some(*ra), Some(*rb), None, None], - Self::CSIZ(ra, rb) => [Some(*ra), Some(*rb), None, None], - Self::CB(ra) => [Some(*ra), None, None, None], - Self::LDC(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::LOG(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::LOGD(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::MINT(ra) => [Some(*ra), None, None, None], - Self::RVRT(ra) => [Some(*ra), None, None, None], - Self::SMO(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::SCWQ(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::SRW(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::SRWQ(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::SWW(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::SWWQ(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::TIME(ra, rb) => [Some(*ra), Some(*rb), None, None], - Self::TR(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::TRO(ra, rb, rc, rd) => [Some(*ra), Some(*rb), Some(*rc), Some(*rd)], - Self::ECR(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::K256(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::S256(ra, rb, rc) => [Some(*ra), Some(*rb), Some(*rc), None], - Self::NOOP => [None; 4], - Self::FLAG(ra) => [Some(*ra), None, None, None], - Self::GM(ra, _) => [Some(*ra), None, None, None], - Self::GTF(ra, rb, _) => [Some(*ra), Some(*rb), None, None], - Self::Undefined => [None; 4], - } - } - - /// Return the underlying immediate value, if present - pub const fn immediate(&self) -> Option { - match self { - Self::ADDI(_, _, imm) - | Self::ANDI(_, _, imm) - | Self::DIVI(_, _, imm) - | Self::EXPI(_, _, imm) - | Self::MODI(_, _, imm) - | Self::MULI(_, _, imm) - | Self::ORI(_, _, imm) - | Self::SLLI(_, _, imm) - | Self::SRLI(_, _, imm) - | Self::SUBI(_, _, imm) - | Self::XORI(_, _, imm) - | Self::JNEI(_, _, imm) - | Self::MCPI(_, _, imm) - | Self::LB(_, _, imm) - | Self::LW(_, _, imm) - | Self::SB(_, _, imm) - | Self::SW(_, _, imm) - | Self::GTF(_, _, imm) => Some(*imm as Word), - - Self::MCLI(_, imm) | Self::GM(_, imm) | Self::JNZI(_, imm) | Self::MOVI(_, imm) => Some(*imm as Word), - - Self::JI(imm) | Self::CFEI(imm) | Self::CFSI(imm) => Some(*imm as Word), - - Self::ADD(_, _, _) - | Self::AND(_, _, _) - | Self::DIV(_, _, _) - | Self::EQ(_, _, _) - | Self::EXP(_, _, _) - | Self::GT(_, _, _) - | Self::LT(_, _, _) - | Self::MLOG(_, _, _) - | Self::MROO(_, _, _) - | Self::MOD(_, _, _) - | Self::MOVE(_, _) - | Self::MUL(_, _, _) - | Self::NOT(_, _) - | Self::OR(_, _, _) - | Self::SLL(_, _, _) - | Self::SRL(_, _, _) - | Self::SUB(_, _, _) - | Self::XOR(_, _, _) - | Self::RET(_) - | Self::RETD(_, _) - | Self::ALOC(_) - | Self::MCL(_, _) - | Self::MCP(_, _, _) - | Self::MEQ(_, _, _, _) - | Self::BAL(_, _, _) - | Self::BHSH(_, _) - | Self::BHEI(_) - | Self::BURN(_) - | Self::CALL(_, _, _, _) - | Self::CCP(_, _, _, _) - | Self::CROO(_, _) - | Self::CSIZ(_, _) - | Self::CB(_) - | Self::LDC(_, _, _) - | Self::LOG(_, _, _, _) - | Self::LOGD(_, _, _, _) - | Self::MINT(_) - | Self::RVRT(_) - | Self::SMO(_, _, _, _) - | Self::JMP(_) - | Self::JNE(_, _, _) - | Self::SCWQ(_, _, _) - | Self::SRW(_, _, _) - | Self::SRWQ(_, _, _, _) - | Self::SWW(_, _, _) - | Self::SWWQ(_, _, _, _) - | Self::TIME(_, _) - | Self::TR(_, _, _) - | Self::TRO(_, _, _, _) - | Self::ECR(_, _, _) - | Self::K256(_, _, _) - | Self::S256(_, _, _) - | Self::NOOP - | Self::FLAG(_) - | Self::Undefined => None, - } - } - - /// Create a new [`Opcode::GM`] instruction from its args - pub const fn gm(ra: RegisterId, args: GMArgs) -> Self { - Self::GM(ra, args as Immediate18) - } - - /// Create a new [`Opcode::GTF`] instruction from its args - pub const fn gtf(ra: RegisterId, rb: RegisterId, args: GTFArgs) -> Self { - Self::GTF(ra, rb, args as Immediate12) - } -} - -#[cfg(feature = "std")] -impl Opcode { - /// Create a `Opcode` from a slice of bytes - /// - /// This function will fail if the length of the bytes is smaller than - /// [`Opcode::LEN`]. - pub fn from_bytes(bytes: &[u8]) -> io::Result { - if bytes.len() < Self::LEN { - Err(io::Error::new( - io::ErrorKind::UnexpectedEof, - "The provided buffer is not big enough!", - )) - } else { - // Safety: checked length - Ok(unsafe { Self::from_bytes_unchecked(bytes) }) - } - } - - /// Create a set of `Opcode` from an iterator of bytes - /// - /// If not padded to [`Self::LEN`], will consume the unaligned bytes but won't try to parse an - /// opcode from them. - pub fn from_bytes_iter(bytes: I) -> Vec - where - I: IntoIterator, - { - let mut bytes = bytes.into_iter(); - let mut buf = [0u8; Self::LEN]; - let mut ret = Vec::with_capacity(bytes.size_hint().0 / Self::LEN); - - loop { - let n = bytes - .by_ref() - .take(Self::LEN) - .zip(buf.as_mut().iter_mut()) - .fold(0, |n, (x, b)| { - *b = x; - - n + 1 - }); - - if n < Self::LEN { - break; - } - - ret.push(Self::from(buf)); - } - - ret - } -} - -impl From for Opcode { - fn from(parsed: Instruction) -> Self { - Self::new(parsed) - } -} - -impl From<[u8; Opcode::LEN]> for Opcode { - fn from(b: [u8; Opcode::LEN]) -> Self { - u32::from_be_bytes(b).into() - } -} - -impl From for Opcode { - fn from(instruction: u32) -> Self { - Self::new(Instruction::from(instruction)) - } -} - -impl From for u32 { - fn from(opcode: Opcode) -> u32 { - match opcode { - Opcode::ADD(ra, rb, rc) => { - ((OpcodeRepr::ADD as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::ADDI(ra, rb, imm12) => { - ((OpcodeRepr::ADDI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::AND(ra, rb, rc) => { - ((OpcodeRepr::AND as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::ANDI(ra, rb, imm12) => { - ((OpcodeRepr::ANDI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::DIV(ra, rb, rc) => { - ((OpcodeRepr::DIV as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::DIVI(ra, rb, imm12) => { - ((OpcodeRepr::DIVI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::EQ(ra, rb, rc) => { - ((OpcodeRepr::EQ as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::EXP(ra, rb, rc) => { - ((OpcodeRepr::EXP as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::EXPI(ra, rb, imm12) => { - ((OpcodeRepr::EXPI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::GT(ra, rb, rc) => { - ((OpcodeRepr::GT as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::LT(ra, rb, rc) => { - ((OpcodeRepr::LT as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::MLOG(ra, rb, rc) => { - ((OpcodeRepr::MLOG as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::MROO(ra, rb, rc) => { - ((OpcodeRepr::MROO as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::MOD(ra, rb, rc) => { - ((OpcodeRepr::MOD as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::MODI(ra, rb, imm12) => { - ((OpcodeRepr::MODI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::MOVE(ra, rb) => ((OpcodeRepr::MOVE as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12), - Opcode::MOVI(ra, imm18) => ((OpcodeRepr::MOVI as u32) << 24) | ((ra as u32) << 18) | (imm18), - Opcode::MUL(ra, rb, rc) => { - ((OpcodeRepr::MUL as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::MULI(ra, rb, imm12) => { - ((OpcodeRepr::MULI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::NOT(ra, rb) => ((OpcodeRepr::NOT as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12), - Opcode::OR(ra, rb, rc) => { - ((OpcodeRepr::OR as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::ORI(ra, rb, imm12) => { - ((OpcodeRepr::ORI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::SLL(ra, rb, rc) => { - ((OpcodeRepr::SLL as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::SLLI(ra, rb, imm12) => { - ((OpcodeRepr::SLLI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::SRL(ra, rb, rc) => { - ((OpcodeRepr::SRL as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::SRLI(ra, rb, imm12) => { - ((OpcodeRepr::SRLI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::SUB(ra, rb, rc) => { - ((OpcodeRepr::SUB as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::SUBI(ra, rb, imm12) => { - ((OpcodeRepr::SUBI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::XOR(ra, rb, rc) => { - ((OpcodeRepr::XOR as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::XORI(ra, rb, imm12) => { - ((OpcodeRepr::XORI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::JI(imm24) => ((OpcodeRepr::JI as u32) << 24) | (imm24), - Opcode::JNEI(ra, rb, imm12) => { - ((OpcodeRepr::JNEI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::JNZI(ra, imm18) => ((OpcodeRepr::JNZI as u32) << 24) | ((ra as u32) << 18) | (imm18), - Opcode::JMP(ra) => ((OpcodeRepr::JMP as u32) << 24) | ((ra as u32) << 18), - Opcode::JNE(ra, rb, rc) => { - ((OpcodeRepr::JNE as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::RET(ra) => ((OpcodeRepr::RET as u32) << 24) | ((ra as u32) << 18), - Opcode::RETD(ra, rb) => ((OpcodeRepr::RETD as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12), - Opcode::CFEI(imm24) => ((OpcodeRepr::CFEI as u32) << 24) | (imm24), - Opcode::CFSI(imm24) => ((OpcodeRepr::CFSI as u32) << 24) | (imm24), - Opcode::LB(ra, rb, imm12) => { - ((OpcodeRepr::LB as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::LW(ra, rb, imm12) => { - ((OpcodeRepr::LW as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::ALOC(ra) => ((OpcodeRepr::ALOC as u32) << 24) | ((ra as u32) << 18), - Opcode::MCL(ra, rb) => ((OpcodeRepr::MCL as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12), - Opcode::MCLI(ra, imm18) => ((OpcodeRepr::MCLI as u32) << 24) | ((ra as u32) << 18) | (imm18), - Opcode::MCP(ra, rb, rc) => { - ((OpcodeRepr::MCP as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::MCPI(ra, rb, imm12) => { - ((OpcodeRepr::MCPI as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::MEQ(ra, rb, rc, rd) => { - ((OpcodeRepr::MEQ as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::SB(ra, rb, imm12) => { - ((OpcodeRepr::SB as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::SW(ra, rb, imm12) => { - ((OpcodeRepr::SW as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::BAL(ra, rb, rc) => { - ((OpcodeRepr::BAL as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::BHSH(ra, rb) => ((OpcodeRepr::BHSH as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12), - Opcode::BHEI(ra) => ((OpcodeRepr::BHEI as u32) << 24) | ((ra as u32) << 18), - Opcode::BURN(ra) => ((OpcodeRepr::BURN as u32) << 24) | ((ra as u32) << 18), - Opcode::CALL(ra, rb, rc, rd) => { - ((OpcodeRepr::CALL as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::CCP(ra, rb, rc, rd) => { - ((OpcodeRepr::CCP as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::CROO(ra, rb) => ((OpcodeRepr::CROO as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12), - Opcode::CSIZ(ra, rb) => ((OpcodeRepr::CSIZ as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12), - Opcode::CB(ra) => ((OpcodeRepr::CB as u32) << 24) | ((ra as u32) << 18), - Opcode::LDC(ra, rb, rc) => { - ((OpcodeRepr::LDC as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::LOG(ra, rb, rc, rd) => { - ((OpcodeRepr::LOG as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::LOGD(ra, rb, rc, rd) => { - ((OpcodeRepr::LOGD as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::MINT(ra) => ((OpcodeRepr::MINT as u32) << 24) | ((ra as u32) << 18), - Opcode::RVRT(ra) => ((OpcodeRepr::RVRT as u32) << 24) | ((ra as u32) << 18), - Opcode::SMO(ra, rb, rc, rd) => { - ((OpcodeRepr::SMO as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::SCWQ(ra, rb, rc) => { - ((OpcodeRepr::SCWQ as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::SRW(ra, rb, rc) => { - ((OpcodeRepr::SRW as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::SRWQ(ra, rb, rc, rd) => { - ((OpcodeRepr::SRWQ as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::SWW(ra, rb, rc) => { - ((OpcodeRepr::SWW as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::SWWQ(ra, rb, rc, rd) => { - ((OpcodeRepr::SWWQ as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::TIME(ra, rb) => ((OpcodeRepr::TIME as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12), - Opcode::TR(ra, rb, rc) => { - ((OpcodeRepr::TR as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::TRO(ra, rb, rc, rd) => { - ((OpcodeRepr::TRO as u32) << 24) - | ((ra as u32) << 18) - | ((rb as u32) << 12) - | ((rc as u32) << 6) - | (rd as u32) - } - Opcode::ECR(ra, rb, rc) => { - ((OpcodeRepr::ECR as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::K256(ra, rb, rc) => { - ((OpcodeRepr::K256 as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::S256(ra, rb, rc) => { - ((OpcodeRepr::S256 as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | ((rc as u32) << 6) - } - Opcode::NOOP => (OpcodeRepr::NOOP as u32) << 24, - Opcode::FLAG(ra) => ((OpcodeRepr::FLAG as u32) << 24) | ((ra as u32) << 18), - Opcode::GM(ra, imm18) => ((OpcodeRepr::GM as u32) << 24) | ((ra as u32) << 18) | (imm18), - Opcode::GTF(ra, rb, imm12) => { - ((OpcodeRepr::GTF as u32) << 24) | ((ra as u32) << 18) | ((rb as u32) << 12) | (imm12 as u32) - } - Opcode::Undefined => 0x00 << 24, - } - } -} - -#[cfg(feature = "std")] -impl io::Read for Opcode { - fn read(&mut self, buf: &mut [u8]) -> io::Result { - buf.chunks_exact_mut(Opcode::LEN) - .next() - .map(|chunk| chunk.copy_from_slice(&u32::from(*self).to_be_bytes())) - .map(|_| Opcode::LEN) - .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "The provided buffer is not big enough!")) - } -} - -#[cfg(feature = "std")] -impl io::Write for Opcode { - fn write(&mut self, buf: &[u8]) -> io::Result { - // Safety: checked length - buf.chunks_exact(Opcode::LEN) - .next() - .ok_or_else(|| io::Error::new(io::ErrorKind::UnexpectedEof, "The provided buffer is not big enough!")) - .map(|bytes| *self = unsafe { Self::from_bytes_unchecked(bytes) }) - .map(|_| Opcode::LEN) - } - - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - -#[cfg(feature = "std")] -impl iter::FromIterator for Vec { - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - iter.into_iter().flat_map(Opcode::to_bytes).collect() - } -} - -#[cfg(feature = "std")] -impl iter::FromIterator for Vec { - fn from_iter(iter: T) -> Self - where - T: IntoIterator, - { - iter.into_iter().map(Opcode::from).collect() - } -} diff --git a/fuel-asm/src/opcode/consts.rs b/fuel-asm/src/opcode/consts.rs deleted file mode 100644 index 7248c94536..0000000000 --- a/fuel-asm/src/opcode/consts.rs +++ /dev/null @@ -1,599 +0,0 @@ -/// Byte representation of an opcode -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[allow(non_camel_case_types, clippy::upper_case_acronyms)] -#[repr(u8)] -#[non_exhaustive] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -pub enum OpcodeRepr { - /// RESERV00 - RESERV00 = 0x00, - /// RESERV01 - RESERV01 = 0x01, - /// RESERV02 - RESERV02 = 0x02, - /// RESERV03 - RESERV03 = 0x03, - /// RESERV04 - RESERV04 = 0x04, - /// RESERV05 - RESERV05 = 0x05, - /// RESERV06 - RESERV06 = 0x06, - /// RESERV07 - RESERV07 = 0x07, - /// RESERV08 - RESERV08 = 0x08, - /// RESERV09 - RESERV09 = 0x09, - /// RESERV0A - RESERV0A = 0x0a, - /// RESERV0B - RESERV0B = 0x0b, - /// RESERV0C - RESERV0C = 0x0c, - /// RESERV0D - RESERV0D = 0x0d, - /// RESERV0E - RESERV0E = 0x0e, - /// RESERV0F - RESERV0F = 0x0f, - - // Classes 0x1_-0x4_ - No immediate value - /// ADD - ADD = 0x10, - /// AND - AND = 0x11, - /// DIV - DIV = 0x12, - /// EQ - EQ = 0x13, - /// EXP - EXP = 0x14, - /// GT - GT = 0x15, - /// LT - LT = 0x16, - /// MLOG - MLOG = 0x17, - /// MROO - MROO = 0x18, - /// MOD - MOD = 0x19, - /// MOVE - MOVE = 0x1a, - /// MUL - MUL = 0x1b, - /// NOT - NOT = 0x1c, - /// OR - OR = 0x1d, - /// SLL - SLL = 0x1e, - /// SRL - SRL = 0x1f, - /// SUB - SUB = 0x20, - /// XOR - XOR = 0x21, - /// RESERV22 - RESERV22 = 0x22, - /// RESERV23 - RESERV23 = 0x23, - /// RET - RET = 0x24, - /// RETD - RETD = 0x25, - /// ALOC - ALOC = 0x26, - /// MCL - MCL = 0x27, - /// MCP - MCP = 0x28, - /// MEQ - MEQ = 0x29, - /// BHSH - BHSH = 0x2a, - /// BHEI - BHEI = 0x2b, - /// BURN - BURN = 0x2c, - /// CALL - CALL = 0x2d, - /// CCP - CCP = 0x2e, - /// CROO - CROO = 0x2f, - /// CSIZ - CSIZ = 0x30, - /// CB - CB = 0x31, - /// LDC - LDC = 0x32, - /// LOG - LOG = 0x33, - /// LOGD - LOGD = 0x34, - /// MINT - MINT = 0x35, - /// RVRT - RVRT = 0x36, - /// SCWQ - SCWQ = 0x37, - /// SRW - SRW = 0x38, - /// SRWQ - SRWQ = 0x39, - /// SWW - SWW = 0x3a, - /// SWWQ - SWWQ = 0x3b, - /// TR - TR = 0x3c, - /// TRO - TRO = 0x3d, - /// ECR - ECR = 0x3e, - /// K256 - K256 = 0x3f, - /// S256 - S256 = 0x40, - /// TIME - TIME = 0x41, - /// RESERV42 - RESERV42 = 0x42, - /// RESERV43 - RESERV43 = 0x43, - /// RESERV44 - RESERV44 = 0x44, - /// RESERV45 - RESERV45 = 0x45, - /// RESERV46 - RESERV46 = 0x46, - /// NOOP - NOOP = 0x47, - /// FLAG - FLAG = 0x48, - /// BAL - BAL = 0x49, - /// JMP - JMP = 0x4a, - /// JNE - JNE = 0x4b, - /// SMO - SMO = 0x4c, - /// RESERV4D - RESERV4D = 0x4d, - /// RESERV4E - RESERV4E = 0x4e, - /// RESERV4F - RESERV4F = 0x4f, - - // Classes 0x5_-0x6_ - Immediate 12 bits - /// ADDI - ADDI = 0x50, - /// ANDI - ANDI = 0x51, - /// DIVI - DIVI = 0x52, - /// EXPI - EXPI = 0x53, - /// MODI - MODI = 0x54, - /// MULI - MULI = 0x55, - /// ORI - ORI = 0x56, - /// SLLI - SLLI = 0x57, - /// SRLI - SRLI = 0x58, - /// SUBI - SUBI = 0x59, - /// XORI - XORI = 0x5a, - /// JNEI - JNEI = 0x5b, - /// LB - LB = 0x5c, - /// LW - LW = 0x5d, - /// SB - SB = 0x5e, - /// SW - SW = 0x5f, - /// MCPI - MCPI = 0x60, - /// GTF - GTF = 0x61, - /// RESERV62 - RESERV62 = 0x62, - /// RESERV63 - RESERV63 = 0x63, - /// RESERV64 - RESERV64 = 0x64, - /// RESERV65 - RESERV65 = 0x65, - /// RESERV66 - RESERV66 = 0x66, - /// RESERV67 - RESERV67 = 0x67, - /// RESERV68 - RESERV68 = 0x68, - /// RESERV69 - RESERV69 = 0x69, - /// RESERV6A - RESERV6A = 0x6a, - /// RESERV6B - RESERV6B = 0x6b, - /// RESERV6C - RESERV6C = 0x6c, - /// RESERV6D - RESERV6D = 0x6d, - /// RESERV6E - RESERV6E = 0x6e, - /// RESERV6F - RESERV6F = 0x6f, - - // Classes 0x7_-0x8_ - Immediate 18 bits - /// MCLI - MCLI = 0x70, - /// GM - GM = 0x71, - /// MOVI - MOVI = 0x72, - /// JNZI - JNZI = 0x73, - /// RESERV74 - RESERV74 = 0x74, - /// RESERV75 - RESERV75 = 0x75, - /// RESERV76 - RESERV76 = 0x76, - /// RESERV77 - RESERV77 = 0x77, - /// RESERV78 - RESERV78 = 0x78, - /// RESERV79 - RESERV79 = 0x79, - /// RESERV7A - RESERV7A = 0x7a, - /// RESERV7B - RESERV7B = 0x7b, - /// RESERV7C - RESERV7C = 0x7c, - /// RESERV7D - RESERV7D = 0x7d, - /// RESERV7E - RESERV7E = 0x7e, - /// RESERV7F - RESERV7F = 0x7f, - /// RESERV80 - RESERV80 = 0x80, - /// RESERV81 - RESERV81 = 0x81, - /// RESERV82 - RESERV82 = 0x82, - /// RESERV83 - RESERV83 = 0x83, - /// RESERV84 - RESERV84 = 0x84, - /// RESERV85 - RESERV85 = 0x85, - /// RESERV86 - RESERV86 = 0x86, - /// RESERV87 - RESERV87 = 0x87, - /// RESERV88 - RESERV88 = 0x88, - /// RESERV89 - RESERV89 = 0x89, - /// RESERV8A - RESERV8A = 0x8a, - /// RESERV8B - RESERV8B = 0x8b, - /// RESERV8C - RESERV8C = 0x8c, - /// RESERV8D - RESERV8D = 0x8d, - /// RESERV8E - RESERV8E = 0x8e, - /// RESERV8F - RESERV8F = 0x8f, - - // Classes 0x9_-0xa_ - Immediate 24 bits - /// JI - JI = 0x90, - /// CFEI - CFEI = 0x91, - /// CFSI - CFSI = 0x92, - /// RESERV93 - RESERV93 = 0x93, - /// RESERV94 - RESERV94 = 0x94, - /// RESERV95 - RESERV95 = 0x95, - /// RESERV96 - RESERV96 = 0x96, - /// RESERV97 - RESERV97 = 0x97, - /// RESERV98 - RESERV98 = 0x98, - /// RESERV99 - RESERV99 = 0x99, - /// RESERV9A - RESERV9A = 0x9a, - /// RESERV9B - RESERV9B = 0x9b, - /// RESERV9C - RESERV9C = 0x9c, - /// RESERV9D - RESERV9D = 0x9d, - /// RESERV9E - RESERV9E = 0x9e, - /// RESERV9F - RESERV9F = 0x9f, - /// RESERVA0 - RESERVA0 = 0xa0, - /// RESERVA1 - RESERVA1 = 0xa1, - /// RESERVA2 - RESERVA2 = 0xa2, - /// RESERVA3 - RESERVA3 = 0xa3, - /// RESERVA4 - RESERVA4 = 0xa4, - /// RESERVA5 - RESERVA5 = 0xa5, - /// RESERVA6 - RESERVA6 = 0xa6, - /// RESERVA7 - RESERVA7 = 0xa7, - /// RESERVA8 - RESERVA8 = 0xa8, - /// RESERVA9 - RESERVA9 = 0xa9, - /// RESERVAA - RESERVAA = 0xaa, - /// RESERVAB - RESERVAB = 0xab, - /// RESERVAC - RESERVAC = 0xac, - /// RESERVAD - RESERVAD = 0xad, - /// RESERVAE - RESERVAE = 0xae, - /// RESERVAF - RESERVAF = 0xaf, - - /// RESERVB0 - RESERVB0 = 0xb0, - /// RESERVB1 - RESERVB1 = 0xb1, - /// RESERVB2 - RESERVB2 = 0xb2, - /// RESERVB3 - RESERVB3 = 0xb3, - /// RESERVB4 - RESERVB4 = 0xb4, - /// RESERVB5 - RESERVB5 = 0xb5, - /// RESERVB6 - RESERVB6 = 0xb6, - /// RESERVB7 - RESERVB7 = 0xb7, - /// RESERVB8 - RESERVB8 = 0xb8, - /// RESERVB9 - RESERVB9 = 0xb9, - /// RESERVBA - RESERVBA = 0xba, - /// RESERVBB - RESERVBB = 0xbb, - /// RESERVBC - RESERVBC = 0xbc, - /// RESERVBD - RESERVBD = 0xbd, - /// RESERVBE - RESERVBE = 0xbe, - /// RESERVBF - RESERVBF = 0xbf, - - /// RESERVC0 - RESERVC0 = 0xc0, - /// RESERVC1 - RESERVC1 = 0xc1, - /// RESERVC2 - RESERVC2 = 0xc2, - /// RESERVC3 - RESERVC3 = 0xc3, - /// RESERVC4 - RESERVC4 = 0xc4, - /// RESERVC5 - RESERVC5 = 0xc5, - /// RESERVC6 - RESERVC6 = 0xc6, - /// RESERVC7 - RESERVC7 = 0xc7, - /// RESERVC8 - RESERVC8 = 0xc8, - /// RESERVC9 - RESERVC9 = 0xc9, - /// RESERVCA - RESERVCA = 0xca, - /// RESERVCB - RESERVCB = 0xcb, - /// RESERVCC - RESERVCC = 0xcc, - /// RESERVCD - RESERVCD = 0xcd, - /// RESERVCE - RESERVCE = 0xce, - /// RESERVCF - RESERVCF = 0xcf, - - /// RESERVD0 - RESERVD0 = 0xd0, - /// RESERVD1 - RESERVD1 = 0xd1, - /// RESERVD2 - RESERVD2 = 0xd2, - /// RESERVD3 - RESERVD3 = 0xd3, - /// RESERVD4 - RESERVD4 = 0xd4, - /// RESERVD5 - RESERVD5 = 0xd5, - /// RESERVD6 - RESERVD6 = 0xd6, - /// RESERVD7 - RESERVD7 = 0xd7, - /// RESERVD8 - RESERVD8 = 0xd8, - /// RESERVD9 - RESERVD9 = 0xd9, - /// RESERVDA - RESERVDA = 0xda, - /// RESERVDB - RESERVDB = 0xdb, - /// RESERVDC - RESERVDC = 0xdc, - /// RESERVDD - RESERVDD = 0xdd, - /// RESERVDE - RESERVDE = 0xde, - /// RESERVDF - RESERVDF = 0xdf, - - /// RESERVE0 - RESERVE0 = 0xe0, - /// RESERVE1 - RESERVE1 = 0xe1, - /// RESERVE2 - RESERVE2 = 0xe2, - /// RESERVE3 - RESERVE3 = 0xe3, - /// RESERVE4 - RESERVE4 = 0xe4, - /// RESERVE5 - RESERVE5 = 0xe5, - /// RESERVE6 - RESERVE6 = 0xe6, - /// RESERVE7 - RESERVE7 = 0xe7, - /// RESERVE8 - RESERVE8 = 0xe8, - /// RESERVE9 - RESERVE9 = 0xe9, - /// RESERVEA - RESERVEA = 0xea, - /// RESERVEB - RESERVEB = 0xeb, - /// RESERVEC - RESERVEC = 0xec, - /// RESERVED - RESERVED = 0xed, - /// RESERVEE - RESERVEE = 0xee, - /// RESERVEF - RESERVEF = 0xef, - - /// RESERVF0 - RESERVF0 = 0xf0, - /// RESERVF1 - RESERVF1 = 0xf1, - /// RESERVF2 - RESERVF2 = 0xf2, - /// RESERVF3 - RESERVF3 = 0xf3, - /// RESERVF4 - RESERVF4 = 0xf4, - /// RESERVF5 - RESERVF5 = 0xf5, - /// RESERVF6 - RESERVF6 = 0xf6, - /// RESERVF7 - RESERVF7 = 0xf7, - /// RESERVF8 - RESERVF8 = 0xf8, - /// RESERVF9 - RESERVF9 = 0xf9, - /// RESERVFA - RESERVFA = 0xfa, - /// RESERVFB - RESERVFB = 0xfb, - /// RESERVFC - RESERVFC = 0xfc, - /// RESERVFD - RESERVFD = 0xfd, - /// RESERVFE - RESERVFE = 0xfe, - /// RESERVFF - RESERVFF = 0xff, -} - -impl OpcodeRepr { - /// Check if the opcode representation is allowed for predicates - /// - /// - /// - #[allow(clippy::match_like_matches_macro)] - // Not following clippy conventions because rustfmt formats macros into bloated code - pub const fn is_predicate_allowed(&self) -> bool { - // TODO Update `OpcodeRepr` to separate contract opcodes - // https://github.com/FuelLabs/fuel-asm/issues/68 - - use OpcodeRepr::*; - - match self { - ADD | AND | DIV | EQ | EXP | GT | LT | MLOG | MROO | MOD | MOVE | MUL | NOT | OR | SLL | SRL | SUB - | XOR | RET | ALOC | MCL | MCP | MEQ | ECR | K256 | S256 | NOOP | FLAG | ADDI | ANDI | DIVI | EXPI - | MODI | MULI | ORI | SLLI | SRLI | SUBI | XORI | JNEI | LB | LW | SB | SW | MCPI | MCLI | GM | MOVI - | JNZI | JI | JMP | JNE | CFEI | CFSI | GTF => true, - - _ => false, - } - } -} - -#[test] -#[allow(clippy::match_like_matches_macro)] -fn check_predicate_allowed() { - use OpcodeRepr::*; - - // This exhaustive test will shield against changes in the opcodes structure - for byte in 0..u8::MAX { - let repr = OpcodeRepr::from_u8(byte); - - let should_allow = match repr { - BAL | BHEI | BHSH | BURN | CALL | CB | CCP | CROO | CSIZ | LDC | LOG | LOGD | MINT | RETD | RVRT | SMO - | SCWQ | SRW | SRWQ | SWW | SWWQ | TIME | TR | TRO => false, - - RESERV00 | RESERV01 | RESERV02 | RESERV03 | RESERV04 | RESERV05 | RESERV06 | RESERV07 | RESERV08 - | RESERV09 | RESERV0A | RESERV0B | RESERV0C | RESERV0D | RESERV0E | RESERV0F | RESERV22 | RESERV23 - | RESERV42 | RESERV43 | RESERV44 | RESERV45 | RESERV46 | RESERV4D | RESERV4E | RESERV4F | RESERV62 - | RESERV63 | RESERV64 | RESERV65 | RESERV66 | RESERV67 | RESERV68 | RESERV69 | RESERV6A | RESERV6B - | RESERV6C | RESERV6D | RESERV6E | RESERV6F | RESERV74 | RESERV75 | RESERV76 | RESERV77 | RESERV78 - | RESERV79 | RESERV7A | RESERV7B | RESERV7C | RESERV7D | RESERV7E | RESERV7F | RESERV80 | RESERV81 - | RESERV82 | RESERV83 | RESERV84 | RESERV85 | RESERV86 | RESERV87 | RESERV88 | RESERV89 | RESERV8A - | RESERV8B | RESERV8C | RESERV8D | RESERV8E | RESERV8F | RESERV93 | RESERV94 | RESERV95 | RESERV96 - | RESERV97 | RESERV98 | RESERV99 | RESERV9A | RESERV9B | RESERV9C | RESERV9D | RESERV9E | RESERV9F - | RESERVA0 | RESERVA1 | RESERVA2 | RESERVA3 | RESERVA4 | RESERVA5 | RESERVA6 | RESERVA7 | RESERVA8 - | RESERVA9 | RESERVAA | RESERVAB | RESERVAC | RESERVAD | RESERVAE | RESERVAF | RESERVB0 | RESERVB1 - | RESERVB2 | RESERVB3 | RESERVB4 | RESERVB5 | RESERVB6 | RESERVB7 | RESERVB8 | RESERVB9 | RESERVBA - | RESERVBB | RESERVBC | RESERVBD | RESERVBE | RESERVBF | RESERVC0 | RESERVC1 | RESERVC2 | RESERVC3 - | RESERVC4 | RESERVC5 | RESERVC6 | RESERVC7 | RESERVC8 | RESERVC9 | RESERVCA | RESERVCB | RESERVCC - | RESERVCD | RESERVCE | RESERVCF | RESERVD0 | RESERVD1 | RESERVD2 | RESERVD3 | RESERVD4 | RESERVD5 - | RESERVD6 | RESERVD7 | RESERVD8 | RESERVD9 | RESERVDA | RESERVDB | RESERVDC | RESERVDD | RESERVDE - | RESERVDF | RESERVE0 | RESERVE1 | RESERVE2 | RESERVE3 | RESERVE4 | RESERVE5 | RESERVE6 | RESERVE7 - | RESERVE8 | RESERVE9 | RESERVEA | RESERVEB | RESERVEC | RESERVED | RESERVEE | RESERVEF | RESERVF0 - | RESERVF1 | RESERVF2 | RESERVF3 | RESERVF4 | RESERVF5 | RESERVF6 | RESERVF7 | RESERVF8 | RESERVF9 - | RESERVFA | RESERVFB | RESERVFC | RESERVFD | RESERVFE | RESERVFF => false, - - _ => true, - }; - - assert_eq!(should_allow, repr.is_predicate_allowed()); - } -} diff --git a/fuel-asm/src/pack.rs b/fuel-asm/src/pack.rs new file mode 100644 index 0000000000..b305123d36 --- /dev/null +++ b/fuel-asm/src/pack.rs @@ -0,0 +1,84 @@ +//! Functions for packing instruction data into bytes or u32s. + +use crate::{Imm12, Imm18, Imm24, RegId}; + +pub(super) fn bytes_from_ra(ra: RegId) -> [u8; 3] { + u8x3_from_u8x4(u32_from_ra(ra).to_be_bytes()) +} + +pub(super) fn bytes_from_ra_rb(ra: RegId, rb: RegId) -> [u8; 3] { + u8x3_from_u8x4(u32_from_ra_rb(ra, rb).to_be_bytes()) +} + +pub(super) fn bytes_from_ra_rb_rc(ra: RegId, rb: RegId, rc: RegId) -> [u8; 3] { + u8x3_from_u8x4(u32_from_ra_rb_rc(ra, rb, rc).to_be_bytes()) +} + +pub(super) fn bytes_from_ra_rb_rc_rd(ra: RegId, rb: RegId, rc: RegId, rd: RegId) -> [u8; 3] { + u8x3_from_u8x4(u32_from_ra_rb_rc_rd(ra, rb, rc, rd).to_be_bytes()) +} + +pub(super) fn bytes_from_ra_rb_imm12(ra: RegId, rb: RegId, imm: Imm12) -> [u8; 3] { + u8x3_from_u8x4(u32_from_ra_rb_imm12(ra, rb, imm).to_be_bytes()) +} + +pub(super) fn bytes_from_ra_imm18(ra: RegId, imm: Imm18) -> [u8; 3] { + u8x3_from_u8x4(u32_from_ra_imm18(ra, imm).to_be_bytes()) +} + +pub(super) fn bytes_from_imm24(imm: Imm24) -> [u8; 3] { + u8x3_from_u8x4(u32_from_imm24(imm).to_be_bytes()) +} + +fn u32_from_ra(r: RegId) -> u32 { + (r.0 as u32) << 18 +} + +fn u32_from_rb(r: RegId) -> u32 { + (r.0 as u32) << 12 +} + +fn u32_from_rc(r: RegId) -> u32 { + (r.0 as u32) << 6 +} + +fn u32_from_rd(r: RegId) -> u32 { + r.0 as u32 +} + +fn u32_from_imm12(imm: Imm12) -> u32 { + imm.0 as u32 +} + +fn u32_from_imm18(imm: Imm18) -> u32 { + imm.0 +} + +fn u32_from_imm24(imm: Imm24) -> u32 { + imm.0 +} + +fn u32_from_ra_rb(ra: RegId, rb: RegId) -> u32 { + u32_from_ra(ra) | u32_from_rb(rb) +} + +fn u32_from_ra_rb_rc(ra: RegId, rb: RegId, rc: RegId) -> u32 { + u32_from_ra_rb(ra, rb) | u32_from_rc(rc) +} + +fn u32_from_ra_rb_rc_rd(ra: RegId, rb: RegId, rc: RegId, rd: RegId) -> u32 { + u32_from_ra_rb_rc(ra, rb, rc) | u32_from_rd(rd) +} + +fn u32_from_ra_rb_imm12(ra: RegId, rb: RegId, imm: Imm12) -> u32 { + u32_from_ra_rb(ra, rb) | u32_from_imm12(imm) +} + +fn u32_from_ra_imm18(ra: RegId, imm: Imm18) -> u32 { + u32_from_ra(ra) | u32_from_imm18(imm) +} + +// Ignore the opcode byte, take the remaining instruction data. +fn u8x3_from_u8x4([_, a, b, c]: [u8; 4]) -> [u8; 3] { + [a, b, c] +} diff --git a/fuel-asm/src/panic_reason.rs b/fuel-asm/src/panic_reason.rs index 5b8d35cec5..f386f069d7 100644 --- a/fuel-asm/src/panic_reason.rs +++ b/fuel-asm/src/panic_reason.rs @@ -1,8 +1,4 @@ -use crate::{Instruction, Opcode, Word}; - -use core::{convert, fmt, mem}; - -const WORD_SIZE: usize = mem::size_of::(); +use core::{convert, fmt}; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] @@ -538,96 +534,21 @@ impl std::error::Error for PanicReason { } } +// TODO: Remove this - `Infallible` has nothing to do with `PanicReason`. impl From for PanicReason { fn from(_i: convert::Infallible) -> Self { unreachable!() } } -#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] -#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "arbitrary", derive(arbitrary::Arbitrary))] -/// Describe a panic reason with the instruction that generated it -pub struct InstructionResult { - reason: PanicReason, - instruction: Instruction, -} - -impl InstructionResult { - /// Represents success - pub const fn success() -> Self { - let reason = PanicReason::from_u8(0); - let instruction = Instruction::new(0); - - Self { reason, instruction } - } - - /// Represents an error described by a reason and an instruction. - pub const fn error(reason: PanicReason, instruction: Instruction) -> Self { - Self { reason, instruction } - } - - /// Underlying panic reason - pub const fn reason(&self) -> &PanicReason { - &self.reason - } - - /// Underlying instruction - pub const fn instruction(&self) -> &Instruction { - &self.instruction - } - - /// This result represents success? - pub const fn is_success(&self) -> bool { - (self.reason as u8) == 0u8 - } - - /// This result represents error? - pub const fn is_error(&self) -> bool { - !self.is_success() - } -} - -const REASON_OFFSET: Word = (WORD_SIZE * 8 - 8) as Word; -const INSTR_OFFSET: Word = ((WORD_SIZE - mem::size_of::()) * 8 - 8) as Word; - -impl From for Word { - fn from(r: InstructionResult) -> Word { - let reason = Word::from(r.reason); - let instruction = (reason != 0) as Word * u32::from(r.instruction) as Word; - - (reason << REASON_OFFSET) | (instruction << INSTR_OFFSET) - } -} - -impl From for InstructionResult { - fn from(val: Word) -> Self { - let reason = val >> REASON_OFFSET; - let instruction = val >> INSTR_OFFSET; - let instruction = (reason != 0) as Word * instruction; - - let reason = PanicReason::from(reason); - let instruction = Instruction::from(instruction as u32); - - Self { reason, instruction } - } -} - -impl From for Instruction { - fn from(r: InstructionResult) -> Self { - r.instruction - } -} - -impl From for Opcode { - fn from(r: InstructionResult) -> Self { - r.instruction.into() - } -} - -impl From for PanicReason { - fn from(r: InstructionResult) -> Self { - r.reason +// TODO: `PanicReason` shouldn't list reserved variants, and this should be `TryFrom` with no +// `unsafe`. +impl From for PanicReason { + fn from(b: u8) -> Self { + // Currently, the language doesn't support customized type coercion + // + // Safety: all possible values of `b` are either allocated or reserved + unsafe { core::mem::transmute::(b) } } } diff --git a/fuel-asm/src/unpack.rs b/fuel-asm/src/unpack.rs new file mode 100644 index 0000000000..3f3a954c88 --- /dev/null +++ b/fuel-asm/src/unpack.rs @@ -0,0 +1,89 @@ +//! Functions for unpacking instruction data from bytes or u32s. + +use crate::{Imm12, Imm18, Imm24, RegId}; + +pub(super) fn rd_from_bytes(bs: [u8; 3]) -> RegId { + rd_from_u32(u32::from_be_bytes(u8x4_from_u8x3(bs))) +} + +pub(super) fn imm12_from_bytes(bs: [u8; 3]) -> Imm12 { + imm12_from_u32(u32::from_be_bytes(u8x4_from_u8x3(bs))) +} + +pub(super) fn imm18_from_bytes(bs: [u8; 3]) -> Imm18 { + imm18_from_u32(u32::from_be_bytes(u8x4_from_u8x3(bs))) +} + +pub(super) fn imm24_from_bytes(bs: [u8; 3]) -> Imm24 { + imm24_from_u32(u32::from_be_bytes(u8x4_from_u8x3(bs))) +} + +pub(super) fn ra_rb_from_bytes(bs: [u8; 3]) -> (RegId, RegId) { + (ra_from_bytes(bs), rb_from_bytes(bs)) +} + +pub(super) fn ra_rb_rc_from_bytes(bs: [u8; 3]) -> (RegId, RegId, RegId) { + (ra_from_bytes(bs), rb_from_bytes(bs), rc_from_bytes(bs)) +} + +pub(super) fn ra_rb_rc_rd_from_bytes(bs: [u8; 3]) -> (RegId, RegId, RegId, RegId) { + ( + ra_from_bytes(bs), + rb_from_bytes(bs), + rc_from_bytes(bs), + rd_from_bytes(bs), + ) +} + +pub(super) fn ra_rb_imm12_from_bytes(bs: [u8; 3]) -> (RegId, RegId, Imm12) { + (ra_from_bytes(bs), rb_from_bytes(bs), imm12_from_bytes(bs)) +} + +pub(super) fn ra_imm18_from_bytes(bs: [u8; 3]) -> (RegId, Imm18) { + (ra_from_bytes(bs), imm18_from_bytes(bs)) +} + +fn ra_from_u32(u: u32) -> RegId { + RegId::new((u >> 18) as u8) +} + +fn rb_from_u32(u: u32) -> RegId { + RegId::new((u >> 12) as u8) +} + +fn rc_from_u32(u: u32) -> RegId { + RegId::new((u >> 6) as u8) +} + +fn rd_from_u32(u: u32) -> RegId { + RegId::new(u as u8) +} + +fn imm12_from_u32(u: u32) -> Imm12 { + Imm12::new(u as u16) +} + +fn imm18_from_u32(u: u32) -> Imm18 { + Imm18::new(u) +} + +fn imm24_from_u32(u: u32) -> Imm24 { + Imm24::new(u) +} + +pub(super) fn ra_from_bytes(bs: [u8; 3]) -> RegId { + ra_from_u32(u32::from_be_bytes(u8x4_from_u8x3(bs))) +} + +pub(super) fn rb_from_bytes(bs: [u8; 3]) -> RegId { + rb_from_u32(u32::from_be_bytes(u8x4_from_u8x3(bs))) +} + +pub(super) fn rc_from_bytes(bs: [u8; 3]) -> RegId { + rc_from_u32(u32::from_be_bytes(u8x4_from_u8x3(bs))) +} + +// Produce the big-endian bytes for an instruction's data, with a zeroed opcode byte. +fn u8x4_from_u8x3([a, b, c]: [u8; 3]) -> [u8; 4] { + [0, a, b, c] +} diff --git a/fuel-asm/tests/encoding.rs b/fuel-asm/tests/encoding.rs index c22808928d..b6600423c9 100644 --- a/fuel-asm/tests/encoding.rs +++ b/fuel-asm/tests/encoding.rs @@ -1,7 +1,6 @@ #![allow(clippy::iter_cloned_collect)] // https://github.com/rust-lang/rust-clippy/issues/9119 use fuel_asm::*; -use std::io::{Read, Write}; #[test] fn opcode() { @@ -11,261 +10,195 @@ fn opcode() { let imm18 = 0x02fffd; let imm24 = 0xbffffd; - let mut data = vec![ - Opcode::ADD(r, r, r), - Opcode::ADDI(r, r, imm12), - Opcode::AND(r, r, r), - Opcode::ANDI(r, r, imm12), - Opcode::DIV(r, r, r), - Opcode::DIVI(r, r, imm12), - Opcode::EQ(r, r, r), - Opcode::EXP(r, r, r), - Opcode::EXPI(r, r, imm12), - Opcode::GT(r, r, r), - Opcode::LT(r, r, r), - Opcode::MLOG(r, r, r), - Opcode::MROO(r, r, r), - Opcode::MOD(r, r, r), - Opcode::MODI(r, r, imm12), - Opcode::MOVE(r, r), - Opcode::MOVI(r, imm18), - Opcode::MUL(r, r, r), - Opcode::MULI(r, r, imm12), - Opcode::NOT(r, r), - Opcode::OR(r, r, r), - Opcode::ORI(r, r, imm12), - Opcode::SLL(r, r, r), - Opcode::SLLI(r, r, imm12), - Opcode::SRL(r, r, r), - Opcode::SRLI(r, r, imm12), - Opcode::SUB(r, r, r), - Opcode::SUBI(r, r, imm12), - Opcode::XOR(r, r, r), - Opcode::XORI(r, r, imm12), - Opcode::JI(imm24), - Opcode::JNEI(r, r, imm12), - Opcode::JNZI(r, imm18), - Opcode::JMP(r), - Opcode::JNE(r, r, r), - Opcode::RET(r), - Opcode::RETD(r, r), - Opcode::CFEI(imm24), - Opcode::CFSI(imm24), - Opcode::LB(r, r, imm12), - Opcode::LW(r, r, imm12), - Opcode::ALOC(r), - Opcode::MCL(r, r), - Opcode::MCLI(r, imm18), - Opcode::MCP(r, r, r), - Opcode::MCPI(r, r, imm12), - Opcode::MEQ(r, r, r, r), - Opcode::SB(r, r, imm12), - Opcode::SW(r, r, imm12), - Opcode::BAL(r, r, r), - Opcode::BHSH(r, r), - Opcode::BHEI(r), - Opcode::BURN(r), - Opcode::CALL(r, r, r, r), - Opcode::CCP(r, r, r, r), - Opcode::CROO(r, r), - Opcode::CSIZ(r, r), - Opcode::CB(r), - Opcode::LDC(r, r, r), - Opcode::LOG(r, r, r, r), - Opcode::LOGD(r, r, r, r), - Opcode::MINT(r), - Opcode::RVRT(r), - Opcode::SMO(r, r, r, r), - Opcode::SCWQ(r, r, r), - Opcode::SRW(r, r, r), - Opcode::SRWQ(r, r, r, r), - Opcode::SWW(r, r, r), - Opcode::SWWQ(r, r, r, r), - Opcode::TIME(r, r), - Opcode::TR(r, r, r), - Opcode::TRO(r, r, r, r), - Opcode::ECR(r, r, r), - Opcode::K256(r, r, r), - Opcode::S256(r, r, r), - Opcode::NOOP, - Opcode::FLAG(r), - Opcode::GM(r, imm18), - Opcode::gm(r, GMArgs::IsCallerExternal), - Opcode::gm(r, GMArgs::GetCaller), - Opcode::gm(r, GMArgs::GetVerifyingPredicate), - Opcode::GTF(r, r, imm12), - Opcode::gtf(r, r, GTFArgs::Type), - Opcode::gtf(r, r, GTFArgs::ScriptGasPrice), - Opcode::gtf(r, r, GTFArgs::ScriptGasLimit), - Opcode::gtf(r, r, GTFArgs::ScriptMaturity), - Opcode::gtf(r, r, GTFArgs::ScriptLength), - Opcode::gtf(r, r, GTFArgs::ScriptDataLength), - Opcode::gtf(r, r, GTFArgs::ScriptInputsCount), - Opcode::gtf(r, r, GTFArgs::ScriptOutputsCount), - Opcode::gtf(r, r, GTFArgs::ScriptWitnessesCound), - Opcode::gtf(r, r, GTFArgs::ScriptReceiptsRoot), - Opcode::gtf(r, r, GTFArgs::Script), - Opcode::gtf(r, r, GTFArgs::ScriptData), - Opcode::gtf(r, r, GTFArgs::ScriptInputAtIndex), - Opcode::gtf(r, r, GTFArgs::ScriptOutputAtIndex), - Opcode::gtf(r, r, GTFArgs::ScriptWitnessAtIndex), - Opcode::gtf(r, r, GTFArgs::CreateGasPrice), - Opcode::gtf(r, r, GTFArgs::CreateGasLimit), - Opcode::gtf(r, r, GTFArgs::CreateMaturity), - Opcode::gtf(r, r, GTFArgs::CreateBytecodeLength), - Opcode::gtf(r, r, GTFArgs::CreateBytecodeWitnessIndex), - Opcode::gtf(r, r, GTFArgs::CreateStorageSlotsCount), - Opcode::gtf(r, r, GTFArgs::CreateInputsCount), - Opcode::gtf(r, r, GTFArgs::CreateOutputsCount), - Opcode::gtf(r, r, GTFArgs::CreateWitnessesCount), - Opcode::gtf(r, r, GTFArgs::CreateSalt), - Opcode::gtf(r, r, GTFArgs::CreateStorageSlotAtIndex), - Opcode::gtf(r, r, GTFArgs::CreateInputAtIndex), - Opcode::gtf(r, r, GTFArgs::CreateOutputAtIndex), - Opcode::gtf(r, r, GTFArgs::CreateWitnessAtIndex), - Opcode::gtf(r, r, GTFArgs::InputType), - Opcode::gtf(r, r, GTFArgs::InputCoinTxId), - Opcode::gtf(r, r, GTFArgs::InputCoinOutputIndex), - Opcode::gtf(r, r, GTFArgs::InputCoinOwner), - Opcode::gtf(r, r, GTFArgs::InputCoinAmount), - Opcode::gtf(r, r, GTFArgs::InputCoinAssetId), - Opcode::gtf(r, r, GTFArgs::InputCoinTxPointer), - Opcode::gtf(r, r, GTFArgs::InputCoinWitnessIndex), - Opcode::gtf(r, r, GTFArgs::InputCoinMaturity), - Opcode::gtf(r, r, GTFArgs::InputCoinPredicateLength), - Opcode::gtf(r, r, GTFArgs::InputCoinPredicateDataLength), - Opcode::gtf(r, r, GTFArgs::InputCoinPredicate), - Opcode::gtf(r, r, GTFArgs::InputCoinPredicateData), - Opcode::gtf(r, r, GTFArgs::InputContractTxId), - Opcode::gtf(r, r, GTFArgs::InputContractOutputIndex), - Opcode::gtf(r, r, GTFArgs::InputContractBalanceRoot), - Opcode::gtf(r, r, GTFArgs::InputContractStateRoot), - Opcode::gtf(r, r, GTFArgs::InputContractTxPointer), - Opcode::gtf(r, r, GTFArgs::InputContractId), - Opcode::gtf(r, r, GTFArgs::InputMessageId), - Opcode::gtf(r, r, GTFArgs::InputMessageSender), - Opcode::gtf(r, r, GTFArgs::InputMessageRecipient), - Opcode::gtf(r, r, GTFArgs::InputMessageAmount), - Opcode::gtf(r, r, GTFArgs::InputMessageNonce), - Opcode::gtf(r, r, GTFArgs::InputMessageWitnessIndex), - Opcode::gtf(r, r, GTFArgs::InputMessageDataLength), - Opcode::gtf(r, r, GTFArgs::InputMessagePredicateLength), - Opcode::gtf(r, r, GTFArgs::InputMessagePredicateDataLength), - Opcode::gtf(r, r, GTFArgs::InputMessageData), - Opcode::gtf(r, r, GTFArgs::InputMessagePredicate), - Opcode::gtf(r, r, GTFArgs::InputMessagePredicateData), - Opcode::gtf(r, r, GTFArgs::OutputType), - Opcode::gtf(r, r, GTFArgs::OutputCoinTo), - Opcode::gtf(r, r, GTFArgs::OutputCoinAmount), - Opcode::gtf(r, r, GTFArgs::OutputCoinAssetId), - Opcode::gtf(r, r, GTFArgs::OutputContractInputIndex), - Opcode::gtf(r, r, GTFArgs::OutputContractBalanceRoot), - Opcode::gtf(r, r, GTFArgs::OutputContractStateRoot), - Opcode::gtf(r, r, GTFArgs::OutputMessageRecipient), - Opcode::gtf(r, r, GTFArgs::OutputMessageAmount), - Opcode::gtf(r, r, GTFArgs::OutputContractCreatedContractId), - Opcode::gtf(r, r, GTFArgs::OutputContractCreatedStateRoot), - Opcode::gtf(r, r, GTFArgs::WitnessDataLength), - Opcode::gtf(r, r, GTFArgs::WitnessData), - Opcode::Undefined, + let mut instructions = vec![ + op::add(r, r, r), + op::addi(r, r, imm12), + op::and(r, r, r), + op::andi(r, r, imm12), + op::div(r, r, r), + op::divi(r, r, imm12), + op::eq(r, r, r), + op::exp(r, r, r), + op::expi(r, r, imm12), + op::gt(r, r, r), + op::lt(r, r, r), + op::mlog(r, r, r), + op::mroo(r, r, r), + op::mod_(r, r, r), + op::modi(r, r, imm12), + op::move_(r, r), + op::movi(r, imm18), + op::mul(r, r, r), + op::muli(r, r, imm12), + op::not(r, r), + op::or(r, r, r), + op::ori(r, r, imm12), + op::sll(r, r, r), + op::slli(r, r, imm12), + op::srl(r, r, r), + op::srli(r, r, imm12), + op::sub(r, r, r), + op::subi(r, r, imm12), + op::xor(r, r, r), + op::xori(r, r, imm12), + op::ji(imm24), + op::jnei(r, r, imm12), + op::jnzi(r, imm18), + op::jmp(r), + op::jne(r, r, r), + op::ret(r), + op::retd(r, r), + op::cfei(imm24), + op::cfsi(imm24), + op::lb(r, r, imm12), + op::lw(r, r, imm12), + op::aloc(r), + op::mcl(r, r), + op::mcli(r, imm18), + op::mcp(r, r, r), + op::mcpi(r, r, imm12), + op::meq(r, r, r, r), + op::sb(r, r, imm12), + op::sw(r, r, imm12), + op::bal(r, r, r), + op::bhsh(r, r), + op::bhei(r), + op::burn(r), + op::call(r, r, r, r), + op::ccp(r, r, r, r), + op::croo(r, r), + op::csiz(r, r), + op::cb(r), + op::ldc(r, r, r), + op::log(r, r, r, r), + op::logd(r, r, r, r), + op::mint(r), + op::rvrt(r), + op::smo(r, r, r, r), + op::scwq(r, r, r), + op::srw(r, r, r), + op::srwq(r, r, r, r), + op::sww(r, r, r), + op::swwq(r, r, r, r), + op::time(r, r), + op::tr(r, r, r), + op::tro(r, r, r, r), + op::ecr(r, r, r), + op::k256(r, r, r), + op::s256(r, r, r), + op::noop(), + op::flag(r), + op::gm(r, imm18), + op::gm_args(r, GMArgs::IsCallerExternal), + op::gm_args(r, GMArgs::GetCaller), + op::gm_args(r, GMArgs::GetVerifyingPredicate), + op::gtf(r, r, imm12), + op::gtf_args(r, r, GTFArgs::Type), + op::gtf_args(r, r, GTFArgs::ScriptGasPrice), + op::gtf_args(r, r, GTFArgs::ScriptGasLimit), + op::gtf_args(r, r, GTFArgs::ScriptMaturity), + op::gtf_args(r, r, GTFArgs::ScriptLength), + op::gtf_args(r, r, GTFArgs::ScriptDataLength), + op::gtf_args(r, r, GTFArgs::ScriptInputsCount), + op::gtf_args(r, r, GTFArgs::ScriptOutputsCount), + op::gtf_args(r, r, GTFArgs::ScriptWitnessesCound), + op::gtf_args(r, r, GTFArgs::ScriptReceiptsRoot), + op::gtf_args(r, r, GTFArgs::Script), + op::gtf_args(r, r, GTFArgs::ScriptData), + op::gtf_args(r, r, GTFArgs::ScriptInputAtIndex), + op::gtf_args(r, r, GTFArgs::ScriptOutputAtIndex), + op::gtf_args(r, r, GTFArgs::ScriptWitnessAtIndex), + op::gtf_args(r, r, GTFArgs::CreateGasPrice), + op::gtf_args(r, r, GTFArgs::CreateGasLimit), + op::gtf_args(r, r, GTFArgs::CreateMaturity), + op::gtf_args(r, r, GTFArgs::CreateBytecodeLength), + op::gtf_args(r, r, GTFArgs::CreateBytecodeWitnessIndex), + op::gtf_args(r, r, GTFArgs::CreateStorageSlotsCount), + op::gtf_args(r, r, GTFArgs::CreateInputsCount), + op::gtf_args(r, r, GTFArgs::CreateOutputsCount), + op::gtf_args(r, r, GTFArgs::CreateWitnessesCount), + op::gtf_args(r, r, GTFArgs::CreateSalt), + op::gtf_args(r, r, GTFArgs::CreateStorageSlotAtIndex), + op::gtf_args(r, r, GTFArgs::CreateInputAtIndex), + op::gtf_args(r, r, GTFArgs::CreateOutputAtIndex), + op::gtf_args(r, r, GTFArgs::CreateWitnessAtIndex), + op::gtf_args(r, r, GTFArgs::InputType), + op::gtf_args(r, r, GTFArgs::InputCoinTxId), + op::gtf_args(r, r, GTFArgs::InputCoinOutputIndex), + op::gtf_args(r, r, GTFArgs::InputCoinOwner), + op::gtf_args(r, r, GTFArgs::InputCoinAmount), + op::gtf_args(r, r, GTFArgs::InputCoinAssetId), + op::gtf_args(r, r, GTFArgs::InputCoinTxPointer), + op::gtf_args(r, r, GTFArgs::InputCoinWitnessIndex), + op::gtf_args(r, r, GTFArgs::InputCoinMaturity), + op::gtf_args(r, r, GTFArgs::InputCoinPredicateLength), + op::gtf_args(r, r, GTFArgs::InputCoinPredicateDataLength), + op::gtf_args(r, r, GTFArgs::InputCoinPredicate), + op::gtf_args(r, r, GTFArgs::InputCoinPredicateData), + op::gtf_args(r, r, GTFArgs::InputContractTxId), + op::gtf_args(r, r, GTFArgs::InputContractOutputIndex), + op::gtf_args(r, r, GTFArgs::InputContractBalanceRoot), + op::gtf_args(r, r, GTFArgs::InputContractStateRoot), + op::gtf_args(r, r, GTFArgs::InputContractTxPointer), + op::gtf_args(r, r, GTFArgs::InputContractId), + op::gtf_args(r, r, GTFArgs::InputMessageId), + op::gtf_args(r, r, GTFArgs::InputMessageSender), + op::gtf_args(r, r, GTFArgs::InputMessageRecipient), + op::gtf_args(r, r, GTFArgs::InputMessageAmount), + op::gtf_args(r, r, GTFArgs::InputMessageNonce), + op::gtf_args(r, r, GTFArgs::InputMessageWitnessIndex), + op::gtf_args(r, r, GTFArgs::InputMessageDataLength), + op::gtf_args(r, r, GTFArgs::InputMessagePredicateLength), + op::gtf_args(r, r, GTFArgs::InputMessagePredicateDataLength), + op::gtf_args(r, r, GTFArgs::InputMessageData), + op::gtf_args(r, r, GTFArgs::InputMessagePredicate), + op::gtf_args(r, r, GTFArgs::InputMessagePredicateData), + op::gtf_args(r, r, GTFArgs::OutputType), + op::gtf_args(r, r, GTFArgs::OutputCoinTo), + op::gtf_args(r, r, GTFArgs::OutputCoinAmount), + op::gtf_args(r, r, GTFArgs::OutputCoinAssetId), + op::gtf_args(r, r, GTFArgs::OutputContractInputIndex), + op::gtf_args(r, r, GTFArgs::OutputContractBalanceRoot), + op::gtf_args(r, r, GTFArgs::OutputContractStateRoot), + op::gtf_args(r, r, GTFArgs::OutputMessageRecipient), + op::gtf_args(r, r, GTFArgs::OutputMessageAmount), + op::gtf_args(r, r, GTFArgs::OutputContractCreatedContractId), + op::gtf_args(r, r, GTFArgs::OutputContractCreatedStateRoot), + op::gtf_args(r, r, GTFArgs::WitnessDataLength), + op::gtf_args(r, r, GTFArgs::WitnessData), ]; // Pad to even length - if data.len() % 2 != 0 { - data.push(Opcode::Undefined); + if instructions.len() % 2 != 0 { + instructions.push(op::noop()); } - let bytes: Vec = data.iter().copied().collect(); + let bytes: Vec = instructions.iter().copied().collect(); - let data_p = Opcode::from_bytes_iter(bytes.clone()); - let data_q = Instruction::from_bytes_iter(bytes.clone()); - let data_q: Vec = data_q.into_iter().collect(); + let instructions_from_bytes: Result, _> = fuel_asm::from_bytes(bytes.iter().copied()).collect(); - assert_eq!(data, data_p); - assert_eq!(data, data_q); + assert_eq!(instructions, instructions_from_bytes.unwrap()); let pairs = bytes.chunks(8).map(|chunk| { let mut arr = [0; core::mem::size_of::()]; arr.copy_from_slice(chunk); - Instruction::parse_word(Word::from_be_bytes(arr)) + Word::from_be_bytes(arr) }); - let result: Vec = pairs + let instructions_from_words: Vec = pairs .into_iter() - .flat_map(|(a, b)| [Opcode::from(a), Opcode::from(b)]) + .flat_map(raw_instructions_from_word) + .map(|raw| Instruction::try_from(raw).unwrap()) .collect(); - assert_eq!(data, result); - - let mut bytes: Vec = vec![]; - let mut buffer = [0u8; Opcode::LEN]; - - for mut op in data.clone() { - let _ = op.read(&mut buffer).expect("Failed to write opcode to buffer"); - bytes.extend(buffer); - - let op_p = u32::from(op); - let op_bytes = op_p.to_be_bytes().to_vec(); - - let ins = Instruction::from(op_p); - let ins_p = Instruction::from(op); - - assert_eq!(ins, ins_p); - - let (_, _, _, _, _, imm_ins) = ins.into_inner(); - let imm_op = op.immediate().unwrap_or_default(); - - assert_eq!(imm_op, imm_ins); - - let op_p = Opcode::from(op_p); - let op_q = unsafe { Opcode::from_bytes_unchecked(op_bytes.as_slice()) }; - - assert_eq!(op, Opcode::from(ins)); - assert_eq!(op, op_p); - assert_eq!(op, op_q); - - let mut op_bytes = op.to_bytes().to_vec(); - - // Assert opcode can be created from big slices - op_bytes.extend_from_slice(&[0xff; 25]); - while op_bytes.len() > Opcode::LEN { - op_bytes.pop(); - - let op_r = unsafe { Opcode::from_bytes_unchecked(op_bytes.as_slice()) }; - let op_s = Opcode::from_bytes(op_bytes.as_slice()).expect("Failed to safely generate op from bytes!"); - - assert_eq!(op, op_r); - assert_eq!(op, op_s); - - let ins_r = unsafe { Instruction::from_slice_unchecked(op_bytes.as_slice()) }; - let ins_s = Instruction::from_bytes(op_bytes.as_slice()).expect("Failed to safely generate op from bytes!"); - - assert_eq!(op, Opcode::from(ins_r)); - assert_eq!(op, Opcode::from(ins_s)); - } - - // Assert no panic with checked function - while !op_bytes.is_empty() { - op_bytes.pop(); - - assert!(Opcode::from_bytes(op_bytes.as_slice()).is_err()); - } - - #[cfg(feature = "serde")] - { - let op_s = bincode::serialize(&op).expect("Failed to serialize opcode"); - let op_s: Opcode = bincode::deserialize(&op_s).expect("Failed to deserialize opcode"); - - assert_eq!(op_s, op); - } + #[cfg(feature = "serde")] + for ins in &instructions { + let ins_ser = bincode::serialize(ins).expect("Failed to serialize opcode"); + let ins_de: Instruction = bincode::deserialize(&ins_ser).expect("Failed to serialize opcode"); + assert_eq!(ins, &ins_de); } - let mut op_p = Opcode::Undefined; - bytes.chunks(Opcode::LEN).zip(data.iter()).for_each(|(chunk, op)| { - let _ = op_p.write(chunk).expect("Failed to parse opcode from chunk"); - - assert_eq!(op, &op_p); - }); + assert_eq!(instructions, instructions_from_words); } #[test] @@ -306,11 +239,9 @@ fn panic_reason_description() { PanicReason::IllegalJump, ]; - let pd = InstructionResult::success(); - + let pd = InstructionResult::error(PanicReason::Success, op::noop().into()); let w = Word::from(pd); let pd_p = InstructionResult::from(w); - assert_eq!(pd, pd_p); #[cfg(feature = "serde")] @@ -322,21 +253,17 @@ fn panic_reason_description() { } for r in reasons { - let b = u8::from(r); + let b = r as u8; let r_p = PanicReason::from(b); - - let w = Word::from(r); - let r_q = PanicReason::from(w); - + let w = Word::from(r as u8); + let r_q = PanicReason::from(u8::try_from(w).unwrap()); assert_eq!(r, r_p); assert_eq!(r, r_q); - let op = Opcode::JI(imm24); + let op = op::ji(imm24); let pd = InstructionResult::error(r, op.into()); - let w = Word::from(pd); let pd_p = InstructionResult::from(w); - assert_eq!(pd, pd_p); #[cfg(feature = "serde")] diff --git a/fuel-tx/src/transaction/types/script.rs b/fuel-tx/src/transaction/types/script.rs index 6229e8dbed..315868766b 100644 --- a/fuel-tx/src/transaction/types/script.rs +++ b/fuel-tx/src/transaction/types/script.rs @@ -16,7 +16,6 @@ use std::io; #[cfg(feature = "alloc")] use alloc::vec::Vec; -use fuel_asm::Opcode; #[cfg(feature = "std")] use fuel_types::bytes::SerializableVec; @@ -50,7 +49,7 @@ impl Default for Script { // Create a valid transaction with a single return instruction // // The Return op is mandatory for the execution of any context - let script = Opcode::RET(0x10).to_bytes().to_vec(); + let script = fuel_asm::op::ret(0x10).to_bytes().to_vec(); Self { gas_price: Default::default(), diff --git a/fuel-tx/tests/bytes.rs b/fuel-tx/tests/bytes.rs index fb6f561830..131726ada1 100644 --- a/fuel-tx/tests/bytes.rs +++ b/fuel-tx/tests/bytes.rs @@ -1,4 +1,4 @@ -use fuel_asm::Opcode; +use fuel_asm::op; use fuel_tx::*; use fuel_tx_test_helpers::{generate_bytes, generate_nonempty_padded_bytes}; use fuel_types::{bytes, Immediate24}; @@ -194,13 +194,15 @@ fn receipt() { ), Receipt::transfer(rng.gen(), rng.gen(), rng.gen(), rng.gen(), rng.gen(), rng.gen()), Receipt::transfer_out(rng.gen(), rng.gen(), rng.gen(), rng.gen(), rng.gen(), rng.gen()), - Receipt::panic(rng.gen(), InstructionResult::success(), rng.gen(), rng.gen()), Receipt::panic( rng.gen(), - InstructionResult::error( - PanicReason::Revert, - Opcode::JI(rng.gen::() & 0xffffff).into(), - ), + InstructionResult::error(PanicReason::Success, op::noop().into()), + rng.gen(), + rng.gen(), + ), + Receipt::panic( + rng.gen(), + InstructionResult::error(PanicReason::Revert, op::ji(rng.gen::() & 0xffffff).into()), rng.gen(), rng.gen(), ), @@ -208,7 +210,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::OutOfGas, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -217,7 +219,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::TransactionValidity, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -226,7 +228,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::MemoryOverflow, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -235,7 +237,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ArithmeticOverflow, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -244,7 +246,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ContractNotFound, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -253,7 +255,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::MemoryOwnership, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -262,7 +264,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::NotEnoughBalance, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -271,7 +273,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ExpectedInternalContext, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -280,7 +282,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::AssetIdNotFound, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -289,7 +291,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::InputNotFound, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -298,7 +300,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::OutputNotFound, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -307,7 +309,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::WitnessNotFound, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -316,7 +318,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::TransactionMaturity, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -325,7 +327,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::InvalidMetadataIdentifier, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -334,7 +336,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::MalformedCallStructure, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -343,7 +345,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ReservedRegisterNotWritable, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -352,7 +354,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ErrorFlag, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -361,7 +363,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::InvalidImmediateValue, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -370,7 +372,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ExpectedCoinInput, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -379,7 +381,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::MaxMemoryAccess, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -388,7 +390,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::MemoryWriteOverlap, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -397,7 +399,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ContractNotInInputs, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -406,7 +408,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::InternalBalanceOverflow, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -415,7 +417,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ContractMaxSize, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -424,7 +426,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ExpectedUnallocatedStack, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -433,7 +435,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::TransferAmountCannotBeZero, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -442,7 +444,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ExpectedOutputVariable, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -451,7 +453,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ExpectedParentInternalContext, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), @@ -460,7 +462,7 @@ fn receipt() { rng.gen(), InstructionResult::error( PanicReason::ContractNotInInputs, - Opcode::JI(rng.gen::() & 0xffffff).into(), + op::ji(rng.gen::() & 0xffffff).into(), ), rng.gen(), rng.gen(), diff --git a/fuel-vm/src/call.rs b/fuel-vm/src/call.rs index 195e0236b7..a3b683ebe0 100644 --- a/fuel-vm/src/call.rs +++ b/fuel-vm/src/call.rs @@ -1,12 +1,11 @@ //! Inter-contract call supporting structures -use crate::consts::*; - -use fuel_asm::PanicReason; +use fuel_asm::{PanicReason, RegId}; use fuel_tx::Contract; use fuel_types::bytes::{self, SizedBytes}; use fuel_types::{AssetId, ContractId, Word}; +use crate::consts::*; use crate::{arith::checked_add_usize, consts::WORD_SIZE}; use std::io::{self, Write}; @@ -185,8 +184,8 @@ impl CallFrame { } /// Gas context prior to the called execution. - pub const fn context_gas(&self) -> Word { - self.registers[REG_CGAS] + pub fn context_gas(&self) -> Word { + self.registers[RegId::CGAS] } /// Asset ID of forwarded coins. @@ -196,12 +195,12 @@ impl CallFrame { /// Set the value of the context gas for this call frame. pub fn set_context_gas(&mut self) -> &mut Word { - &mut self.registers[REG_CGAS] + &mut self.registers[RegId::CGAS] } /// Set the value of the global gas for this call frame. pub fn set_global_gas(&mut self) -> &mut Word { - &mut self.registers[REG_GGAS] + &mut self.registers[RegId::GGAS] } } diff --git a/fuel-vm/src/checked_transaction.rs b/fuel-vm/src/checked_transaction.rs index 2dd17e3c3f..d316b832a2 100644 --- a/fuel-vm/src/checked_transaction.rs +++ b/fuel-vm/src/checked_transaction.rs @@ -341,6 +341,7 @@ impl IntoChecked for Transaction { #[cfg(test)] mod tests { use super::*; + use fuel_asm::op; use fuel_crypto::SecretKey; use fuel_tx::{CheckError, Script, TransactionBuilder}; use quickcheck::TestResult; @@ -790,7 +791,7 @@ mod tests { // used when proptesting to avoid expensive crypto signatures fn predicate_tx(rng: &mut StdRng, gas_price: u64, gas_limit: u64, fee_input_amount: u64) -> Script { let asset = AssetId::default(); - let predicate = vec![Opcode::RET(1)].into_iter().collect::>(); + let predicate = vec![op::ret(1)].into_iter().collect::>(); let owner = Input::predicate_owner(&predicate); TransactionBuilder::script(vec![], vec![]) .gas_price(gas_price) diff --git a/fuel-vm/src/consts.rs b/fuel-vm/src/consts.rs index 99ebd63727..79bee559f4 100644 --- a/fuel-vm/src/consts.rs +++ b/fuel-vm/src/consts.rs @@ -4,65 +4,9 @@ use fuel_types::{Bytes32, Word}; use std::mem; -/* FLAG AND REGISTER TYPES */ - /// Register count for checking constraints pub const VM_REGISTER_COUNT: usize = 64; -/// Contains zero (0), for convenience. -pub const REG_ZERO: usize = 0x00; - -/// Contains one (1), for convenience. -pub const REG_ONE: usize = 0x01; - -/// Contains overflow/underflow of addition, subtraction, and multiplication. -pub const REG_OF: usize = 0x02; - -/// The program counter. Memory address of the current instruction. -pub const REG_PC: usize = 0x03; - -/// Memory address of bottom of current writable stack area. -pub const REG_SSP: usize = 0x04; - -/// Memory address on top of current writable stack area (points to free -/// memory). -pub const REG_SP: usize = 0x05; - -/// Memory address of beginning of current call frame. -pub const REG_FP: usize = 0x06; - -/// Memory address below the current bottom of the heap (points to free memory). -pub const REG_HP: usize = 0x07; - -/// Error codes for particular operations. -pub const REG_ERR: usize = 0x08; - -/// Remaining gas globally. -pub const REG_GGAS: usize = 0x09; - -/// Remaining gas in the context. -pub const REG_CGAS: usize = 0x0a; - -/// Received balance for this context. -pub const REG_BAL: usize = 0x0b; - -/// Pointer to the start of the currently-executing code. -pub const REG_IS: usize = 0x0c; - -/// Return value or pointer. -pub const REG_RET: usize = 0x0d; - -/// Return value length in bytes. -pub const REG_RETL: usize = 0x0e; - -/// Flags register. -pub const REG_FLAG: usize = 0x0f; - -/// Smallest writable register -pub const REG_WRITABLE: usize = 0x10; - -/* END */ - /* MEMORY TYPES */ /// Length of a word, in bytes diff --git a/fuel-vm/src/error.rs b/fuel-vm/src/error.rs index 8f5fb670da..9a29216fe6 100644 --- a/fuel-vm/src/error.rs +++ b/fuel-vm/src/error.rs @@ -1,6 +1,6 @@ //! Runtime interpreter error implementation -use fuel_asm::{Instruction, InstructionResult, PanicReason}; +use fuel_asm::{InstructionResult, PanicReason, RawInstruction}; use fuel_tx::CheckError; use thiserror::Error; @@ -42,10 +42,10 @@ pub enum InterpreterError { impl InterpreterError { /// Describe the error as recoverable or halt. - pub fn from_runtime(error: RuntimeError, instruction: Instruction) -> Self { + pub fn from_runtime(error: RuntimeError, instruction: RawInstruction) -> Self { match error { RuntimeError::Recoverable(reason) => Self::PanicInstruction(InstructionResult::error(reason, instruction)), - RuntimeError::Halt(e) => Self::Io(e), + _ => Self::from(error), } } @@ -59,7 +59,7 @@ impl InterpreterError { } /// Return the instruction that caused this error, if applicable. - pub const fn instruction(&self) -> Option<&Instruction> { + pub const fn instruction(&self) -> Option<&RawInstruction> { match self { Self::PanicInstruction(result) => Some(result.instruction()), _ => None, @@ -68,9 +68,9 @@ impl InterpreterError { /// Return the underlying `InstructionResult` if this instance is /// `PanicInstruction`; returns `None` otherwise. - pub fn instruction_result(&self) -> Option<&InstructionResult> { + pub fn instruction_result(&self) -> Option { match self { - Self::PanicInstruction(r) => Some(r), + Self::PanicInstruction(r) => Some(*r), _ => None, } } @@ -84,12 +84,6 @@ impl InterpreterError { } } -impl From for InterpreterError { - fn from(r: InstructionResult) -> InterpreterError { - Self::PanicInstruction(r) - } -} - impl From for InterpreterError { fn from(error: RuntimeError) -> Self { match error { @@ -168,7 +162,6 @@ impl PartialEq for RuntimeError { match (self, other) { (RuntimeError::Recoverable(s), RuntimeError::Recoverable(o)) => s == o, (RuntimeError::Halt(s), RuntimeError::Halt(o)) => s.kind() == o.kind(), - _ => false, } } diff --git a/fuel-vm/src/interpreter.rs b/fuel-vm/src/interpreter.rs index 4ffdd54ca7..94d6f9f721 100644 --- a/fuel-vm/src/interpreter.rs +++ b/fuel-vm/src/interpreter.rs @@ -5,7 +5,7 @@ use crate::consts::*; use crate::context::Context; use crate::gas::GasCosts; use crate::state::Debugger; -use fuel_asm::PanicReason; +use fuel_asm::{PanicReason, RegId}; use std::collections::BTreeMap; use std::io::Read; use std::ops::Index; @@ -111,12 +111,12 @@ impl Interpreter { &self.debugger } - pub(crate) const fn is_unsafe_math(&self) -> bool { - self.registers[REG_FLAG] & 0x01 == 0x01 + pub(crate) fn is_unsafe_math(&self) -> bool { + self.registers[RegId::FLAG] & 0x01 == 0x01 } - pub(crate) const fn is_wrapping(&self) -> bool { - self.registers[REG_FLAG] & 0x02 == 0x02 + pub(crate) fn is_wrapping(&self) -> bool { + self.registers[RegId::FLAG] & 0x02 == 0x02 } /// The current transaction. @@ -146,10 +146,9 @@ impl Interpreter { #[cfg(feature = "profile-gas")] fn current_location(&self) -> InstructionLocation { - use crate::consts::*; InstructionLocation::new( self.frames.last().map(|frame| *frame.to()), - self.registers[REG_PC] - self.registers[REG_IS], + self.registers[RegId::PC] - self.registers[RegId::IS], ) } diff --git a/fuel-vm/src/interpreter/alu.rs b/fuel-vm/src/interpreter/alu.rs index ebaca356c9..0013cd0a06 100644 --- a/fuel-vm/src/interpreter/alu.rs +++ b/fuel-vm/src/interpreter/alu.rs @@ -1,15 +1,14 @@ use super::{ExecutableTransaction, Interpreter}; -use crate::consts::*; use crate::error::RuntimeError; -use fuel_asm::PanicReason; +use fuel_asm::{PanicReason, RegId}; use fuel_types::{RegisterId, Word}; impl Interpreter where Tx: ExecutableTransaction, { - /// Stores the overflowed wrapped value into REG_OF + /// Stores the overflowed wrapped value into RegId::OF pub(crate) fn alu_capture_overflow(&mut self, ra: RegisterId, f: F, b: B, c: C) -> Result<(), RuntimeError> where F: FnOnce(B, C) -> (u128, bool), @@ -23,8 +22,8 @@ where } // set the OF register to high bits of the u128 result - self.registers[REG_OF] = (result >> 64) as u64; - self.registers[REG_ERR] = 0; + self.registers[RegId::OF] = (result >> 64) as u64; + self.registers[RegId::ERR] = 0; // set the return value to the low bits of the u128 result self.registers[ra] = (result & Word::MAX as u128) as u64; @@ -32,7 +31,7 @@ where self.inc_pc() } - /// Set REG_OF to true and zero the result register if overflow occurred. + /// Set RegId::OF to true and zero the result register if overflow occurred. pub(crate) fn alu_boolean_overflow(&mut self, ra: RegisterId, f: F, b: B, c: C) -> Result<(), RuntimeError> where F: FnOnce(B, C) -> (Word, bool), @@ -46,8 +45,8 @@ where } // set the OF register to 1 if an overflow occurred - self.registers[REG_OF] = overflow as Word; - self.registers[REG_ERR] = 0; + self.registers[RegId::OF] = overflow as Word; + self.registers[RegId::ERR] = 0; self.registers[ra] = if overflow { 0 } else { result }; @@ -64,8 +63,8 @@ where return Err(PanicReason::ErrorFlag.into()); } - self.registers[REG_OF] = 0; - self.registers[REG_ERR] = err as Word; + self.registers[RegId::OF] = 0; + self.registers[RegId::ERR] = err as Word; self.registers[ra] = if err { 0 } else { f(b, c) }; @@ -75,8 +74,8 @@ where pub(crate) fn alu_set(&mut self, ra: RegisterId, b: Word) -> Result<(), RuntimeError> { Self::is_register_writable(ra)?; - self.registers[REG_OF] = 0; - self.registers[REG_ERR] = 0; + self.registers[RegId::OF] = 0; + self.registers[RegId::ERR] = 0; self.registers[ra] = b; @@ -84,8 +83,8 @@ where } pub(crate) fn alu_clear(&mut self) -> Result<(), RuntimeError> { - self.registers[REG_OF] = 0; - self.registers[REG_ERR] = 0; + self.registers[RegId::OF] = 0; + self.registers[RegId::ERR] = 0; self.inc_pc() } diff --git a/fuel-vm/src/interpreter/balances.rs b/fuel-vm/src/interpreter/balances.rs index e790f0a73a..b6c6f57f36 100644 --- a/fuel-vm/src/interpreter/balances.rs +++ b/fuel-vm/src/interpreter/balances.rs @@ -1,7 +1,7 @@ use crate::consts::*; use crate::interpreter::{ExecutableTransaction, InitialBalances, Interpreter}; -use fuel_asm::Word; +use fuel_asm::{RegId, Word}; use fuel_tx::CheckError; use fuel_types::AssetId; use itertools::Itertools; @@ -128,7 +128,7 @@ impl RuntimeBalances { { let len = vm.params().max_inputs * (AssetId::LEN + WORD_SIZE) as Word; - vm.registers[REG_SP] += len; + vm.registers[RegId::SP] += len; vm.reserve_stack(len) .expect("consensus parameters won't allow stack overflow for VM initialization"); diff --git a/fuel-vm/src/interpreter/blockchain.rs b/fuel-vm/src/interpreter/blockchain.rs index 37f23a248d..a4b9503946 100644 --- a/fuel-vm/src/interpreter/blockchain.rs +++ b/fuel-vm/src/interpreter/blockchain.rs @@ -5,7 +5,7 @@ use crate::consts::*; use crate::error::{Bug, BugId, BugVariant, RuntimeError}; use crate::storage::InterpreterStorage; -use fuel_asm::PanicReason; +use fuel_asm::{PanicReason, RegId}; use fuel_tx::{Output, Receipt}; use fuel_types::bytes::{self, Deserializable}; use fuel_types::{Address, AssetId, Bytes32, Bytes8, ContractId, RegisterId, Word}; @@ -35,9 +35,9 @@ where /// mem[$ssp, $rC] = contract_code[$rB, $rC] /// ``` pub(crate) fn load_contract_code(&mut self, a: Word, b: Word, c: Word) -> Result<(), RuntimeError> { - let ssp = self.registers[REG_SSP]; - let sp = self.registers[REG_SP]; - let fp = self.registers[REG_FP] as usize; + let ssp = self.registers[RegId::SSP]; + let sp = self.registers[RegId::SP]; + let fp = self.registers[RegId::FP] as usize; if ssp != sp { return Err(PanicReason::ExpectedUnallocatedStack.into()); @@ -52,7 +52,7 @@ where let memory_offset_end = checked_add_usize(memory_offset, length)?; // Validate arguments - if memory_offset_end > self.registers[REG_HP] as usize + if memory_offset_end > self.registers[RegId::HP] as usize || contract_id_end as Word > VM_MAX_RAM || length > MEM_MAX_ACCESS_SIZE as usize || length > self.params.contract_max_size as usize @@ -97,12 +97,12 @@ where // perform the code copy memory.copy_from_slice(code); - self.registers[REG_SP] - //TODO this is looser than the compare against [REG_HP,REG_SSP+length] + self.registers[RegId::SP] + //TODO this is looser than the compare against [RegId::HP,RegId::SSP+length] .checked_add(length as Word) .map(|sp| { - self.registers[REG_SP] = sp; - self.registers[REG_SSP] = sp; + self.registers[RegId::SP] = sp; + self.registers[RegId::SSP] = sp; }) .ok_or_else(|| Bug::new(BugId::ID007, BugVariant::StackPointerOverflow))?; @@ -416,7 +416,7 @@ where self.base_asset_balance_sub(amount)?; - let fp = self.registers[REG_FP] as usize; + let fp = self.registers[RegId::FP] as usize; let txid = self.tx_id(); let data = ax; let data = &self.memory[data..bx]; diff --git a/fuel-vm/src/interpreter/contract.rs b/fuel-vm/src/interpreter/contract.rs index b75c767dde..075f841b2d 100644 --- a/fuel-vm/src/interpreter/contract.rs +++ b/fuel-vm/src/interpreter/contract.rs @@ -4,7 +4,7 @@ use crate::error::RuntimeError; use crate::interpreter::PanicContext; use crate::storage::InterpreterStorage; -use fuel_asm::{PanicReason, RegisterId, Word}; +use fuel_asm::{PanicReason, RegId, RegisterId, Word}; use fuel_tx::{Contract, Output, Receipt}; use fuel_types::{Address, AssetId, ContractId}; @@ -115,8 +115,8 @@ where destination, amount, asset_id, - self.registers[REG_PC], - self.registers[REG_IS], + self.registers[RegId::PC], + self.registers[RegId::IS], ); self.append_receipt(receipt); @@ -172,8 +172,8 @@ where to, amount, asset_id, - self.registers[REG_PC], - self.registers[REG_IS], + self.registers[RegId::PC], + self.registers[RegId::IS], ); self.append_receipt(receipt); diff --git a/fuel-vm/src/interpreter/debug.rs b/fuel-vm/src/interpreter/debug.rs index 45c78ad025..1bdf31d518 100644 --- a/fuel-vm/src/interpreter/debug.rs +++ b/fuel-vm/src/interpreter/debug.rs @@ -1,6 +1,6 @@ use super::Interpreter; -use crate::consts::*; use crate::prelude::*; +use fuel_asm::RegId; impl Interpreter where @@ -30,7 +30,7 @@ where let debugger = &mut self.debugger; let contract = self.frames.last().map(CallFrame::to); - let pc = self.registers[REG_PC].saturating_sub(self.registers[REG_IS]); + let pc = self.registers[RegId::PC].saturating_sub(self.registers[RegId::IS]); debugger.eval_state(contract, pc) } @@ -46,7 +46,7 @@ where #[test] fn breakpoint_script() { - use crate::consts::*; + use fuel_asm::op; let mut vm = Interpreter::with_memory_storage(); @@ -57,12 +57,12 @@ fn breakpoint_script() { let params = ConsensusParameters::default(); let script = [ - Opcode::ADDI(0x10, REG_ZERO, 8), - Opcode::ADDI(0x11, REG_ZERO, 16), - Opcode::ADDI(0x12, REG_ZERO, 32), - Opcode::ADDI(0x13, REG_ZERO, 64), - Opcode::ADDI(0x14, REG_ZERO, 128), - Opcode::RET(0x10), + op::addi(0x10, RegId::ZERO, 8), + op::addi(0x11, RegId::ZERO, 16), + op::addi(0x12, RegId::ZERO, 32), + op::addi(0x13, RegId::ZERO, 64), + op::addi(0x14, RegId::ZERO, 128), + op::ret(0x10), ] .into_iter() .collect(); @@ -112,6 +112,7 @@ fn breakpoint_script() { #[test] fn single_stepping() { + use fuel_asm::op; let mut vm = Interpreter::with_memory_storage(); let gas_price = 0; @@ -122,10 +123,10 @@ fn single_stepping() { // Repeats the middle two instructions five times let script = [ - Opcode::ADDI(0x10, REG_ZERO, 5), - Opcode::ADDI(0x11, 0x11, 1), - Opcode::JNEI(0x10, 0x11, 1), - Opcode::RET(0x10), + op::addi(0x10, RegId::ZERO, 5), + op::addi(0x11, 0x11, 1), + op::jnei(0x10, 0x11, 1), + op::ret(0x10), ] .into_iter() .collect(); diff --git a/fuel-vm/src/interpreter/diff/tests.rs b/fuel-vm/src/interpreter/diff/tests.rs index 303739f8eb..2a47830265 100644 --- a/fuel-vm/src/interpreter/diff/tests.rs +++ b/fuel-vm/src/interpreter/diff/tests.rs @@ -1,5 +1,4 @@ -use fuel_asm::Instruction; -use fuel_asm::Opcode; +use fuel_asm::op; use fuel_tx::field::Outputs; use fuel_tx::Script; use fuel_types::Address; @@ -26,7 +25,7 @@ fn reset_vm_state() { let a = Interpreter::<_, Script>::with_memory_storage(); let mut b = Interpreter::<_, Script>::with_memory_storage(); b.set_remaining_gas(1_000_000); - b.instruction(Instruction::from(Opcode::ADDI(0x10, 0x11, 1))).unwrap(); + b.instruction(op::addi(0x10, 0x11, 1)).unwrap(); let diff: Diff = a.diff(&b).into(); assert_ne!(a, b); b.reset_vm_state(&diff); @@ -53,7 +52,7 @@ fn record_and_invert_storage() { ) .unwrap(); b.set_remaining_gas(1_000_000); - b.instruction(Instruction::from(Opcode::ADDI(0x10, 0x11, 1))).unwrap(); + b.instruction(op::addi(0x10, 0x11, 1)).unwrap(); let storage_diff: Diff = b.storage_diff().into(); let mut diff: Diff = a.diff(&b).into(); @@ -73,7 +72,7 @@ fn record_and_invert_storage() { ) .unwrap(); d.set_remaining_gas(1_000_000); - d.instruction(Instruction::from(Opcode::ADDI(0x10, 0x11, 1))).unwrap(); + d.instruction(op::addi(0x10, 0x11, 1)).unwrap(); assert_ne!(c, d); d.reset_vm_state(&diff); diff --git a/fuel-vm/src/interpreter/executors/instruction.rs b/fuel-vm/src/interpreter/executors/instruction.rs index 2d3efacf06..448bf53c4a 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -4,8 +4,8 @@ use crate::interpreter::{ExecutableTransaction, Interpreter}; use crate::state::{ExecuteState, ProgramState}; use crate::storage::InterpreterStorage; -use fuel_asm::{Instruction, OpcodeRepr, PanicReason}; -use fuel_types::{bytes, Immediate12, Immediate18, Word}; +use fuel_asm::{Instruction, PanicReason, RawInstruction, RegId}; +use fuel_types::{bytes, Word}; use std::ops::Div; @@ -17,23 +17,23 @@ where /// Execute the current instruction pair located in `$m[$pc]`. pub fn execute(&mut self) -> Result { // Safety: `chunks_exact` is guaranteed to return a well-formed slice - let (hi, lo) = self.memory[self.registers[REG_PC] as usize..] + let [hi, lo] = self.memory[self.registers[RegId::PC] as usize..] .chunks_exact(WORD_SIZE) .next() .map(|b| unsafe { bytes::from_slice_unchecked(b) }) .map(Word::from_be_bytes) - .map(Instruction::parse_word) + .map(fuel_asm::raw_instructions_from_word) .ok_or(InterpreterError::Panic(PanicReason::MemoryOverflow))?; // Store the expected `$pc` after executing `hi` - let pc = self.registers[REG_PC] + Instruction::LEN as Word; + let pc = self.registers[RegId::PC] + Instruction::SIZE as Word; let state = self.instruction(hi)?; // TODO optimize // Should execute `lo` only if there is no rupture in the flow - that means // either a breakpoint or some instruction that would skip `lo` such as // `RET`, `JI` or `CALL` - if self.registers[REG_PC] == pc && state.should_continue() { + if self.registers[RegId::PC] == pc && state.should_continue() { self.instruction(lo) } else { Ok(state) @@ -41,7 +41,7 @@ where } /// Execute a provided instruction - pub fn instruction(&mut self, instruction: Instruction) -> Result { + pub fn instruction + Copy>(&mut self, raw: R) -> Result { #[cfg(feature = "debug")] { let debug = self.eval_debugger_state(); @@ -50,446 +50,539 @@ where } } - self._instruction(instruction) - .map_err(|e| InterpreterError::from_runtime(e, instruction)) + self._instruction(raw.into()) + .map_err(|e| InterpreterError::from_runtime(e, raw.into())) } #[tracing::instrument(name = "instruction", skip(self))] - fn _instruction(&mut self, instruction: Instruction) -> Result { - let (op, ra, rb, rc, rd, imm) = instruction.into_inner(); - let (a, b, c, d) = ( - self.registers[ra], - self.registers[rb], - self.registers[rc], - self.registers[rd], - ); + fn _instruction(&mut self, raw: RawInstruction) -> Result { + let instruction = Instruction::try_from(raw).map_err(|_| RuntimeError::from(PanicReason::ErrorFlag))?; - tracing::trace!("Op code: {:?}, Registers: a {}, b, {}, c {}, d {}", op, a, b, c, d); + tracing::trace!("Instruction: {:?}", instruction); // TODO additional branch that might be optimized after // https://github.com/FuelLabs/fuel-asm/issues/68 - if self.is_predicate() && !op.is_predicate_allowed() { + if self.is_predicate() && !instruction.opcode().is_predicate_allowed() { return Err(PanicReason::TransactionValidity.into()); } - match op { - OpcodeRepr::ADD => { + // Short-hand for retrieving the value from the register with the given ID. + // We use a macro to "close over" `self.registers` without taking ownership of it. + macro_rules! r { + ($id:expr) => { + self.registers[$id] + }; + } + + match instruction { + Instruction::ADD(add) => { self.gas_charge(self.gas_costs.add)?; - self.alu_capture_overflow(ra, u128::overflowing_add, b.into(), c.into())?; + let (a, b, c) = add.unpack(); + self.alu_capture_overflow(a.into(), u128::overflowing_add, r!(b).into(), r!(c).into())?; } - OpcodeRepr::ADDI => { + Instruction::ADDI(addi) => { self.gas_charge(self.gas_costs.addi)?; - self.alu_capture_overflow(ra, u128::overflowing_add, b.into(), imm.into())?; + let (a, b, imm) = addi.unpack(); + self.alu_capture_overflow(a.into(), u128::overflowing_add, r!(b).into(), imm.into())?; } - OpcodeRepr::AND => { + Instruction::AND(and) => { self.gas_charge(self.gas_costs.and)?; - self.alu_set(ra, b & c)?; + let (a, b, c) = and.unpack(); + self.alu_set(a.into(), r!(b) & r!(c))?; } - OpcodeRepr::ANDI => { + Instruction::ANDI(andi) => { self.gas_charge(self.gas_costs.andi)?; - self.alu_set(ra, b & imm)?; + let (a, b, imm) = andi.unpack(); + self.alu_set(a.into(), r!(b) & Word::from(imm))?; } - OpcodeRepr::DIV => { + Instruction::DIV(div) => { self.gas_charge(self.gas_costs.div)?; - self.alu_error(ra, Word::div, b, c, c == 0)?; + let (a, b, c) = div.unpack(); + let c = r!(c); + self.alu_error(a.into(), Word::div, r!(b), c, c == 0)?; } - OpcodeRepr::DIVI => { + Instruction::DIVI(divi) => { self.gas_charge(self.gas_costs.divi)?; - self.alu_error(ra, Word::div, b, imm, imm == 0)?; + let (a, b, imm) = divi.unpack(); + let imm = Word::from(imm); + self.alu_error(a.into(), Word::div, r!(b), imm, imm == 0)?; } - OpcodeRepr::EQ => { + Instruction::EQ(eq) => { self.gas_charge(self.gas_costs.eq)?; - self.alu_set(ra, (b == c) as Word)?; + let (a, b, c) = eq.unpack(); + self.alu_set(a.into(), (r!(b) == r!(c)) as Word)?; } - OpcodeRepr::EXP => { + Instruction::EXP(exp) => { self.gas_charge(self.gas_costs.exp)?; - self.alu_boolean_overflow(ra, Word::overflowing_pow, b, c as u32)?; + let (a, b, c) = exp.unpack(); + let expo = u32::try_from(r!(c)).expect("value out of range"); + self.alu_boolean_overflow(a.into(), Word::overflowing_pow, r!(b), expo)?; } - OpcodeRepr::EXPI => { + Instruction::EXPI(expi) => { self.gas_charge(self.gas_costs.expi)?; - self.alu_boolean_overflow(ra, Word::overflowing_pow, b, imm as u32)?; + let (a, b, imm) = expi.unpack(); + let expo = u32::from(imm); + self.alu_boolean_overflow(a.into(), Word::overflowing_pow, r!(b), expo)?; } - OpcodeRepr::GT => { + Instruction::GT(gt) => { self.gas_charge(self.gas_costs.gt)?; - self.alu_set(ra, (b > c) as Word)?; + let (a, b, c) = gt.unpack(); + self.alu_set(a.into(), (r!(b) > r!(c)) as Word)?; } - OpcodeRepr::LT => { + Instruction::LT(lt) => { self.gas_charge(self.gas_costs.lt)?; - self.alu_set(ra, (b < c) as Word)?; + let (a, b, c) = lt.unpack(); + self.alu_set(a.into(), (r!(b) < r!(c)) as Word)?; } - OpcodeRepr::MLOG => { + Instruction::MLOG(mlog) => { self.gas_charge(self.gas_costs.mlog)?; + let (a, b, c) = mlog.unpack(); + let (lhs, rhs) = (r!(b), r!(c)); self.alu_error( - ra, - |b, c| b.checked_ilog(c).expect("checked_ilog returned None for valid values") as Word, - b, - c, - b == 0 || c <= 1, + a.into(), + |l, r| l.checked_ilog(r).expect("checked_ilog returned None for valid values") as Word, + lhs, + rhs, + lhs == 0 || rhs <= 1, )?; } - OpcodeRepr::MOD => { + Instruction::MOD(mod_) => { self.gas_charge(self.gas_costs.mod_op)?; - self.alu_error(ra, Word::wrapping_rem, b, c, c == 0)?; + let (a, b, c) = mod_.unpack(); + let rhs = r!(c); + self.alu_error(a.into(), Word::wrapping_rem, r!(b), rhs, rhs == 0)?; } - OpcodeRepr::MODI => { + Instruction::MODI(modi) => { self.gas_charge(self.gas_costs.modi)?; - self.alu_error(ra, Word::wrapping_rem, b, imm, imm == 0)?; + let (a, b, imm) = modi.unpack(); + let rhs = Word::from(imm); + self.alu_error(a.into(), Word::wrapping_rem, r!(b), rhs, rhs == 0)?; } - OpcodeRepr::MOVE => { + Instruction::MOVE(move_) => { self.gas_charge(self.gas_costs.move_op)?; - self.alu_set(ra, b)?; + let (a, b) = move_.unpack(); + self.alu_set(a.into(), r!(b))?; } - OpcodeRepr::MOVI => { + Instruction::MOVI(movi) => { self.gas_charge(self.gas_costs.movi)?; - self.alu_set(ra, imm)?; + let (a, imm) = movi.unpack(); + self.alu_set(a.into(), Word::from(imm))?; } - OpcodeRepr::MROO => { + Instruction::MROO(mroo) => { self.gas_charge(self.gas_costs.mroo)?; + let (a, b, c) = mroo.unpack(); + let (lhs, rhs) = (r!(b), r!(c)); self.alu_error( - ra, - |b, c| checked_nth_root(b, c).expect("checked_nth_root returned None for valid values") as Word, - b, - c, - c == 0, + a.into(), + |l, r| checked_nth_root(l, r).expect("checked_nth_root returned None for valid values") as Word, + lhs, + rhs, + rhs == 0, )?; } - OpcodeRepr::MUL => { + Instruction::MUL(mul) => { self.gas_charge(self.gas_costs.mul)?; - self.alu_capture_overflow(ra, u128::overflowing_mul, b.into(), c.into())?; + let (a, b, c) = mul.unpack(); + self.alu_capture_overflow(a.into(), u128::overflowing_mul, r!(b).into(), r!(c).into())?; } - OpcodeRepr::MULI => { + Instruction::MULI(muli) => { self.gas_charge(self.gas_costs.muli)?; - self.alu_capture_overflow(ra, u128::overflowing_mul, b.into(), imm.into())?; + let (a, b, imm) = muli.unpack(); + self.alu_capture_overflow(a.into(), u128::overflowing_mul, r!(b).into(), imm.into())?; } - OpcodeRepr::NOOP => { + Instruction::NOOP(_noop) => { self.gas_charge(self.gas_costs.noop)?; self.alu_clear()?; } - OpcodeRepr::NOT => { + Instruction::NOT(not) => { self.gas_charge(self.gas_costs.not)?; - self.alu_set(ra, !b)?; + let (a, b) = not.unpack(); + self.alu_set(a.into(), !r!(b))?; } - OpcodeRepr::OR => { + Instruction::OR(or) => { self.gas_charge(self.gas_costs.or)?; - self.alu_set(ra, b | c)?; + let (a, b, c) = or.unpack(); + self.alu_set(a.into(), r!(b) | r!(c))?; } - OpcodeRepr::ORI => { + Instruction::ORI(ori) => { self.gas_charge(self.gas_costs.ori)?; - self.alu_set(ra, b | imm)?; + let (a, b, imm) = ori.unpack(); + self.alu_set(a.into(), r!(b) | Word::from(imm))?; } - OpcodeRepr::SLL => { + Instruction::SLL(sll) => { self.gas_charge(self.gas_costs.sll)?; - self.alu_set(ra, b.checked_shl(c as u32).unwrap_or_default())?; + let (a, b, c) = sll.unpack(); + let rhs = u32::try_from(r!(c)).expect("value out of range"); + self.alu_set(a.into(), r!(b).checked_shl(rhs).unwrap_or_default())?; } - OpcodeRepr::SLLI => { + Instruction::SLLI(slli) => { self.gas_charge(self.gas_costs.slli)?; - self.alu_set(ra, b.checked_shl(imm as u32).unwrap_or_default())?; + let (a, b, imm) = slli.unpack(); + let rhs = u32::from(imm); + self.alu_set(a.into(), r!(b).checked_shl(rhs).unwrap_or_default())?; } - OpcodeRepr::SRL => { + Instruction::SRL(srl) => { self.gas_charge(self.gas_costs.srl)?; - self.alu_set(ra, b.checked_shr(c as u32).unwrap_or_default())?; + let (a, b, c) = srl.unpack(); + let rhs = u32::try_from(r!(c)).expect("value out of range"); + self.alu_set(a.into(), r!(b).checked_shr(rhs).unwrap_or_default())?; } - OpcodeRepr::SRLI => { + Instruction::SRLI(srli) => { self.gas_charge(self.gas_costs.srli)?; - self.alu_set(ra, b.checked_shr(imm as u32).unwrap_or_default())?; + let (a, b, imm) = srli.unpack(); + let rhs = u32::from(imm); + self.alu_set(a.into(), r!(b).checked_shr(rhs).unwrap_or_default())?; } - OpcodeRepr::SUB => { + Instruction::SUB(sub) => { self.gas_charge(self.gas_costs.sub)?; - self.alu_capture_overflow(ra, u128::overflowing_sub, b.into(), c.into())?; + let (a, b, c) = sub.unpack(); + self.alu_capture_overflow(a.into(), u128::overflowing_sub, r!(b).into(), r!(c).into())?; } - OpcodeRepr::SUBI => { + Instruction::SUBI(subi) => { self.gas_charge(self.gas_costs.subi)?; - self.alu_capture_overflow(ra, u128::overflowing_sub, b.into(), imm.into())?; + let (a, b, imm) = subi.unpack(); + self.alu_capture_overflow(a.into(), u128::overflowing_sub, r!(b).into(), imm.into())?; } - OpcodeRepr::XOR => { + Instruction::XOR(xor) => { self.gas_charge(self.gas_costs.xor)?; - self.alu_set(ra, b ^ c)?; + let (a, b, c) = xor.unpack(); + self.alu_set(a.into(), r!(b) ^ r!(c))?; } - OpcodeRepr::XORI => { + Instruction::XORI(xori) => { self.gas_charge(self.gas_costs.xori)?; - self.alu_set(ra, b ^ imm)?; + let (a, b, imm) = xori.unpack(); + self.alu_set(a.into(), r!(b) ^ Word::from(imm))?; } - OpcodeRepr::JI => { + Instruction::JI(ji) => { self.gas_charge(self.gas_costs.ji)?; - self.jump(imm)?; + let imm = ji.unpack(); + self.jump(imm.into())?; } - OpcodeRepr::JNEI => { + Instruction::JNEI(jnei) => { self.gas_charge(self.gas_costs.jnei)?; - self.jump_not_equal(a, b, imm)?; + let (a, b, imm) = jnei.unpack(); + self.jump_not_equal(r!(a), r!(b), imm.into())?; } - OpcodeRepr::JNZI => { + Instruction::JNZI(jnzi) => { self.gas_charge(self.gas_costs.jnzi)?; - self.jump_not_zero(a, imm)?; + let (a, imm) = jnzi.unpack(); + self.jump_not_zero(r!(a), imm.into())?; } - OpcodeRepr::JMP => { + Instruction::JMP(jmp) => { self.gas_charge(self.gas_costs.jmp)?; - self.jump(a)?; + let a = jmp.unpack(); + self.jump(r!(a))?; } - OpcodeRepr::JNE => { + Instruction::JNE(jne) => { self.gas_charge(self.gas_costs.jne)?; - self.jump_not_equal(a, b, c)?; + let (a, b, c) = jne.unpack(); + self.jump_not_equal(r!(a), r!(b), r!(c))?; } - OpcodeRepr::RET => { + Instruction::RET(ret) => { self.gas_charge(self.gas_costs.ret)?; - self.ret(a)?; - - return Ok(ExecuteState::Return(a)); + let a = ret.unpack(); + let ra = r!(a); + self.ret(ra)?; + return Ok(ExecuteState::Return(ra)); } - OpcodeRepr::RETD => { - self.dependent_gas_charge(self.gas_costs.retd, b)?; - - return self.ret_data(a, b).map(ExecuteState::ReturnData); + Instruction::RETD(retd) => { + let (a, b) = retd.unpack(); + let len = r!(b); + self.dependent_gas_charge(self.gas_costs.retd, len)?; + return self.ret_data(r!(a), len).map(ExecuteState::ReturnData); } - OpcodeRepr::RVRT => { + Instruction::RVRT(rvrt) => { self.gas_charge(self.gas_costs.rvrt)?; - self.revert(a); - - return Ok(ExecuteState::Revert(a)); + let a = rvrt.unpack(); + let ra = r!(a); + self.revert(ra); + return Ok(ExecuteState::Revert(ra)); } - OpcodeRepr::SMO => { - self.dependent_gas_charge(self.gas_costs.smo, b)?; - self.message_output(a, b, c, d)?; + Instruction::SMO(smo) => { + let (a, b, c, d) = smo.unpack(); + self.dependent_gas_charge(self.gas_costs.smo, r!(b))?; + self.message_output(r!(a), r!(b), r!(c), r!(d))?; } - OpcodeRepr::ALOC => { + Instruction::ALOC(aloc) => { self.gas_charge(self.gas_costs.aloc)?; - self.malloc(a)?; + let a = aloc.unpack(); + self.malloc(r!(a))?; } - OpcodeRepr::CFEI => { + Instruction::CFEI(cfei) => { self.gas_charge(self.gas_costs.cfei)?; - self.stack_pointer_overflow(Word::overflowing_add, imm)?; + let imm = cfei.unpack(); + self.stack_pointer_overflow(Word::overflowing_add, imm.into())?; } - OpcodeRepr::CFSI => { + Instruction::CFSI(cfsi) => { self.gas_charge(self.gas_costs.cfsi)?; - self.stack_pointer_overflow(Word::overflowing_sub, imm)?; + let imm = cfsi.unpack(); + self.stack_pointer_overflow(Word::overflowing_sub, imm.into())?; } - OpcodeRepr::LB => { + Instruction::LB(lb) => { self.gas_charge(self.gas_costs.lb)?; - self.load_byte(ra, b, imm)?; + let (a, b, imm) = lb.unpack(); + self.load_byte(a.into(), r!(b), imm.into())?; } - OpcodeRepr::LW => { + Instruction::LW(lw) => { self.gas_charge(self.gas_costs.lw)?; - self.load_word(ra, b, imm)?; + let (a, b, imm) = lw.unpack(); + self.load_word(a.into(), r!(b), imm.into())?; } - OpcodeRepr::MCL => { - self.dependent_gas_charge(self.gas_costs.mcl, b)?; - self.memclear(a, b)?; + Instruction::MCL(mcl) => { + let (a, b) = mcl.unpack(); + let len = r!(b); + self.dependent_gas_charge(self.gas_costs.mcl, len)?; + self.memclear(r!(a), len)?; } - OpcodeRepr::MCLI => { - self.dependent_gas_charge(self.gas_costs.mcli, b)?; - self.memclear(a, imm)?; + Instruction::MCLI(mcli) => { + let (a, imm) = mcli.unpack(); + let len = Word::from(imm); + self.dependent_gas_charge(self.gas_costs.mcli, len)?; + self.memclear(r!(a), len)?; } - OpcodeRepr::MCP => { - self.dependent_gas_charge(self.gas_costs.mcp, c)?; - self.memcopy(a, b, c)?; + Instruction::MCP(mcp) => { + let (a, b, c) = mcp.unpack(); + let len = r!(c); + self.dependent_gas_charge(self.gas_costs.mcp, len)?; + self.memcopy(r!(a), r!(b), len)?; } - OpcodeRepr::MCPI => { + Instruction::MCPI(mcpi) => { self.gas_charge(self.gas_costs.mcpi)?; - self.memcopy(a, b, imm)?; + let (a, b, imm) = mcpi.unpack(); + let len = imm.into(); + self.memcopy(r!(a), r!(b), len)?; } - OpcodeRepr::MEQ => { - self.dependent_gas_charge(self.gas_costs.meq, d)?; - self.memeq(ra, b, c, d)?; + Instruction::MEQ(meq) => { + let (a, b, c, d) = meq.unpack(); + let len = r!(d); + self.dependent_gas_charge(self.gas_costs.meq, len)?; + self.memeq(a.into(), r!(b), r!(c), len)?; } - OpcodeRepr::SB => { + Instruction::SB(sb) => { self.gas_charge(self.gas_costs.sb)?; - self.store_byte(a, b, imm)?; + let (a, b, imm) = sb.unpack(); + self.store_byte(r!(a), r!(b), imm.into())?; } - OpcodeRepr::SW => { + Instruction::SW(sw) => { self.gas_charge(self.gas_costs.sw)?; - self.store_word(a, b, imm)?; + let (a, b, imm) = sw.unpack(); + self.store_word(r!(a), r!(b), imm.into())?; } - OpcodeRepr::BAL => { + Instruction::BAL(bal) => { self.gas_charge(self.gas_costs.bal)?; - self.contract_balance(ra, b, c)?; + let (a, b, c) = bal.unpack(); + self.contract_balance(a.into(), r!(b), r!(c))?; } - OpcodeRepr::BHEI => { + Instruction::BHEI(bhei) => { self.gas_charge(self.gas_costs.bhei)?; - self.set_block_height(ra)?; + let a = bhei.unpack(); + self.set_block_height(a.into())?; } - OpcodeRepr::BHSH => { + Instruction::BHSH(bhsh) => { self.gas_charge(self.gas_costs.bhsh)?; - self.block_hash(a, b)?; + let (a, b) = bhsh.unpack(); + self.block_hash(r!(a), r!(b))?; } - OpcodeRepr::BURN => { + Instruction::BURN(burn) => { self.gas_charge(self.gas_costs.burn)?; - self.burn(a)?; + let a = burn.unpack(); + self.burn(r!(a))?; } - OpcodeRepr::CALL => { - let state = self.call(a, b, c, d)?; + Instruction::CALL(call) => { + let (a, b, c, d) = call.unpack(); + let state = self.call(r!(a), r!(b), r!(c), r!(d))?; // raise revert state to halt execution for the callee if let ProgramState::Revert(ra) = state { return Ok(ExecuteState::Revert(ra)); } } - OpcodeRepr::CB => { + Instruction::CB(cb) => { self.gas_charge(self.gas_costs.cb)?; - self.block_proposer(a)?; + let a = cb.unpack(); + self.block_proposer(r!(a))?; } - OpcodeRepr::CCP => { - self.dependent_gas_charge(self.gas_costs.ccp, d)?; - self.code_copy(a, b, c, d)?; + Instruction::CCP(ccp) => { + let (a, b, c, d) = ccp.unpack(); + let len = r!(d); + self.dependent_gas_charge(self.gas_costs.ccp, len)?; + self.code_copy(r!(a), r!(b), r!(c), len)?; } - OpcodeRepr::CROO => { + Instruction::CROO(croo) => { self.gas_charge(self.gas_costs.croo)?; - self.code_root(a, b)?; + let (a, b) = croo.unpack(); + self.code_root(r!(a), r!(b))?; } - OpcodeRepr::CSIZ => { - self.code_size(ra, self.registers[rb])?; + Instruction::CSIZ(csiz) => { + let (a, b) = csiz.unpack(); + self.code_size(a.into(), r!(b))?; } - OpcodeRepr::LDC => { - self.dependent_gas_charge(self.gas_costs.ldc, c)?; - self.load_contract_code(a, b, c)?; + Instruction::LDC(ldc) => { + let (a, b, c) = ldc.unpack(); + self.dependent_gas_charge(self.gas_costs.ldc, r!(c))?; + self.load_contract_code(r!(a), r!(b), r!(c))?; } - OpcodeRepr::LOG => { + Instruction::LOG(log) => { self.gas_charge(self.gas_costs.log)?; - self.log(a, b, c, d)?; + let (a, b, c, d) = log.unpack(); + self.log(r!(a), r!(b), r!(c), r!(d))?; } - OpcodeRepr::LOGD => { - self.dependent_gas_charge(self.gas_costs.logd, d)?; - self.log_data(a, b, c, d)?; + Instruction::LOGD(logd) => { + let (a, b, c, d) = logd.unpack(); + self.dependent_gas_charge(self.gas_costs.logd, r!(d))?; + self.log_data(r!(a), r!(b), r!(c), r!(d))?; } - OpcodeRepr::MINT => { + Instruction::MINT(mint) => { self.gas_charge(self.gas_costs.mint)?; - self.mint(a)?; + let a = mint.unpack(); + self.mint(r!(a))?; } - OpcodeRepr::SCWQ => { + Instruction::SCWQ(scwq) => { self.gas_charge(self.gas_costs.scwq)?; - self.state_clear_qword(a, rb, c)?; + let (a, b, c) = scwq.unpack(); + self.state_clear_qword(r!(a), b.into(), r!(c))?; } - OpcodeRepr::SRW => { + Instruction::SRW(srw) => { self.gas_charge(self.gas_costs.srw)?; - self.state_read_word(ra, rb, c)?; + let (a, b, c) = srw.unpack(); + self.state_read_word(a.into(), b.into(), r!(c))?; } - OpcodeRepr::SRWQ => { - self.dependent_gas_charge(self.gas_costs.srwq, d)?; - self.state_read_qword(a, rb, c, d)?; + Instruction::SRWQ(srwq) => { + let (a, b, c, d) = srwq.unpack(); + self.dependent_gas_charge(self.gas_costs.srwq, r!(d))?; + self.state_read_qword(r!(a), b.into(), r!(c), r!(d))?; } - OpcodeRepr::SWW => { + Instruction::SWW(sww) => { self.gas_charge(self.gas_costs.sww)?; - self.state_write_word(a, rb, c)?; + let (a, b, c) = sww.unpack(); + self.state_write_word(r!(a), b.into(), r!(c))?; } - OpcodeRepr::SWWQ => { + Instruction::SWWQ(swwq) => { self.gas_charge(self.gas_costs.swwq)?; - self.state_write_qword(a, rb, c, d)?; + let (a, b, c, d) = swwq.unpack(); + self.state_write_qword(r!(a), b.into(), r!(c), r!(d))?; } - OpcodeRepr::TIME => { + Instruction::TIME(time) => { self.gas_charge(self.gas_costs.time)?; - self.timestamp(ra, b)?; + let (a, b) = time.unpack(); + self.timestamp(a.into(), r!(b))?; } - OpcodeRepr::ECR => { + Instruction::ECR(ecr) => { self.gas_charge(self.gas_costs.ecr)?; - self.ecrecover(a, b, c)?; + let (a, b, c) = ecr.unpack(); + self.ecrecover(r!(a), r!(b), r!(c))?; } - OpcodeRepr::K256 => { + Instruction::K256(k256) => { self.gas_charge(self.gas_costs.k256)?; - self.keccak256(a, b, c)?; + let (a, b, c) = k256.unpack(); + self.keccak256(r!(a), r!(b), r!(c))?; } - OpcodeRepr::S256 => { + Instruction::S256(s256) => { self.gas_charge(self.gas_costs.s256)?; - self.sha256(a, b, c)?; + let (a, b, c) = s256.unpack(); + self.sha256(r!(a), r!(b), r!(c))?; } - OpcodeRepr::FLAG => { + Instruction::FLAG(flag) => { self.gas_charge(self.gas_costs.flag)?; - self.set_flag(a)?; + let a = flag.unpack(); + self.set_flag(r!(a))?; } - OpcodeRepr::GM => { + Instruction::GM(gm) => { self.gas_charge(self.gas_costs.gm)?; - self.metadata(ra, imm as Immediate18)?; + let (a, imm) = gm.unpack(); + self.metadata(a.into(), imm.into())?; } - OpcodeRepr::GTF => { + Instruction::GTF(gtf) => { self.gas_charge(self.gas_costs.gtf)?; - self.get_transaction_field(ra, b, imm as Immediate12)?; + let (a, b, imm) = gtf.unpack(); + self.get_transaction_field(a.into(), r!(b), imm.into())?; } - OpcodeRepr::TR => { + Instruction::TR(tr) => { self.gas_charge(self.gas_costs.tr)?; - self.transfer(a, b, c)?; + let (a, b, c) = tr.unpack(); + self.transfer(r!(a), r!(b), r!(c))?; } - OpcodeRepr::TRO => { + Instruction::TRO(tro) => { self.gas_charge(self.gas_costs.tro)?; - self.transfer_output(a, b, c, d)?; - } - - // list of currently unimplemented opcodes - _ => { - return Err(PanicReason::ErrorFlag.into()); + let (a, b, c, d) = tro.unpack(); + self.transfer_output(r!(a), r!(b), r!(c), r!(d))?; } } diff --git a/fuel-vm/src/interpreter/executors/instruction/tests.rs b/fuel-vm/src/interpreter/executors/instruction/tests.rs index 13b24c62dc..0efb11a46b 100644 --- a/fuel-vm/src/interpreter/executors/instruction/tests.rs +++ b/fuel-vm/src/interpreter/executors/instruction/tests.rs @@ -1,6 +1,6 @@ use super::*; -use fuel_asm::Opcode; use fuel_asm::PanicReason::ReservedRegisterNotWritable; +use fuel_asm::{op, Instruction, Opcode}; mod math_operations; mod reserved_registers; diff --git a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs index e160f932c1..0380fc73c9 100644 --- a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs +++ b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs @@ -8,23 +8,24 @@ use quickcheck_macros::quickcheck; // Ensure none of the opcodes can write to reserved registers #[quickcheck] fn cant_write_to_reserved_registers(raw_random_instruction: u32) -> TestResult { - let random_instruction = Instruction::new(raw_random_instruction); - let opcode = Opcode::new(random_instruction); - // skip undefined opcodes - if matches!(opcode, Opcode::Undefined) { - return TestResult::discard(); - } + let random_instruction = match Instruction::try_from(raw_random_instruction) { + Ok(inst) => inst, + Err(_) => return TestResult::discard(), + }; + let opcode = random_instruction.opcode(); + // ignore if rA/rB isn't set to writeable register and the opcode should write to that register - if (writes_to_ra(opcode) && random_instruction.ra() >= REG_WRITABLE) - || (writes_to_rb(opcode) && random_instruction.rb() >= REG_WRITABLE) - { - return TestResult::discard(); + let [ra, rb, _, _] = random_instruction.reg_ids(); + match (ra, rb) { + (Some(r), _) if writes_to_ra(opcode) && r >= RegId::WRITABLE => return TestResult::discard(), + (_, Some(r)) if writes_to_rb(opcode) && r >= RegId::WRITABLE => return TestResult::discard(), + _ => (), } let mut vm = Interpreter::with_memory_storage(); let params = ConsensusParameters::default(); - let script = Opcode::RET(0x10).to_bytes().to_vec(); + let script = op::ret(0x10).to_bytes().to_vec(); let block_height = 0; let tx = Transaction::script(0, params.max_gas_per_tx, 0, script, vec![], vec![], vec![], vec![]); let tx = tx @@ -32,21 +33,18 @@ fn cant_write_to_reserved_registers(raw_random_instruction: u32) -> TestResult { .expect("failed to check tx"); vm.init_script(tx).expect("Failed to init VM"); - let res = vm.instruction(random_instruction); + let res = vm.instruction(raw_random_instruction); if writes_to_ra(opcode) || writes_to_rb(opcode) { // if this opcode writes to $rA or $rB, expect an error since we're attempting to use a reserved register // This assumes that writeable register is validated before other properties of the instruction. - match res { - Err(InterpreterError::PanicInstruction(r)) if r.reason() == &ReservedRegisterNotWritable => { - // expected failure - } - Err(InterpreterError::PanicInstruction(r)) if r.reason() == &OutOfGas => { - // Some opcodes may run out of gas if they access too much data. - // Simply discard these results as an alternative to structural fuzzing that avoids - // out of gas errors. - return TestResult::discard(); - } + match res.as_ref().map_err(|e| e.panic_reason()) { + // expected failure + Err(Some(ReservedRegisterNotWritable)) => {} + // Some opcodes may run out of gas if they access too much data. + // Simply discard these results as an alternative to structural fuzzing that avoids + // out of gas errors. + Err(Some(OutOfGas)) => return TestResult::discard(), _ => { return TestResult::error(format!( "expected ReservedRegisterNotWritable error {:?}", @@ -66,15 +64,15 @@ fn cant_write_to_reserved_registers(raw_random_instruction: u32) -> TestResult { )); } - // Ensure REG_ZERO and REG_ONE were not changed. + // Ensure RegId::ZERO and RegId::ONE were not changed. // While not a perfect guarantee against the opcode writing a value // to an invalid register, this increases the likelihood of detecting // erroneous register access. This is not a comprehensive set of all possible // writeable violations but more can be added. - if vm.registers[REG_ZERO] != 0 { + if vm.registers[RegId::ZERO] != 0 { return TestResult::error("reserved register was modified!"); } - if vm.registers[REG_ONE] != 1 { + if vm.registers[RegId::ONE] != 1 { return TestResult::error("reserved register was modified!"); } @@ -84,171 +82,169 @@ fn cant_write_to_reserved_registers(raw_random_instruction: u32) -> TestResult { // determines whether a given opcode stores a value into $rA fn writes_to_ra(opcode: Opcode) -> bool { match opcode { - Opcode::ADD(_, _, _) => true, - Opcode::ADDI(_, _, _) => true, - Opcode::AND(_, _, _) => true, - Opcode::ANDI(_, _, _) => true, - Opcode::DIV(_, _, _) => true, - Opcode::DIVI(_, _, _) => true, - Opcode::EQ(_, _, _) => true, - Opcode::EXP(_, _, _) => true, - Opcode::EXPI(_, _, _) => true, - Opcode::GT(_, _, _) => true, - Opcode::LT(_, _, _) => true, - Opcode::MLOG(_, _, _) => true, - Opcode::MROO(_, _, _) => true, - Opcode::MOD(_, _, _) => true, - Opcode::MODI(_, _, _) => true, - Opcode::MOVE(_, _) => true, - Opcode::MOVI(_, _) => true, - Opcode::MUL(_, _, _) => true, - Opcode::MULI(_, _, _) => true, - Opcode::NOT(_, _) => true, - Opcode::OR(_, _, _) => true, - Opcode::ORI(_, _, _) => true, - Opcode::SLL(_, _, _) => true, - Opcode::SLLI(_, _, _) => true, - Opcode::SRL(_, _, _) => true, - Opcode::SRLI(_, _, _) => true, - Opcode::SUB(_, _, _) => true, - Opcode::SUBI(_, _, _) => true, - Opcode::XOR(_, _, _) => true, - Opcode::XORI(_, _, _) => true, - Opcode::JI(_) => false, - Opcode::JNEI(_, _, _) => false, - Opcode::JNZI(_, _) => false, - Opcode::JMP(_) => false, - Opcode::JNE(_, _, _) => false, - Opcode::RET(_) => false, - Opcode::RETD(_, _) => false, - Opcode::CFEI(_) => false, - Opcode::CFSI(_) => false, - Opcode::LB(_, _, _) => true, - Opcode::LW(_, _, _) => true, - Opcode::ALOC(_) => false, - Opcode::MCL(_, _) => false, - Opcode::MCLI(_, _) => false, - Opcode::MCP(_, _, _) => false, - Opcode::MCPI(_, _, _) => false, - Opcode::MEQ(_, _, _, _) => true, - Opcode::SB(_, _, _) => false, - Opcode::SW(_, _, _) => false, - Opcode::BAL(_, _, _) => true, - Opcode::BHSH(_, _) => false, - Opcode::BHEI(_) => true, - Opcode::BURN(_) => false, - Opcode::CALL(_, _, _, _) => false, - Opcode::CCP(_, _, _, _) => false, - Opcode::CROO(_, _) => false, - Opcode::CSIZ(_, _) => true, - Opcode::CB(_) => false, - Opcode::LDC(_, _, _) => false, - Opcode::LOG(_, _, _, _) => false, - Opcode::LOGD(_, _, _, _) => false, - Opcode::MINT(_) => false, - Opcode::RVRT(_) => false, - Opcode::SMO(_, _, _, _) => false, - Opcode::SCWQ(_, _, _) => false, - Opcode::SRW(_, _, _) => true, - Opcode::SRWQ(_, _, _, _) => false, - Opcode::SWW(_, _, _) => false, - Opcode::SWWQ(_, _, _, _) => false, - Opcode::TR(_, _, _) => false, - Opcode::TRO(_, _, _, _) => false, - Opcode::ECR(_, _, _) => false, - Opcode::K256(_, _, _) => false, - Opcode::S256(_, _, _) => false, + Opcode::ADD => true, + Opcode::ADDI => true, + Opcode::AND => true, + Opcode::ANDI => true, + Opcode::DIV => true, + Opcode::DIVI => true, + Opcode::EQ => true, + Opcode::EXP => true, + Opcode::EXPI => true, + Opcode::GT => true, + Opcode::LT => true, + Opcode::MLOG => true, + Opcode::MROO => true, + Opcode::MOD => true, + Opcode::MODI => true, + Opcode::MOVE => true, + Opcode::MOVI => true, + Opcode::MUL => true, + Opcode::MULI => true, + Opcode::NOT => true, + Opcode::OR => true, + Opcode::ORI => true, + Opcode::SLL => true, + Opcode::SLLI => true, + Opcode::SRL => true, + Opcode::SRLI => true, + Opcode::SUB => true, + Opcode::SUBI => true, + Opcode::XOR => true, + Opcode::XORI => true, + Opcode::JI => false, + Opcode::JNEI => false, + Opcode::JNZI => false, + Opcode::JMP => false, + Opcode::JNE => false, + Opcode::RET => false, + Opcode::RETD => false, + Opcode::CFEI => false, + Opcode::CFSI => false, + Opcode::LB => true, + Opcode::LW => true, + Opcode::ALOC => false, + Opcode::MCL => false, + Opcode::MCLI => false, + Opcode::MCP => false, + Opcode::MCPI => false, + Opcode::MEQ => true, + Opcode::SB => false, + Opcode::SW => false, + Opcode::BAL => true, + Opcode::BHSH => false, + Opcode::BHEI => true, + Opcode::BURN => false, + Opcode::CALL => false, + Opcode::CCP => false, + Opcode::CROO => false, + Opcode::CSIZ => true, + Opcode::CB => false, + Opcode::LDC => false, + Opcode::LOG => false, + Opcode::LOGD => false, + Opcode::MINT => false, + Opcode::RVRT => false, + Opcode::SMO => false, + Opcode::SCWQ => false, + Opcode::SRW => true, + Opcode::SRWQ => false, + Opcode::SWW => false, + Opcode::SWWQ => false, + Opcode::TR => false, + Opcode::TRO => false, + Opcode::ECR => false, + Opcode::K256 => false, + Opcode::S256 => false, Opcode::NOOP => false, - Opcode::FLAG(_) => false, - Opcode::GM(_, _) => true, - Opcode::GTF(_, _, _) => true, - Opcode::Undefined => false, - Opcode::TIME(_, _) => true, + Opcode::FLAG => false, + Opcode::GM => true, + Opcode::GTF => true, + Opcode::TIME => true, } } // determines whether a given opcode stores a value into $rB fn writes_to_rb(opcode: Opcode) -> bool { match opcode { - Opcode::ADD(_, _, _) => false, - Opcode::ADDI(_, _, _) => false, - Opcode::AND(_, _, _) => false, - Opcode::ANDI(_, _, _) => false, - Opcode::DIV(_, _, _) => false, - Opcode::DIVI(_, _, _) => false, - Opcode::EQ(_, _, _) => false, - Opcode::EXP(_, _, _) => false, - Opcode::EXPI(_, _, _) => false, - Opcode::GT(_, _, _) => false, - Opcode::LT(_, _, _) => false, - Opcode::MLOG(_, _, _) => false, - Opcode::MROO(_, _, _) => false, - Opcode::MOD(_, _, _) => false, - Opcode::MODI(_, _, _) => false, - Opcode::MOVE(_, _) => false, - Opcode::MOVI(_, _) => false, - Opcode::MUL(_, _, _) => false, - Opcode::MULI(_, _, _) => false, - Opcode::NOT(_, _) => false, - Opcode::OR(_, _, _) => false, - Opcode::ORI(_, _, _) => false, - Opcode::SLL(_, _, _) => false, - Opcode::SLLI(_, _, _) => false, - Opcode::SRL(_, _, _) => false, - Opcode::SRLI(_, _, _) => false, - Opcode::SUB(_, _, _) => false, - Opcode::SUBI(_, _, _) => false, - Opcode::XOR(_, _, _) => false, - Opcode::XORI(_, _, _) => false, - Opcode::JI(_) => false, - Opcode::JNEI(_, _, _) => false, - Opcode::JNZI(_, _) => false, - Opcode::JMP(_) => false, - Opcode::JNE(_, _, _) => false, - Opcode::RET(_) => false, - Opcode::RETD(_, _) => false, - Opcode::CFEI(_) => false, - Opcode::CFSI(_) => false, - Opcode::LB(_, _, _) => false, - Opcode::LW(_, _, _) => false, - Opcode::ALOC(_) => false, - Opcode::MCL(_, _) => false, - Opcode::MCLI(_, _) => false, - Opcode::MCP(_, _, _) => false, - Opcode::MCPI(_, _, _) => false, - Opcode::MEQ(_, _, _, _) => false, - Opcode::SB(_, _, _) => false, - Opcode::SW(_, _, _) => false, - Opcode::BAL(_, _, _) => false, - Opcode::BHSH(_, _) => false, - Opcode::BHEI(_) => false, - Opcode::BURN(_) => false, - Opcode::CALL(_, _, _, _) => false, - Opcode::CCP(_, _, _, _) => false, - Opcode::CROO(_, _) => false, - Opcode::CSIZ(_, _) => false, - Opcode::CB(_) => false, - Opcode::LDC(_, _, _) => false, - Opcode::LOG(_, _, _, _) => false, - Opcode::LOGD(_, _, _, _) => false, - Opcode::MINT(_) => false, - Opcode::RVRT(_) => false, - Opcode::SMO(_, _, _, _) => false, - Opcode::SCWQ(_, _, _) => true, - Opcode::SRW(_, _, _) => true, - Opcode::SRWQ(_, _, _, _) => true, - Opcode::SWW(_, _, _) => true, - Opcode::SWWQ(_, _, _, _) => true, - Opcode::TR(_, _, _) => false, - Opcode::TRO(_, _, _, _) => false, - Opcode::ECR(_, _, _) => false, - Opcode::K256(_, _, _) => false, - Opcode::S256(_, _, _) => false, + Opcode::ADD => false, + Opcode::ADDI => false, + Opcode::AND => false, + Opcode::ANDI => false, + Opcode::DIV => false, + Opcode::DIVI => false, + Opcode::EQ => false, + Opcode::EXP => false, + Opcode::EXPI => false, + Opcode::GT => false, + Opcode::LT => false, + Opcode::MLOG => false, + Opcode::MROO => false, + Opcode::MOD => false, + Opcode::MODI => false, + Opcode::MOVE => false, + Opcode::MOVI => false, + Opcode::MUL => false, + Opcode::MULI => false, + Opcode::NOT => false, + Opcode::OR => false, + Opcode::ORI => false, + Opcode::SLL => false, + Opcode::SLLI => false, + Opcode::SRL => false, + Opcode::SRLI => false, + Opcode::SUB => false, + Opcode::SUBI => false, + Opcode::XOR => false, + Opcode::XORI => false, + Opcode::JI => false, + Opcode::JNEI => false, + Opcode::JNZI => false, + Opcode::JMP => false, + Opcode::JNE => false, + Opcode::RET => false, + Opcode::RETD => false, + Opcode::CFEI => false, + Opcode::CFSI => false, + Opcode::LB => false, + Opcode::LW => false, + Opcode::ALOC => false, + Opcode::MCL => false, + Opcode::MCLI => false, + Opcode::MCP => false, + Opcode::MCPI => false, + Opcode::MEQ => false, + Opcode::SB => false, + Opcode::SW => false, + Opcode::BAL => false, + Opcode::BHSH => false, + Opcode::BHEI => false, + Opcode::BURN => false, + Opcode::CALL => false, + Opcode::CCP => false, + Opcode::CROO => false, + Opcode::CSIZ => false, + Opcode::CB => false, + Opcode::LDC => false, + Opcode::LOG => false, + Opcode::LOGD => false, + Opcode::MINT => false, + Opcode::RVRT => false, + Opcode::SMO => false, + Opcode::SCWQ => true, + Opcode::SRW => true, + Opcode::SRWQ => true, + Opcode::SWW => true, + Opcode::SWWQ => true, + Opcode::TR => false, + Opcode::TRO => false, + Opcode::ECR => false, + Opcode::K256 => false, + Opcode::S256 => false, Opcode::NOOP => false, - Opcode::FLAG(_) => false, - Opcode::GM(_, _) => false, - Opcode::GTF(_, _, _) => false, - Opcode::Undefined => false, - Opcode::TIME(_, _) => false, + Opcode::FLAG => false, + Opcode::GM => false, + Opcode::GTF => false, + Opcode::TIME => false, } } diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index fe27cfacae..be2c483f72 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -11,7 +11,7 @@ use crate::state::{StateTransition, StateTransitionRef}; use crate::storage::{InterpreterStorage, PredicateStorage}; use crate::error::BugVariant::GlobalGasUnderflow; -use fuel_asm::PanicReason; +use fuel_asm::{PanicReason, RegId}; use fuel_tx::{ field::{Outputs, ReceiptsRoot, Salt, Script as ScriptField, StorageSlots}, Chargeable, ConsensusParameters, Contract, Create, Input, Output, Receipt, ScriptExecutionResult, @@ -81,7 +81,7 @@ impl Interpreter { return Err(PredicateVerificationFailed::False); } - remaining_gas = vm.registers[REG_GGAS]; + remaining_gas = vm.registers[RegId::GGAS]; } Ok(PredicatesChecked { @@ -99,7 +99,7 @@ where { pub(crate) fn run_call(&mut self) -> Result { loop { - if self.registers[REG_PC] >= VM_MAX_RAM { + if self.registers[RegId::PC] >= VM_MAX_RAM { return Err(PanicReason::MemoryOverflow.into()); } @@ -215,8 +215,8 @@ where if let Some(script) = self.transaction().as_script() { let offset = (self.tx_offset() + script.script_offset()) as Word; - self.registers[REG_PC] = offset; - self.registers[REG_IS] = offset; + self.registers[RegId::PC] = offset; + self.registers[RegId::IS] = offset; } // TODO set tree balance @@ -258,7 +258,7 @@ where Err(e) => match e.instruction_result() { Some(result) => { - self.append_panic_receipt(*result); + self.append_panic_receipt(result); (ScriptExecutionResult::Panic, ProgramState::Revert(0)) } @@ -313,7 +313,7 @@ where pub(crate) fn run_program(&mut self) -> Result { loop { - if self.registers[REG_PC] >= VM_MAX_RAM { + if self.registers[RegId::PC] >= VM_MAX_RAM { return Err(InterpreterError::Panic(PanicReason::MemoryOverflow)); } diff --git a/fuel-vm/src/interpreter/executors/predicate.rs b/fuel-vm/src/interpreter/executors/predicate.rs index 7124326359..df86222e4d 100644 --- a/fuel-vm/src/interpreter/executors/predicate.rs +++ b/fuel-vm/src/interpreter/executors/predicate.rs @@ -1,10 +1,9 @@ -use crate::consts::*; use crate::error::InterpreterError; use crate::prelude::{ExecutableTransaction, Interpreter}; use crate::state::{ExecuteState, ProgramState}; use crate::storage::PredicateStorage; -use fuel_asm::PanicReason; +use fuel_asm::{PanicReason, RegId}; impl Interpreter where @@ -17,11 +16,11 @@ where .map(|p| p.program().boundaries(&self.ownership_registers())) .ok_or(InterpreterError::PredicateFailure)?; - self.registers[REG_PC] = start; - self.registers[REG_IS] = start; + self.registers[RegId::PC] = start; + self.registers[RegId::IS] = start; loop { - if end <= self.registers[REG_PC] { + if end <= self.registers[RegId::PC] { return Err(InterpreterError::Panic(PanicReason::MemoryOverflow)); } diff --git a/fuel-vm/src/interpreter/flow.rs b/fuel-vm/src/interpreter/flow.rs index 914cc0f9a0..98e44984e9 100644 --- a/fuel-vm/src/interpreter/flow.rs +++ b/fuel-vm/src/interpreter/flow.rs @@ -7,7 +7,7 @@ use crate::interpreter::PanicContext; use crate::state::ProgramState; use crate::storage::InterpreterStorage; -use fuel_asm::{Instruction, InstructionResult, RegisterId}; +use fuel_asm::{Instruction, InstructionResult, RegId}; use fuel_crypto::Hasher; use fuel_tx::{PanicReason, Receipt}; use fuel_types::bytes::SerializableVec; @@ -20,14 +20,14 @@ where Tx: ExecutableTransaction, { pub(crate) fn jump(&mut self, j: Word) -> Result<(), RuntimeError> { - let j = self.registers[REG_IS].saturating_add(j.saturating_mul(Instruction::LEN as Word)); + let j = self.registers[RegId::IS].saturating_add(j.saturating_mul(Instruction::SIZE as Word)); if j > VM_MAX_RAM - 1 { Err(PanicReason::MemoryOverflow.into()) - } else if self.is_predicate() && j <= self.registers[REG_PC] { + } else if self.is_predicate() && j <= self.registers[RegId::PC] { Err(PanicReason::IllegalJump.into()) } else { - self.registers[REG_PC] = j; + self.registers[RegId::PC] = j; Ok(()) } @@ -42,7 +42,7 @@ where } pub(crate) fn jump_not_zero(&mut self, a: Word, to: Word) -> Result<(), RuntimeError> { - if a != self.registers[REG_ZERO] { + if a != self.registers[RegId::ZERO] { self.jump(to) } else { self.inc_pc() @@ -51,21 +51,21 @@ where pub(crate) fn return_from_context(&mut self, receipt: Receipt) -> Result<(), RuntimeError> { if let Some(frame) = self.frames.pop() { - self.registers[REG_CGAS] = arith::add_word(self.registers[REG_CGAS], frame.context_gas())?; + self.registers[RegId::CGAS] = arith::add_word(self.registers[RegId::CGAS], frame.context_gas())?; - let cgas = self.registers[REG_CGAS]; - let ggas = self.registers[REG_GGAS]; - let ret = self.registers[REG_RET]; - let retl = self.registers[REG_RETL]; + let cgas = self.registers[RegId::CGAS]; + let ggas = self.registers[RegId::GGAS]; + let ret = self.registers[RegId::RET]; + let retl = self.registers[RegId::RETL]; self.registers.copy_from_slice(frame.registers()); - self.registers[REG_CGAS] = cgas; - self.registers[REG_GGAS] = ggas; - self.registers[REG_RET] = ret; - self.registers[REG_RETL] = retl; + self.registers[RegId::CGAS] = cgas; + self.registers[RegId::GGAS] = ggas; + self.registers[RegId::RET] = ret; + self.registers[RegId::RETL] = retl; - self.set_frame_pointer(self.registers[REG_FP]); + self.set_frame_pointer(self.registers[RegId::FP]); } self.append_receipt(receipt); @@ -77,12 +77,12 @@ where let receipt = Receipt::ret( self.internal_contract_or_default(), a, - self.registers[REG_PC], - self.registers[REG_IS], + self.registers[RegId::PC], + self.registers[RegId::IS], ); - self.registers[REG_RET] = a; - self.registers[REG_RETL] = 0; + self.registers[RegId::RET] = a; + self.registers[RegId::RETL] = 0; // TODO if ret instruction is in memory boundary, inc_pc shouldn't fail self.return_from_context(receipt) @@ -102,12 +102,12 @@ where b, digest, self.memory[a as usize..ab].to_vec(), - self.registers[REG_PC], - self.registers[REG_IS], + self.registers[RegId::PC], + self.registers[RegId::IS], ); - self.registers[REG_RET] = a; - self.registers[REG_RETL] = b; + self.registers[RegId::RET] = a; + self.registers[RegId::RETL] = b; self.return_from_context(receipt)?; @@ -118,16 +118,16 @@ where let receipt = Receipt::revert( self.internal_contract_or_default(), a, - self.registers[REG_PC], - self.registers[REG_IS], + self.registers[RegId::PC], + self.registers[RegId::IS], ); self.append_receipt(receipt); } pub(crate) fn append_panic_receipt(&mut self, result: InstructionResult) { - let pc = self.registers[REG_PC]; - let is = self.registers[REG_IS]; + let pc = self.registers[RegId::PC]; + let is = self.registers[RegId::IS]; let mut receipt = Receipt::panic(self.internal_contract_or_default(), result, pc, is); @@ -187,34 +187,35 @@ where // credit contract asset_id balance self.balance_increase(call.to(), &asset_id, b)?; - let forward_gas_amount = cmp::min(self.registers[REG_CGAS], d); + let forward_gas_amount = cmp::min(self.registers[RegId::CGAS], d); // subtract gas - self.registers[REG_CGAS] = arith::sub_word(self.registers[REG_CGAS], forward_gas_amount)?; + self.registers[RegId::CGAS] = arith::sub_word(self.registers[RegId::CGAS], forward_gas_amount)?; - *frame.set_context_gas() = self.registers[REG_CGAS]; - *frame.set_global_gas() = self.registers[REG_GGAS]; + *frame.set_context_gas() = self.registers[RegId::CGAS]; + *frame.set_global_gas() = self.registers[RegId::GGAS]; let stack = frame.to_bytes(); let len = stack.len() as Word; - if len > self.registers[REG_HP] || self.registers[REG_SP] > self.registers[REG_HP] - len { + if len > self.registers[RegId::HP] || self.registers[RegId::SP] > self.registers[RegId::HP] - len { return Err(PanicReason::MemoryOverflow.into()); } let id = self.internal_contract_or_default(); - self.set_frame_pointer(self.registers[REG_SP]); + self.set_frame_pointer(self.registers[RegId::SP]); - self.registers[REG_SP] += len; - self.registers[REG_SSP] = self.registers[REG_SP]; + self.registers[RegId::SP] += len; + self.registers[RegId::SSP] = self.registers[RegId::SP]; - self.memory[self.registers[REG_FP] as usize..self.registers[REG_SP] as usize].copy_from_slice(stack.as_slice()); + self.memory[self.registers[RegId::FP] as usize..self.registers[RegId::SP] as usize] + .copy_from_slice(stack.as_slice()); - self.registers[REG_BAL] = b; - self.registers[REG_PC] = self.registers[REG_FP].saturating_add(CallFrame::code_offset() as Word); - self.registers[REG_IS] = self.registers[REG_PC]; - self.registers[REG_CGAS] = forward_gas_amount; + self.registers[RegId::BAL] = b; + self.registers[RegId::PC] = self.registers[RegId::FP].saturating_add(CallFrame::code_offset() as Word); + self.registers[RegId::IS] = self.registers[RegId::PC]; + self.registers[RegId::CGAS] = forward_gas_amount; let receipt = Receipt::call( id, @@ -224,8 +225,8 @@ where d, frame.a(), frame.b(), - self.registers[REG_PC], - self.registers[REG_IS], + self.registers[RegId::PC], + self.registers[RegId::IS], ); self.append_receipt(receipt); @@ -236,36 +237,30 @@ where } /// Prepare a call instruction for execution - pub fn prepare_call( - &mut self, - ra: RegisterId, - rb: RegisterId, - rc: RegisterId, - rd: RegisterId, - ) -> Result<(), RuntimeError> { + pub fn prepare_call(&mut self, ra: RegId, rb: RegId, rc: RegId, rd: RegId) -> Result<(), RuntimeError> { const M: &str = "the provided id is not a valid register"; let a = self .registers - .get(ra) + .get(ra.to_u8() as usize) .copied() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, M))?; let b = self .registers - .get(rb) + .get(rb.to_u8() as usize) .copied() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, M))?; let c = self .registers - .get(rc) + .get(rc.to_u8() as usize) .copied() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, M))?; let d = self .registers - .get(rd) + .get(rd.to_u8() as usize) .copied() .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidInput, M))?; diff --git a/fuel-vm/src/interpreter/gas.rs b/fuel-vm/src/interpreter/gas.rs index efd9021b4b..169f01e8a5 100644 --- a/fuel-vm/src/interpreter/gas.rs +++ b/fuel-vm/src/interpreter/gas.rs @@ -1,23 +1,23 @@ use super::Interpreter; use crate::arith; -use crate::consts::*; use crate::error::RuntimeError; use crate::gas::DependentCost; use fuel_asm::PanicReason; +use fuel_asm::RegId; use fuel_types::Word; impl Interpreter { - pub(crate) const fn remaining_gas(&self) -> Word { - self.registers[REG_GGAS] + pub(crate) fn remaining_gas(&self) -> Word { + self.registers[RegId::GGAS] } /// Sets the remaining amout of gas to both CGAS and GGAS. /// Only useful in contexts where CGAS and GGAS are the same, /// i.e. predicates and testing. pub(crate) fn set_remaining_gas(&mut self, gas: Word) { - self.registers[REG_GGAS] = gas; - self.registers[REG_CGAS] = gas; + self.registers[RegId::GGAS] = gas; + self.registers[RegId::CGAS] = gas; } pub(crate) fn dependent_gas_charge(&mut self, gas_cost: DependentCost, arg: Word) -> Result<(), RuntimeError> { @@ -37,19 +37,19 @@ impl Interpreter { #[cfg(feature = "profile-gas")] { - let gas_use = gas.min(self.registers[REG_CGAS]); + let gas_use = gas.min(self.registers[RegId::CGAS]); let location = self.current_location(); self.profiler.data_mut().gas_mut().add(location, gas_use); } - if gas > self.registers[REG_CGAS] { - self.registers[REG_GGAS] = arith::sub_word(self.registers[REG_GGAS], self.registers[REG_CGAS])?; - self.registers[REG_CGAS] = 0; + if gas > self.registers[RegId::CGAS] { + self.registers[RegId::GGAS] = arith::sub_word(self.registers[RegId::GGAS], self.registers[RegId::CGAS])?; + self.registers[RegId::CGAS] = 0; Err(PanicReason::OutOfGas.into()) } else { - self.registers[REG_CGAS] = arith::sub_word(self.registers[REG_CGAS], gas)?; - self.registers[REG_GGAS] = arith::sub_word(self.registers[REG_GGAS], gas)?; + self.registers[RegId::CGAS] = arith::sub_word(self.registers[RegId::CGAS], gas)?; + self.registers[RegId::GGAS] = arith::sub_word(self.registers[RegId::GGAS], gas)?; Ok(()) } diff --git a/fuel-vm/src/interpreter/initialization.rs b/fuel-vm/src/interpreter/initialization.rs index f1c9ed533f..7a53327473 100644 --- a/fuel-vm/src/interpreter/initialization.rs +++ b/fuel-vm/src/interpreter/initialization.rs @@ -5,6 +5,7 @@ use crate::context::Context; use crate::error::{Bug, BugId, InterpreterError}; use crate::storage::InterpreterStorage; +use fuel_asm::RegId; use fuel_types::Word; use crate::error::BugVariant::GlobalGasUnderflow; @@ -31,11 +32,11 @@ where // Optimized for memset self.registers.iter_mut().for_each(|r| *r = 0); - self.registers[REG_ONE] = 1; - self.registers[REG_SSP] = 0; + self.registers[RegId::ONE] = 1; + self.registers[RegId::SSP] = 0; // Set heap area - self.registers[REG_HP] = VM_MAX_RAM - 1; + self.registers[RegId::HP] = VM_MAX_RAM - 1; self.push_stack(self.transaction().id().as_ref()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; @@ -60,7 +61,7 @@ where self.push_stack(tx_bytes.as_slice()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; - self.registers[REG_SP] = self.registers[REG_SSP]; + self.registers[RegId::SP] = self.registers[RegId::SSP]; Ok(()) } diff --git a/fuel-vm/src/interpreter/internal.rs b/fuel-vm/src/interpreter/internal.rs index de80e1ffdb..6e7a14608c 100644 --- a/fuel-vm/src/interpreter/internal.rs +++ b/fuel-vm/src/interpreter/internal.rs @@ -4,7 +4,7 @@ use crate::context::Context; use crate::crypto; use crate::error::RuntimeError; -use fuel_asm::{Instruction, PanicReason}; +use fuel_asm::{Instruction, PanicReason, RegId}; use fuel_tx::field::ReceiptsRoot; use fuel_tx::{Output, Receipt}; use fuel_types::bytes::SerializableVec; @@ -75,42 +75,42 @@ where impl Interpreter { pub(crate) fn reserve_stack(&mut self, len: Word) -> Result { - let (ssp, overflow) = self.registers[REG_SSP].overflowing_add(len); + let (ssp, overflow) = self.registers[RegId::SSP].overflowing_add(len); - if overflow || !self.is_external_context() && ssp > self.registers[REG_SP] { + if overflow || !self.is_external_context() && ssp > self.registers[RegId::SP] { Err(PanicReason::MemoryOverflow.into()) } else { - Ok(mem::replace(&mut self.registers[REG_SSP], ssp)) + Ok(mem::replace(&mut self.registers[RegId::SSP], ssp)) } } pub(crate) fn push_stack(&mut self, data: &[u8]) -> Result<(), RuntimeError> { let ssp = self.reserve_stack(data.len() as Word)?; - self.memory[ssp as usize..self.registers[REG_SSP] as usize].copy_from_slice(data); + self.memory[ssp as usize..self.registers[RegId::SSP] as usize].copy_from_slice(data); Ok(()) } pub(crate) fn set_flag(&mut self, a: Word) -> Result<(), RuntimeError> { - self.registers[REG_FLAG] = a; + self.registers[RegId::FLAG] = a; self.inc_pc() } pub(crate) fn clear_err(&mut self) { - self.registers[REG_ERR] = 0; + self.registers[RegId::ERR] = 0; } pub(crate) fn set_err(&mut self) { - self.registers[REG_ERR] = 1; + self.registers[RegId::ERR] = 1; } pub(crate) fn inc_pc(&mut self) -> Result<(), RuntimeError> { - self.registers[REG_PC] - .checked_add(Instruction::LEN as Word) + self.registers[RegId::PC] + .checked_add(Instruction::SIZE as Word) .ok_or_else(|| PanicReason::ArithmeticOverflow.into()) - .map(|pc| self.registers[REG_PC] = pc) + .map(|pc| self.registers[RegId::PC] = pc) } pub(crate) const fn context(&self) -> &Context { @@ -129,7 +129,8 @@ impl Interpreter { matches!(self.context, Context::Predicate { .. }) } - pub(crate) const fn is_register_writable(ra: RegisterId) -> Result<(), RuntimeError> { + // TODO: We should take a `RegId` as an argument. + pub(crate) fn is_register_writable(ra: RegisterId) -> Result<(), RuntimeError> { is_register_writable(ra) } @@ -152,7 +153,7 @@ impl Interpreter { pub(crate) fn internal_contract_bounds(&self) -> Result<(usize, usize), RuntimeError> { self.is_internal_context() .then(|| { - let c = self.registers[REG_FP] as usize; + let c = self.registers[RegId::FP] as usize; let cx = c + ContractId::LEN; (c, cx) @@ -193,7 +194,7 @@ impl Interpreter { pub(crate) fn set_frame_pointer(&mut self, fp: Word) { self.context.update_from_frame_pointer(fp); - self.registers[REG_FP] = fp; + self.registers[RegId::FP] = fp; } pub(crate) fn block_height(&self) -> Result { @@ -201,8 +202,8 @@ impl Interpreter { } } -pub(crate) const fn is_register_writable(ra: RegisterId) -> Result<(), RuntimeError> { - if ra >= REG_WRITABLE { +pub(crate) fn is_register_writable(ra: RegisterId) -> Result<(), RuntimeError> { + if ra >= RegId::WRITABLE.into() { Ok(()) } else { Err(RuntimeError::Recoverable(PanicReason::ReservedRegisterNotWritable)) @@ -212,6 +213,7 @@ pub(crate) const fn is_register_writable(ra: RegisterId) -> Result<(), RuntimeEr #[cfg(all(test, feature = "random"))] mod tests { use crate::prelude::*; + use fuel_asm::op; use fuel_tx::field::Outputs; use fuel_tx::TransactionBuilder; use rand::rngs::StdRng; @@ -229,7 +231,7 @@ mod tests { let maturity = 0; let height = 0; - let script = Opcode::RET(0x01).to_bytes().to_vec(); + let script = op::ret(0x01).to_bytes().to_vec(); let balances = vec![(rng.gen(), 100), (rng.gen(), 500)]; let mut tx = TransactionBuilder::script(script, Default::default()); diff --git a/fuel-vm/src/interpreter/log.rs b/fuel-vm/src/interpreter/log.rs index a227680e50..1ccd92b482 100644 --- a/fuel-vm/src/interpreter/log.rs +++ b/fuel-vm/src/interpreter/log.rs @@ -2,7 +2,7 @@ use super::{ExecutableTransaction, Interpreter}; use crate::consts::*; use crate::error::RuntimeError; -use fuel_asm::PanicReason; +use fuel_asm::{PanicReason, RegId}; use fuel_crypto::Hasher; use fuel_tx::Receipt; use fuel_types::Word; @@ -18,8 +18,8 @@ where b, c, d, - self.registers[REG_PC], - self.registers[REG_IS], + self.registers[RegId::PC], + self.registers[RegId::IS], ); self.append_receipt(receipt); @@ -43,8 +43,8 @@ where d, digest, self.memory[c as usize..cd].to_vec(), - self.registers[REG_PC], - self.registers[REG_IS], + self.registers[RegId::PC], + self.registers[RegId::IS], ); self.append_receipt(receipt); diff --git a/fuel-vm/src/interpreter/memory.rs b/fuel-vm/src/interpreter/memory.rs index e75a58e44f..93f5129938 100644 --- a/fuel-vm/src/interpreter/memory.rs +++ b/fuel-vm/src/interpreter/memory.rs @@ -2,7 +2,7 @@ use super::{ExecutableTransaction, Interpreter}; use crate::error::RuntimeError; use crate::{consts::*, context::Context}; -use fuel_asm::PanicReason; +use fuel_asm::{PanicReason, RegId}; use fuel_types::{RegisterId, Word}; use std::{ops, ptr}; @@ -108,7 +108,7 @@ impl MemoryRange { { use ops::Bound::*; - let heap = vm.registers()[REG_HP].saturating_add(1); + let heap = vm.registers()[RegId::HP].saturating_add(1); self.start = match self.start { Included(start) => Included(heap.saturating_add(start)), @@ -233,12 +233,12 @@ where where F: FnOnce(Word, Word) -> (Word, bool), { - let (result, overflow) = f(self.registers[REG_SP], v); + let (result, overflow) = f(self.registers[RegId::SP], v); - if overflow || result > self.registers[REG_HP] { + if overflow || result > self.registers[RegId::HP] { Err(PanicReason::MemoryOverflow.into()) } else { - self.registers[REG_SP] = result; + self.registers[RegId::SP] = result; self.inc_pc() } @@ -310,12 +310,12 @@ where } pub(crate) fn malloc(&mut self, a: Word) -> Result<(), RuntimeError> { - let (result, overflow) = self.registers[REG_HP].overflowing_sub(a); + let (result, overflow) = self.registers[RegId::HP].overflowing_sub(a); - if overflow || result < self.registers[REG_SP] { + if overflow || result < self.registers[RegId::SP] { Err(PanicReason::MemoryOverflow.into()) } else { - self.registers[REG_HP] = result; + self.registers[RegId::HP] = result; self.inc_pc() } @@ -398,10 +398,10 @@ pub struct OwnershipRegisters { impl OwnershipRegisters { pub(crate) fn new(vm: &Interpreter) -> Self { OwnershipRegisters { - sp: vm.registers[REG_SP], - ssp: vm.registers[REG_SSP], - hp: vm.registers[REG_HP], - prev_hp: vm.frames.last().map(|frame| frame.registers()[REG_HP]).unwrap_or(0), + sp: vm.registers[RegId::SP], + ssp: vm.registers[RegId::SSP], + hp: vm.registers[RegId::HP], + prev_hp: vm.frames.last().map(|frame| frame.registers()[RegId::HP]).unwrap_or(0), context: vm.context.clone(), } } @@ -494,6 +494,7 @@ mod tests { use super::*; use crate::checked_transaction::Checked; use crate::prelude::*; + use fuel_asm::op; use fuel_tx::Script; use test_case::test_case; @@ -505,7 +506,7 @@ mod tests { 0, params.max_gas_per_tx, 0, - Opcode::RET(0x10).to_bytes().to_vec(), + op::ret(0x10).to_bytes().to_vec(), vec![], vec![], vec![], @@ -521,45 +522,44 @@ mod tests { let alloc = 1024; // r[0x10] := 1024 - vm.instruction(Opcode::ADDI(0x10, REG_ZERO, alloc).into()).unwrap(); - vm.instruction(Opcode::ALOC(0x10).into()).unwrap(); + vm.instruction(op::addi(0x10, RegId::ZERO, alloc)).unwrap(); + vm.instruction(op::aloc(0x10)).unwrap(); // r[0x20] := 128 - vm.instruction(Opcode::ADDI(0x20, 0x20, 128).into()).unwrap(); + vm.instruction(op::addi(0x20, 0x20, 128)).unwrap(); for i in 0..alloc { - vm.instruction(Opcode::ADDI(0x21, REG_ZERO, i).into()).unwrap(); - vm.instruction(Opcode::SB(REG_HP, 0x21, (i + 1) as Immediate12).into()) - .unwrap(); + vm.instruction(op::addi(0x21, RegId::ZERO, i)).unwrap(); + vm.instruction(op::sb(RegId::HP, 0x21, (i + 1) as Immediate12)).unwrap(); } // r[0x23] := m[$hp, 0x20] == m[0x12, 0x20] - vm.instruction(Opcode::MEQ(0x23, REG_HP, 0x12, 0x20).into()).unwrap(); + vm.instruction(op::meq(0x23, RegId::HP, 0x12, 0x20)).unwrap(); assert_eq!(0, vm.registers()[0x23]); // r[0x12] := $hp + r[0x20] - vm.instruction(Opcode::ADD(0x12, REG_HP, 0x20).into()).unwrap(); - vm.instruction(Opcode::ADD(0x12, REG_ONE, 0x12).into()).unwrap(); + vm.instruction(op::add(0x12, RegId::HP, 0x20)).unwrap(); + vm.instruction(op::add(0x12, RegId::ONE, 0x12)).unwrap(); // Test ownership - vm.instruction(Opcode::ADD(0x30, REG_HP, REG_ONE).into()).unwrap(); - vm.instruction(Opcode::MCP(0x30, 0x12, 0x20).into()).unwrap(); + vm.instruction(op::add(0x30, RegId::HP, RegId::ONE)).unwrap(); + vm.instruction(op::mcp(0x30, 0x12, 0x20)).unwrap(); // r[0x23] := m[0x30, 0x20] == m[0x12, 0x20] - vm.instruction(Opcode::MEQ(0x23, 0x30, 0x12, 0x20).into()).unwrap(); + vm.instruction(op::meq(0x23, 0x30, 0x12, 0x20)).unwrap(); assert_eq!(1, vm.registers()[0x23]); // Assert ownership - vm.instruction(Opcode::SUBI(0x24, REG_HP, 1).into()).unwrap(); - let ownership_violated = vm.instruction(Opcode::MCP(0x24, 0x12, 0x20).into()); + vm.instruction(op::subi(0x24, RegId::HP, 1)).unwrap(); + let ownership_violated = vm.instruction(op::mcp(0x24, 0x12, 0x20)); assert!(ownership_violated.is_err()); // Assert no panic on overlapping - vm.instruction(Opcode::SUBI(0x25, 0x12, 1).into()).unwrap(); - let overlapping = vm.instruction(Opcode::MCP(REG_HP, 0x25, 0x20).into()); + vm.instruction(op::subi(0x25, 0x12, 1)).unwrap(); + let overlapping = vm.instruction(op::mcp(RegId::HP, 0x25, 0x20)); assert!(overlapping.is_err()); } @@ -574,17 +574,17 @@ mod tests { vm.init_script(Checked::