use rwasm::{
instruction_set,
wasmtime::{compile_wasmtime_module, WasmtimeExecutor},
CompilationConfig, ExecutionEngine, ImportLinker, ImportName, RwasmModule, RwasmModuleBuilder,
RwasmStore, StoreTr, StrategyDefinition, TrapCode, TypedCaller, Value,
};
use rwasm_fuel_policy::{LinearFuelParams, SyscallFuelParams};
use std::sync::Arc;
use wasmparser::ValType;
fn default_import_linker() -> Arc<ImportLinker> {
let mut import_linker = ImportLinker::default();
import_linker.insert_function(
ImportName::new("hello", "world"),
0xff,
SyscallFuelParams::default(),
&[],
&[],
);
Arc::new(import_linker)
}
fn interrupting_syscall_handler<T: Send + Sync>(
_caller: &mut TypedCaller<'_, T>,
_sys_func_idx: u32,
_params: &[Value],
_result: &mut [Value],
) -> Result<(), TrapCode> {
Err(TrapCode::InterruptionCalled)
}
#[test]
fn test_interrupted_call_rwasm() {
let module = RwasmModuleBuilder::new(instruction_set! {
Return
ConsumeFuel(1u32) Call(0xff)
ConsumeFuel(2u32) Return
})
.with_source_pc(1)
.build();
let import_linker = default_import_linker();
let mut store = RwasmStore::<()>::new(
import_linker,
(),
|_caller, _sys_func_idx, _params, _result| -> Result<(), TrapCode> {
Err(TrapCode::InterruptionCalled)
},
Some(100_000),
None,
);
let engine = ExecutionEngine::new();
let err = engine
.execute(&mut store, &module, &[], &mut [])
.unwrap_err();
assert_eq!(err, TrapCode::InterruptionCalled);
assert_eq!(store.fuel_consumed(), 1);
engine.resume(&mut store, &[], &mut []).unwrap();
assert_eq!(store.fuel_consumed(), 3);
}
#[test]
fn test_interrupted_call_rwasm_with_syscall() {
let wasm_binary = wat::parse_str(
r#"
(module
(func $default_call (import "hello" "world") (param i32))
(func (export "main")
(i32.const 300)
(call $default_call)
)
)
"#,
)
.unwrap();
let mut import_linker = ImportLinker::default();
import_linker.insert_function(
ImportName::new("hello", "world"),
0xee,
SyscallFuelParams::LinearFuel(LinearFuelParams {
base_fuel: 7,
param_index: 1,
word_cost: 5,
}),
&[ValType::I32],
&[],
);
let import_linker = Arc::new(import_linker);
let (rwasm_module, _) = RwasmModule::compile(
CompilationConfig::default()
.with_builtins_consume_fuel(true)
.with_consume_fuel_for_params_and_locals(false)
.with_import_linker(import_linker.clone())
.with_entrypoint_name("main".into()),
&wasm_binary,
)
.unwrap();
let mut store = RwasmStore::<()>::new(
import_linker,
(),
|_caller, _sys_func_idx, _params, _result| -> Result<(), TrapCode> { Ok(()) },
Some(100_000),
None,
);
let engine = ExecutionEngine::new();
engine
.execute(&mut store, &rwasm_module, &[], &mut [])
.unwrap();
assert_eq!(store.fuel_consumed(), 1 + 1 + 10 + 10 * 5 + 7);
}
#[test]
fn test_interrupted_call_rwasm_with_overflow() {
let wasm_binary = wat::parse_str(
r#"
(module
(func $default_call (import "hello" "world") (param i32))
(func (export "main")
(i32.const 134_217_729)
(call $default_call)
)
)
"#,
)
.unwrap();
let mut import_linker = ImportLinker::default();
import_linker.insert_function(
ImportName::new("hello", "world"),
0xee,
SyscallFuelParams::LinearFuel(LinearFuelParams {
base_fuel: 7,
param_index: 1,
word_cost: 5,
}),
&[ValType::I32],
&[],
);
let import_linker = Arc::new(import_linker);
let (rwasm_module, _) = RwasmModule::compile(
CompilationConfig::default()
.with_builtins_consume_fuel(true)
.with_import_linker(import_linker.clone())
.with_entrypoint_name("main".into()),
&wasm_binary,
)
.unwrap();
let mut store = RwasmStore::<()>::new(
import_linker,
(),
|_caller, _sys_func_idx, _params, _result| -> Result<(), TrapCode> { Ok(()) },
Some(100_000),
None,
);
let engine = ExecutionEngine::new();
let err = engine
.execute(&mut store, &rwasm_module, &[], &mut [])
.unwrap_err();
assert_eq!(err, TrapCode::IntegerOverflow);
}
#[test]
#[ignore]
fn test_interrupted_call_wasmtime() {
let wasm_binary = wat::parse_str(
r#"
(module
(func $interrupt (import "hello" "world"))
(func (export "main") (result i32)
(i32.const 100)
(call $interrupt)
(i32.const 20)
(call $interrupt)
(i32.const 3)
(i32.add)
(i32.add)
)
)
"#,
)
.unwrap();
let import_linker = default_import_linker();
let (rwasm_module, _) = RwasmModule::compile(
CompilationConfig::default()
.with_import_linker(import_linker.clone())
.with_entrypoint_name("main".into()),
&wasm_binary,
)
.unwrap();
let mut store = RwasmStore::<()>::new(
import_linker.clone(),
(),
interrupting_syscall_handler,
None,
None,
);
let engine = ExecutionEngine::new();
let mut result = [Value::I32(0); 1];
let err = engine
.execute(&mut store, &rwasm_module, &[], &mut result)
.unwrap_err();
assert_eq!(err, TrapCode::InterruptionCalled);
let err = engine.resume(&mut store, &[], &mut result).unwrap_err();
assert_eq!(err, TrapCode::InterruptionCalled);
engine.resume(&mut store, &[], &mut result).unwrap();
assert_eq!(result[0].i32().unwrap(), 123);
let module = compile_wasmtime_module(
CompilationConfig::default().with_consume_fuel(false),
&wasm_binary,
)
.unwrap();
let mut wasmtime_worker = WasmtimeExecutor::new(
module,
import_linker.clone(),
(),
interrupting_syscall_handler,
None,
None,
);
let mut result = [Value::I32(0); 1];
let err = wasmtime_worker
.execute("main", &[], &mut result)
.unwrap_err();
assert_eq!(err, TrapCode::InterruptionCalled);
let err = wasmtime_worker.resume(&[], &mut result).unwrap_err();
assert_eq!(err, TrapCode::InterruptionCalled);
wasmtime_worker.resume(&[], &mut result).unwrap();
assert_eq!(result[0].i32().unwrap(), 123);
}
#[test]
fn test_call_stack_empty_after_trap_in_nested_call() {
let module = RwasmModuleBuilder::new(instruction_set! {
CallInternal(2) Return ConsumeFuel(1u32) Call(0xff)
Trap(TrapCode::UnreachableCodeReached)
})
.build();
let import_linker = default_import_linker();
let mut store = RwasmStore::<()>::new(
import_linker,
(),
|_caller, _sys_func_idx, _params, _result| -> Result<(), TrapCode> {
Err(TrapCode::InterruptionCalled)
},
None,
None,
);
let engine = ExecutionEngine::new();
let err = engine
.execute(&mut store, &module, &[], &mut [])
.unwrap_err();
assert_eq!(err, TrapCode::InterruptionCalled);
let err = engine.resume(&mut store, &[], &mut []).unwrap_err();
assert_eq!(err, TrapCode::UnreachableCodeReached);
}
#[test]
fn test_memory_write_during_interruption() {
let module = RwasmModuleBuilder::new(instruction_set! {
Return
I32Const(1)
MemoryGrow
Drop
Call(0xff)
I32Const(0)
I32Load(0)
Return
})
.with_source_pc(1)
.build();
let import_linker = default_import_linker();
let test_strategy = |strategy: StrategyDefinition| {
let mut executor = strategy
.create_executor(
import_linker.clone(),
(),
|caller, _sys_func_idx, _params, _result| -> Result<(), TrapCode> {
caller.memory_write(0, &[0x01, 0x02, 0x03, 0x04])?;
Err(TrapCode::InterruptionCalled)
},
None,
None,
)
.unwrap();
let mut result = [Value::I32(0); 1];
let err = executor.execute("main", &[], &mut result).unwrap_err();
assert_eq!(err, TrapCode::InterruptionCalled);
executor.resume(&[], &mut result).unwrap();
assert_eq!(result[0].i32().unwrap(), 0x04030201);
};
test_strategy(StrategyDefinition::Rwasm {
module,
engine: ExecutionEngine::acquire_shared(),
});
}