diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d4724bf28..99e4a5f45c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,7 +16,7 @@ concurrency: env: CARGO_TERM_COLOR: always - RUST_VERSION: 1.79.0 + RUST_VERSION: 1.81.0 RUST_VERSION_FMT: nightly-2024-02-07 RUST_VERSION_COV: nightly-2024-06-05 GIT_BRANCH: ${{ github.head_ref || github.ref_name }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 32608c9cfb..df854afbe9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/). - [907](https://github.com/FuelLabs/fuel-vm/pull/907): `StorageRead` and `StorageWrite` traits no longer return the number of bytes written. They already required that the whole buffer is used, but now this is reflected in signature and documentation as well. - [914](https://github.com/FuelLabs/fuel-vm/pull/914): The built-in profiler is removed. Use the debugger with single-stepping instead. - [918](https://github.com/FuelLabs/fuel-vm/pull/918): Interpreter is now generic over the validation error handling. No behavioural changes, but the `Interpreter` has one extra type parameter. +- [920](https://github.com/FuelLabs/fuel-vm/pull/920): `Interpreter::execute` and friends are now generic over predicateness of the context. This forces monomorphization so the predicate branch is optimized away on non-predicate execution. ### Changed - [904](https://github.com/FuelLabs/fuel-vm/pull/904): Moved the logic of each opcode into its own function. It helps so reduce the size of the `instruction_inner` function, allowing compiler to do better optimizations. The Mira swaps receive performance improvement in 16.5%. diff --git a/fuel-vm/benches/execution.rs b/fuel-vm/benches/execution.rs index 0397bdde68..58e4b31dc0 100644 --- a/fuel-vm/benches/execution.rs +++ b/fuel-vm/benches/execution.rs @@ -58,7 +58,7 @@ fn execution(c: &mut Criterion) { group_execution.bench_function("Infinite `meq` loop", |b| { b.iter(|| { for _ in 0..1000 { - let _ = interpreter.execute().unwrap(); + let _ = interpreter.execute::().unwrap(); } }) }); @@ -66,7 +66,7 @@ fn execution(c: &mut Criterion) { group_execution.bench_function("Infinite `meq` loop black box", |b| { b.iter(|| { for _ in 0..1000 { - black_box(interpreter.execute()).unwrap(); + black_box(interpreter.execute::()).unwrap(); } }) }); @@ -75,7 +75,7 @@ fn execution(c: &mut Criterion) { b.iter(|| { for _ in 0..1000 { unsafe { - let dummy = interpreter.execute().unwrap(); + let dummy = interpreter.execute::().unwrap(); std::ptr::read_volatile(&dummy) }; } @@ -103,7 +103,7 @@ fn execution(c: &mut Criterion) { group_execution.bench_function("Infinite `add` loop", |b| { b.iter(|| { for _ in 0..1000 { - let _ = interpreter.execute().unwrap(); + let _ = interpreter.execute::().unwrap(); } }) }); @@ -111,7 +111,7 @@ fn execution(c: &mut Criterion) { group_execution.bench_function("Infinite `add` loop black box", |b| { b.iter(|| { for _ in 0..1000 { - black_box(interpreter.execute()).unwrap(); + black_box(interpreter.execute::()).unwrap(); } }) }); @@ -120,7 +120,7 @@ fn execution(c: &mut Criterion) { b.iter(|| { for _ in 0..1000 { unsafe { - let dummy = interpreter.execute().unwrap(); + let dummy = interpreter.execute::().unwrap(); std::ptr::read_volatile(&dummy) }; } @@ -148,7 +148,7 @@ fn execution(c: &mut Criterion) { group_execution.bench_function("Infinite `not` loop", |b| { b.iter(|| { for _ in 0..1000 { - let _ = interpreter.execute().unwrap(); + let _ = interpreter.execute::().unwrap(); } }) }); @@ -156,7 +156,7 @@ fn execution(c: &mut Criterion) { group_execution.bench_function("Infinite `not` loop black box", |b| { b.iter(|| { for _ in 0..1000 { - black_box(interpreter.execute()).unwrap(); + black_box(interpreter.execute::()).unwrap(); } }) }); @@ -165,7 +165,7 @@ fn execution(c: &mut Criterion) { b.iter(|| { for _ in 0..1000 { unsafe { - let dummy = interpreter.execute().unwrap(); + let dummy = interpreter.execute::().unwrap(); std::ptr::read_volatile(&dummy) }; } diff --git a/fuel-vm/src/interpreter/diff/tests.rs b/fuel-vm/src/interpreter/diff/tests.rs index 48206ab44c..d223031e61 100644 --- a/fuel-vm/src/interpreter/diff/tests.rs +++ b/fuel-vm/src/interpreter/diff/tests.rs @@ -42,7 +42,9 @@ fn reset_vm_state() { let desired = Interpreter::<_, _, Script>::with_memory_storage(); let mut latest = Interpreter::<_, _, Script>::with_memory_storage(); latest.set_gas(1_000_000); - latest.instruction(op::addi(0x10, 0x11, 1)).unwrap(); + latest + .instruction::<_, false>(op::addi(0x10, 0x11, 1)) + .unwrap(); let diff: Diff = latest.rollback_to(&desired).into(); assert_ne!(desired, latest); latest.reset_vm_state(&diff); @@ -75,7 +77,9 @@ fn record_and_invert_storage() { ) .unwrap(); latest.set_gas(1_000_000); - latest.instruction(op::addi(0x10, 0x11, 1)).unwrap(); + latest + .instruction::<_, false>(op::addi(0x10, 0x11, 1)) + .unwrap(); let storage_diff: Diff = latest.storage_diff().into(); let mut diff: Diff = latest.rollback_to(&desired).into(); @@ -95,7 +99,7 @@ fn record_and_invert_storage() { ) .unwrap(); d.set_gas(1_000_000); - d.instruction(op::addi(0x10, 0x11, 1)).unwrap(); + d.instruction::<_, false>(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 19a23ee4d9..5bb42f89bc 100644 --- a/fuel-vm/src/interpreter/executors/instruction.rs +++ b/fuel-vm/src/interpreter/executors/instruction.rs @@ -32,9 +32,11 @@ where V: Verifier, { /// Execute the current instruction located in `$m[$pc]`. - pub fn execute(&mut self) -> Result> { + pub fn execute( + &mut self, + ) -> Result> { let raw_instruction = self.fetch_instruction()?; - self.instruction_per_inner(raw_instruction) + self.instruction_per_inner::(raw_instruction) } /// Reads the current instruction located in `$m[$pc]`, @@ -59,17 +61,20 @@ where } /// Execute a provided instruction - pub fn instruction + Copy>( + pub fn instruction( &mut self, raw: R, - ) -> Result> { + ) -> Result> + where + R: Into + Copy, + { let raw = raw.into(); let raw = raw.to_be_bytes(); - self.instruction_per_inner(raw) + self.instruction_per_inner::(raw) } - fn instruction_per_inner( + fn instruction_per_inner( &mut self, raw: [u8; 4], ) -> Result> { @@ -80,22 +85,24 @@ where } } - self.instruction_inner(raw).map_err(|e| { + self.instruction_inner::(raw).map_err(|e| { InterpreterError::from_runtime(e, RawInstruction::from_be_bytes(raw)) }) } - fn instruction_inner( + fn instruction_inner( &mut self, raw: [u8; 4], ) -> IoResult { let instruction = Instruction::try_from(raw) .map_err(|_| RuntimeError::from(PanicReason::InvalidInstruction))?; - // TODO additional branch that might be optimized after - // https://github.com/FuelLabs/fuel-asm/issues/68 - if self.is_predicate() && !instruction.opcode().is_predicate_allowed() { - return Err(PanicReason::ContractInstructionNotAllowed.into()) + if PREDICATE { + // TODO additional branch that might be optimized after + // https://github.com/FuelLabs/fuel-asm/issues/68 + if !instruction.opcode().is_predicate_allowed() { + return Err(PanicReason::ContractInstructionNotAllowed.into()) + } } instruction.execute(self) 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 ad60ff4559..37d031c3e0 100644 --- a/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs +++ b/fuel-vm/src/interpreter/executors/instruction/tests/reserved_registers.rs @@ -79,7 +79,7 @@ fn cant_write_to_reserved_registers(raw_random_instruction: u32) -> TestResult { .expect("failed dynamic checks"); vm.init_script(tx).expect("Failed to init VM"); - let res = vm.instruction(raw_random_instruction); + let res = vm.instruction::<_, false>(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 diff --git a/fuel-vm/src/interpreter/executors/main.rs b/fuel-vm/src/interpreter/executors/main.rs index 23fcbe8fea..62c574b9ec 100644 --- a/fuel-vm/src/interpreter/executors/main.rs +++ b/fuel-vm/src/interpreter/executors/main.rs @@ -981,7 +981,7 @@ where // Check whether the instruction will be executed in a call context let in_call = !self.frames.is_empty(); - match self.execute() { + match self.execute::() { // Proceeding with the execution normally Ok(ExecuteState::Proceed) => continue, // Debugger events are returned directly to the caller diff --git a/fuel-vm/src/interpreter/executors/predicate.rs b/fuel-vm/src/interpreter/executors/predicate.rs index 5d91788f59..2d2f23cb67 100644 --- a/fuel-vm/src/interpreter/executors/predicate.rs +++ b/fuel-vm/src/interpreter/executors/predicate.rs @@ -30,7 +30,7 @@ where &mut self, ) -> Result { loop { - match self.execute()? { + match self.execute::()? { ExecuteState::Return(r) => { if r == 1 { return Ok(ProgramState::Return(r)) diff --git a/fuel-vm/src/interpreter/internal.rs b/fuel-vm/src/interpreter/internal.rs index 186d27aa84..e3b9172d60 100644 --- a/fuel-vm/src/interpreter/internal.rs +++ b/fuel-vm/src/interpreter/internal.rs @@ -111,10 +111,6 @@ where &self.context } - pub(crate) const fn is_predicate(&self) -> bool { - self.context.is_predicate() - } - pub(crate) fn internal_contract(&self) -> Result { internal_contract(&self.context, self.registers.fp(), self.memory.as_ref()) } diff --git a/fuel-vm/src/interpreter/memory/tests.rs b/fuel-vm/src/interpreter/memory/tests.rs index ee27f996dc..77acf3f134 100644 --- a/fuel-vm/src/interpreter/memory/tests.rs +++ b/fuel-vm/src/interpreter/memory/tests.rs @@ -48,45 +48,51 @@ fn memcopy() { let alloc = 1024; // r[0x10] := 1024 - vm.instruction(op::addi(0x10, RegId::ZERO, alloc)).unwrap(); - vm.instruction(op::aloc(0x10)).unwrap(); + vm.instruction::<_, false>(op::addi(0x10, RegId::ZERO, alloc)) + .unwrap(); + vm.instruction::<_, false>(op::aloc(0x10)).unwrap(); // r[0x20] := 128 - vm.instruction(op::addi(0x20, RegId::ZERO, 128)).unwrap(); + vm.instruction::<_, false>(op::addi(0x20, RegId::ZERO, 128)) + .unwrap(); for i in 0..alloc { - vm.instruction(op::addi(0x21, RegId::ZERO, i)).unwrap(); - vm.instruction(op::sb(RegId::HP, 0x21, i as Immediate12)) + vm.instruction::<_, false>(op::addi(0x21, RegId::ZERO, i)) + .unwrap(); + vm.instruction::<_, false>(op::sb(RegId::HP, 0x21, i as Immediate12)) .unwrap(); } // r[0x23] := m[$hp, 0x20] == m[$zero, 0x20] - vm.instruction(op::meq(0x23, RegId::HP, RegId::ZERO, 0x20)) + vm.instruction::<_, false>(op::meq(0x23, RegId::HP, RegId::ZERO, 0x20)) .unwrap(); assert_eq!(0, vm.registers()[0x23]); // r[0x12] := $hp + r[0x20] - vm.instruction(op::add(0x12, RegId::HP, 0x20)).unwrap(); + vm.instruction::<_, false>(op::add(0x12, RegId::HP, 0x20)) + .unwrap(); // Test ownership - vm.instruction(op::mcp(RegId::HP, 0x12, 0x20)).unwrap(); + vm.instruction::<_, false>(op::mcp(RegId::HP, 0x12, 0x20)) + .unwrap(); // r[0x23] := m[0x30, 0x20] == m[0x12, 0x20] - vm.instruction(op::meq(0x23, RegId::HP, 0x12, 0x20)) + vm.instruction::<_, false>(op::meq(0x23, RegId::HP, 0x12, 0x20)) .unwrap(); assert_eq!(1, vm.registers()[0x23]); // Assert ownership - vm.instruction(op::subi(0x24, RegId::HP, 1)).unwrap(); // TODO: look into this - let ownership_violated = vm.instruction(op::mcp(0x24, 0x12, 0x20)); + vm.instruction::<_, false>(op::subi(0x24, RegId::HP, 1)) + .unwrap(); // TODO: look into this + let ownership_violated = vm.instruction::<_, false>(op::mcp(0x24, 0x12, 0x20)); assert!(ownership_violated.is_err()); // Assert no panic on overlapping - vm.instruction(op::subi(0x25, 0x12, 1)).unwrap(); - let overlapping = vm.instruction(op::mcp(RegId::HP, 0x25, 0x20)); + vm.instruction::<_, false>(op::subi(0x25, 0x12, 1)).unwrap(); + let overlapping = vm.instruction::<_, false>(op::mcp(RegId::HP, 0x25, 0x20)); assert!(overlapping.is_err()); } @@ -112,11 +118,12 @@ fn stack_alloc_ownership() { .unwrap(); vm.init_script(tx).expect("Failed to init VM"); - vm.instruction(op::move_(0x10, RegId::SP)).unwrap(); - vm.instruction(op::cfei(2)).unwrap(); + vm.instruction::<_, false>(op::move_(0x10, RegId::SP)) + .unwrap(); + vm.instruction::<_, false>(op::cfei(2)).unwrap(); // Assert allocated stack is writable - vm.instruction(op::mcli(0x10, 2)).unwrap(); + vm.instruction::<_, false>(op::mcli(0x10, 2)).unwrap(); } #[test_case(