Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
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