Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }}
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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%.
Expand Down
18 changes: 9 additions & 9 deletions fuel-vm/benches/execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,15 +58,15 @@ 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::<false>().unwrap();
}
})
});

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::<false>()).unwrap();
}
})
});
Expand All @@ -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::<false>().unwrap();
std::ptr::read_volatile(&dummy)
};
}
Expand Down Expand Up @@ -103,15 +103,15 @@ 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::<false>().unwrap();
}
})
});

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::<false>()).unwrap();
}
})
});
Expand All @@ -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::<false>().unwrap();
std::ptr::read_volatile(&dummy)
};
}
Expand Down Expand Up @@ -148,15 +148,15 @@ 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::<false>().unwrap();
}
})
});

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::<false>()).unwrap();
}
})
});
Expand All @@ -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::<false>().unwrap();
std::ptr::read_volatile(&dummy)
};
}
Expand Down
10 changes: 7 additions & 3 deletions fuel-vm/src/interpreter/diff/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<InitialVmState> = latest.rollback_to(&desired).into();
assert_ne!(desired, latest);
latest.reset_vm_state(&diff);
Expand Down Expand Up @@ -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<InitialVmState> = latest.storage_diff().into();
let mut diff: Diff<InitialVmState> = latest.rollback_to(&desired).into();
Expand All @@ -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);
Expand Down
31 changes: 19 additions & 12 deletions fuel-vm/src/interpreter/executors/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ where
V: Verifier,
{
/// Execute the current instruction located in `$m[$pc]`.
pub fn execute(&mut self) -> Result<ExecuteState, InterpreterError<S::DataError>> {
pub fn execute<const PREDICATE: bool>(
&mut self,
) -> Result<ExecuteState, InterpreterError<S::DataError>> {
let raw_instruction = self.fetch_instruction()?;
self.instruction_per_inner(raw_instruction)
self.instruction_per_inner::<PREDICATE>(raw_instruction)
}

/// Reads the current instruction located in `$m[$pc]`,
Expand All @@ -59,17 +61,20 @@ where
}

/// Execute a provided instruction
pub fn instruction<R: Into<RawInstruction> + Copy>(
pub fn instruction<R, const PREDICATE: bool>(
&mut self,
raw: R,
) -> Result<ExecuteState, InterpreterError<S::DataError>> {
) -> Result<ExecuteState, InterpreterError<S::DataError>>
where
R: Into<RawInstruction> + Copy,
{
let raw = raw.into();
let raw = raw.to_be_bytes();

self.instruction_per_inner(raw)
self.instruction_per_inner::<PREDICATE>(raw)
}

fn instruction_per_inner(
fn instruction_per_inner<const PREDICATE: bool>(
&mut self,
raw: [u8; 4],
) -> Result<ExecuteState, InterpreterError<S::DataError>> {
Expand All @@ -80,22 +85,24 @@ where
}
}

self.instruction_inner(raw).map_err(|e| {
self.instruction_inner::<PREDICATE>(raw).map_err(|e| {
InterpreterError::from_runtime(e, RawInstruction::from_be_bytes(raw))
})
}

fn instruction_inner(
fn instruction_inner<const PREDICATE: bool>(
&mut self,
raw: [u8; 4],
) -> IoResult<ExecuteState, S::DataError> {
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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion fuel-vm/src/interpreter/executors/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::<false>() {
// Proceeding with the execution normally
Ok(ExecuteState::Proceed) => continue,
// Debugger events are returned directly to the caller
Expand Down
2 changes: 1 addition & 1 deletion fuel-vm/src/interpreter/executors/predicate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ where
&mut self,
) -> Result<ProgramState, PredicateVerificationFailed> {
loop {
match self.execute()? {
match self.execute::<true>()? {
ExecuteState::Return(r) => {
if r == 1 {
return Ok(ProgramState::Return(r))
Expand Down
4 changes: 0 additions & 4 deletions fuel-vm/src/interpreter/internal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<ContractId, PanicReason> {
internal_contract(&self.context, self.registers.fp(), self.memory.as_ref())
}
Expand Down
39 changes: 23 additions & 16 deletions fuel-vm/src/interpreter/memory/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
Expand All @@ -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(
Expand Down
Loading