diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 0000000..ef06468 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,2 @@ +[default.extend-words] +groth = "groth" #groth16 crypto function diff --git a/c-api/libvmexeccapi.h b/c-api/libvmexeccapi.h index f183140..9d7097b 100644 --- a/c-api/libvmexeccapi.h +++ b/c-api/libvmexeccapi.h @@ -313,12 +313,165 @@ typedef struct vm_exec_vm_hook_c_func_pointers { int32_t (*managed_verify_blsaggregated_signature_func_ptr)(void *context, int32_t key_handle, int32_t message_handle, int32_t sig_handle); } vm_exec_vm_hook_c_func_pointers; +/** + * FFI-safe mirror of `CompilationOptionsLegacy`. + * + * This is a distinct type rather than a cast of `CompilationOptionsLegacy` + * itself, so that the general-purpose struct is free to change shape + * without touching the C ABI, and vice versa. Fields that are `usize` on + * the Rust side are fixed to `u64` here, since `usize` has no guaranteed + * width across host platforms. + */ typedef struct vm_exec_compilation_options_t { - + uint64_t gas_limit; + uint64_t unmetered_locals; + uint64_t max_memory_grow; + uint64_t max_memory_grow_delta; + bool opcode_trace; + bool metering; + bool runtime_breakpoints; } vm_exec_compilation_options_t; +/** + * FFI-safe mirror of `OpcodeCost`. + * + * This is a distinct type rather than a cast of `OpcodeCost` itself, so that + * the general-purpose struct is free to change shape without touching the C + * ABI, and vice versa. + */ typedef struct vm_exec_opcode_cost_t { - + uint32_t opcode_block; + uint32_t opcode_br; + uint32_t opcode_brif; + uint32_t opcode_brtable; + uint32_t opcode_call; + uint32_t opcode_callindirect; + uint32_t opcode_catch; + uint32_t opcode_catchall; + uint32_t opcode_delegate; + uint32_t opcode_drop; + uint32_t opcode_else; + uint32_t opcode_end; + uint32_t opcode_globalget; + uint32_t opcode_globalset; + uint32_t opcode_i32add; + uint32_t opcode_i32and; + uint32_t opcode_i32clz; + uint32_t opcode_i32const; + uint32_t opcode_i32ctz; + uint32_t opcode_i32divs; + uint32_t opcode_i32divu; + uint32_t opcode_i32eq; + uint32_t opcode_i32eqz; + uint32_t opcode_i32extend16s; + uint32_t opcode_i32extend8s; + uint32_t opcode_i32ges; + uint32_t opcode_i32geu; + uint32_t opcode_i32gts; + uint32_t opcode_i32gtu; + uint32_t opcode_i32les; + uint32_t opcode_i32leu; + uint32_t opcode_i32load; + uint32_t opcode_i32load16s; + uint32_t opcode_i32load16u; + uint32_t opcode_i32load8s; + uint32_t opcode_i32load8u; + uint32_t opcode_i32lts; + uint32_t opcode_i32ltu; + uint32_t opcode_i32mul; + uint32_t opcode_i32ne; + uint32_t opcode_i32or; + uint32_t opcode_i32popcnt; + uint32_t opcode_i32rems; + uint32_t opcode_i32remu; + uint32_t opcode_i32rotl; + uint32_t opcode_i32rotr; + uint32_t opcode_i32shl; + uint32_t opcode_i32shrs; + uint32_t opcode_i32shru; + uint32_t opcode_i32store; + uint32_t opcode_i32store16; + uint32_t opcode_i32store8; + uint32_t opcode_i32sub; + uint32_t opcode_i32wrapi64; + uint32_t opcode_i32xor; + uint32_t opcode_i64add; + uint32_t opcode_i64and; + uint32_t opcode_i64clz; + uint32_t opcode_i64const; + uint32_t opcode_i64ctz; + uint32_t opcode_i64divs; + uint32_t opcode_i64divu; + uint32_t opcode_i64eq; + uint32_t opcode_i64eqz; + uint32_t opcode_i64extend16s; + uint32_t opcode_i64extend32s; + uint32_t opcode_i64extend8s; + uint32_t opcode_i64extendi32s; + uint32_t opcode_i64extendi32u; + uint32_t opcode_i64ges; + uint32_t opcode_i64geu; + uint32_t opcode_i64gts; + uint32_t opcode_i64gtu; + uint32_t opcode_i64les; + uint32_t opcode_i64leu; + uint32_t opcode_i64load; + uint32_t opcode_i64load16s; + uint32_t opcode_i64load16u; + uint32_t opcode_i64load32s; + uint32_t opcode_i64load32u; + uint32_t opcode_i64load8s; + uint32_t opcode_i64load8u; + uint32_t opcode_i64lts; + uint32_t opcode_i64ltu; + uint32_t opcode_i64mul; + uint32_t opcode_i64ne; + uint32_t opcode_i64or; + uint32_t opcode_i64popcnt; + uint32_t opcode_i64rems; + uint32_t opcode_i64remu; + uint32_t opcode_i64rotl; + uint32_t opcode_i64rotr; + uint32_t opcode_i64shl; + uint32_t opcode_i64shrs; + uint32_t opcode_i64shru; + uint32_t opcode_i64store; + uint32_t opcode_i64store16; + uint32_t opcode_i64store32; + uint32_t opcode_i64store8; + uint32_t opcode_i64sub; + uint32_t opcode_i64xor; + uint32_t opcode_if; + uint32_t opcode_localget; + uint32_t opcode_localset; + uint32_t opcode_localtee; + uint32_t opcode_loop; + uint32_t opcode_memorygrow; + uint32_t opcode_memorysize; + uint32_t opcode_nop; + uint32_t opcode_reffunc; + uint32_t opcode_refisnull; + uint32_t opcode_refnull; + uint32_t opcode_rethrow; + uint32_t opcode_return; + uint32_t opcode_returncall; + uint32_t opcode_returncallindirect; + uint32_t opcode_select; + uint32_t opcode_tableget; + uint32_t opcode_tablegrow; + uint32_t opcode_tableinit; + uint32_t opcode_tableset; + uint32_t opcode_tablesize; + uint32_t opcode_throw; + uint32_t opcode_try; + uint32_t opcode_typedselect; + uint32_t opcode_unreachable; + uint32_t opcode_unwind; + uint32_t opcode_memorycopy; + uint32_t opcode_memoryfill; + uint32_t opcode_memorycopyperbyte; + uint32_t opcode_memoryfillperbyte; + uint32_t opcode_localallocate; } vm_exec_opcode_cost_t; /** @@ -600,7 +753,7 @@ enum vm_exec_result_t vm_exec_instance_memory_grow(struct vm_exec_instance_t *in * * C API function, works with raw object pointers. */ -enum vm_exec_result_t vm_exec_set_opcode_costs(struct vm_exec_executor_t *executor_ptr, const struct vm_exec_opcode_cost_t *opcode_cost_ptr); +enum vm_exec_result_t vm_exec_set_opcode_config(struct vm_exec_executor_t *executor_ptr, int32_t opcode_version_code, const struct vm_exec_opcode_cost_t *opcode_cost_ptr); /** * Sets the number of points(gas) limit for the given instance. diff --git a/c-api/src/capi_compilation_options.rs b/c-api/src/capi_compilation_options.rs new file mode 100644 index 0000000..704bf89 --- /dev/null +++ b/c-api/src/capi_compilation_options.rs @@ -0,0 +1,36 @@ +//! FFI-safe mirror of `CompilationOptionsLegacy`. + +use multiversx_chain_vm_executor::CompilationOptionsLegacy; + +/// FFI-safe mirror of `CompilationOptionsLegacy`. +/// +/// This is a distinct type rather than a cast of `CompilationOptionsLegacy` +/// itself, so that the general-purpose struct is free to change shape +/// without touching the C ABI, and vice versa. Fields that are `usize` on +/// the Rust side are fixed to `u64` here, since `usize` has no guaranteed +/// width across host platforms. +#[allow(non_camel_case_types)] +#[repr(C)] +pub struct vm_exec_compilation_options_t { + pub gas_limit: u64, + pub unmetered_locals: u64, + pub max_memory_grow: u64, + pub max_memory_grow_delta: u64, + pub opcode_trace: bool, + pub metering: bool, + pub runtime_breakpoints: bool, +} + +impl vm_exec_compilation_options_t { + pub(crate) fn to_legacy(&self) -> CompilationOptionsLegacy { + CompilationOptionsLegacy { + gas_limit: self.gas_limit, + unmetered_locals: self.unmetered_locals as usize, + max_memory_grow: self.max_memory_grow as usize, + max_memory_grow_delta: self.max_memory_grow_delta as usize, + opcode_trace: self.opcode_trace, + metering: self.metering, + runtime_breakpoints: self.runtime_breakpoints, + } + } +} diff --git a/c-api/src/capi_instance.rs b/c-api/src/capi_instance.rs index 5e1edab..d38544e 100644 --- a/c-api/src/capi_instance.rs +++ b/c-api/src/capi_instance.rs @@ -1,13 +1,14 @@ //! Instantiate a module, call functions, and read exports. use crate::{ + capi_compilation_options::vm_exec_compilation_options_t, capi_executor::{CapiExecutor, vm_exec_executor_t}, service_singleton::with_service, string_copy, vm_exec_result_t, }; use libc::{c_char, c_int}; use meta::capi_safe_unwind; -use multiversx_chain_vm_executor::{CompilationOptionsLegacy, InstanceLegacy}; +use multiversx_chain_vm_executor::InstanceLegacy; use std::{ffi::CStr, slice}; /// Opaque pointer to a `wasmer_runtime::Instance` value in Rust. @@ -19,10 +20,6 @@ use std::{ffi::CStr, slice}; #[repr(C)] pub struct vm_exec_instance_t; -#[allow(non_camel_case_types)] -#[repr(C)] -pub struct vm_exec_compilation_options_t; - pub struct CapiInstance { pub(crate) content: Box, } @@ -51,13 +48,14 @@ pub unsafe extern "C" fn vm_exec_new_instance( return vm_exec_result_t::VM_EXEC_ERROR; } + return_if_ptr_null!(options_ptr, "compilation options ptr is null"); + let wasm_bytes: &[u8] = unsafe { slice::from_raw_parts(wasm_bytes_ptr, wasm_bytes_len as usize) }; - let compilation_options: &CompilationOptionsLegacy = - unsafe { &*(options_ptr as *const CompilationOptionsLegacy) }; + let compilation_options = unsafe { (*options_ptr).to_legacy() }; let instance_result = capi_executor .content - .new_instance(wasm_bytes, compilation_options); + .new_instance(wasm_bytes, &compilation_options); match instance_result { Ok(instance_box) => { let capi_instance = CapiInstance { diff --git a/c-api/src/capi_instance_cache.rs b/c-api/src/capi_instance_cache.rs index 807e1cf..58a0380 100644 --- a/c-api/src/capi_instance_cache.rs +++ b/c-api/src/capi_instance_cache.rs @@ -1,11 +1,11 @@ use std::slice; use meta::capi_safe_unwind; -use multiversx_chain_vm_executor::CompilationOptionsLegacy; use crate::{ + capi_compilation_options::vm_exec_compilation_options_t, capi_executor::{CapiExecutor, vm_exec_executor_t}, - capi_instance::{CapiInstance, vm_exec_compilation_options_t, vm_exec_instance_t}, + capi_instance::{CapiInstance, vm_exec_instance_t}, service_singleton::with_service, vm_exec_result_t, }; @@ -68,13 +68,14 @@ pub unsafe extern "C" fn vm_exec_instance_from_cache( return vm_exec_result_t::VM_EXEC_ERROR; } + return_if_ptr_null!(options_ptr, "compilation options ptr is null"); + let cache_bytes: &[u8] = unsafe { slice::from_raw_parts(cache_bytes_ptr, cache_bytes_len as usize) }; - let compilation_options: &CompilationOptionsLegacy = - unsafe { &*(options_ptr as *const CompilationOptionsLegacy) }; + let compilation_options = unsafe { (*options_ptr).to_legacy() }; let instance_result = capi_executor .content - .new_instance_from_cache(cache_bytes, compilation_options); + .new_instance_from_cache(cache_bytes, &compilation_options); match instance_result { Ok(instance_box) => { let capi_instance = CapiInstance { diff --git a/c-api/src/capi_metering.rs b/c-api/src/capi_metering.rs index ffb6576..350d797 100644 --- a/c-api/src/capi_metering.rs +++ b/c-api/src/capi_metering.rs @@ -1,13 +1,10 @@ use crate::capi_executor::{CapiExecutor, vm_exec_executor_t}; use crate::capi_instance::{CapiInstance, vm_exec_instance_t}; +use crate::capi_opcode_cost::vm_exec_opcode_cost_t; use crate::service_singleton::with_service; use crate::vm_exec_result_t; use meta::capi_safe_unwind; -use multiversx_chain_vm_executor::OpcodeCost; - -#[allow(non_camel_case_types)] -#[repr(C)] -pub struct vm_exec_opcode_cost_t; +use multiversx_chain_vm_executor::{OpcodeConfig, OpcodeVersion}; /// Sets the opcode costs for the given executor. /// @@ -21,14 +18,30 @@ pub struct vm_exec_opcode_cost_t; #[allow(clippy::cast_ptr_alignment)] #[unsafe(no_mangle)] #[capi_safe_unwind(vm_exec_result_t::VM_EXEC_ERROR)] -pub unsafe extern "C" fn vm_exec_set_opcode_costs( +pub unsafe extern "C" fn vm_exec_set_opcode_config( executor_ptr: *mut vm_exec_executor_t, + opcode_version_code: i32, opcode_cost_ptr: *const vm_exec_opcode_cost_t, ) -> vm_exec_result_t { let capi_executor = cast_input_ptr!(executor_ptr, CapiExecutor, "executor ptr is null"); - let opcode_costs: &OpcodeCost = unsafe { &*(opcode_cost_ptr as *const OpcodeCost) }; - let result = capi_executor.content.set_opcode_cost(opcode_costs); + let Some(opcode_version) = OpcodeVersion::from_i32(opcode_version_code) else { + with_service(|service| { + service.update_last_error_str(format!( + "invalid opcode version code: {}", + opcode_version_code + )) + }); + return vm_exec_result_t::VM_EXEC_ERROR; + }; + + let opcode_cost = unsafe { (*opcode_cost_ptr).to_opcode_cost() }; + let opcode_config = OpcodeConfig { + opcode_version, + opcode_cost, + }; + + let result = capi_executor.content.set_opcode_config(opcode_config); match result { Ok(()) => vm_exec_result_t::VM_EXEC_OK, Err(message) => { diff --git a/c-api/src/capi_opcode_cost.rs b/c-api/src/capi_opcode_cost.rs new file mode 100644 index 0000000..26abdb2 --- /dev/null +++ b/c-api/src/capi_opcode_cost.rs @@ -0,0 +1,288 @@ +// Code generated by vmhooks generator. DO NOT EDIT. + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!! AUTO-GENERATED FILE !!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +use multiversx_chain_vm_executor::OpcodeCost; + +/// FFI-safe mirror of `OpcodeCost`. +/// +/// This is a distinct type rather than a cast of `OpcodeCost` itself, so that +/// the general-purpose struct is free to change shape without touching the C +/// ABI, and vice versa. +#[allow(non_camel_case_types)] +#[repr(C)] +pub struct vm_exec_opcode_cost_t { + pub opcode_block: u32, + pub opcode_br: u32, + pub opcode_brif: u32, + pub opcode_brtable: u32, + pub opcode_call: u32, + pub opcode_callindirect: u32, + pub opcode_catch: u32, + pub opcode_catchall: u32, + pub opcode_delegate: u32, + pub opcode_drop: u32, + pub opcode_else: u32, + pub opcode_end: u32, + pub opcode_globalget: u32, + pub opcode_globalset: u32, + pub opcode_i32add: u32, + pub opcode_i32and: u32, + pub opcode_i32clz: u32, + pub opcode_i32const: u32, + pub opcode_i32ctz: u32, + pub opcode_i32divs: u32, + pub opcode_i32divu: u32, + pub opcode_i32eq: u32, + pub opcode_i32eqz: u32, + pub opcode_i32extend16s: u32, + pub opcode_i32extend8s: u32, + pub opcode_i32ges: u32, + pub opcode_i32geu: u32, + pub opcode_i32gts: u32, + pub opcode_i32gtu: u32, + pub opcode_i32les: u32, + pub opcode_i32leu: u32, + pub opcode_i32load: u32, + pub opcode_i32load16s: u32, + pub opcode_i32load16u: u32, + pub opcode_i32load8s: u32, + pub opcode_i32load8u: u32, + pub opcode_i32lts: u32, + pub opcode_i32ltu: u32, + pub opcode_i32mul: u32, + pub opcode_i32ne: u32, + pub opcode_i32or: u32, + pub opcode_i32popcnt: u32, + pub opcode_i32rems: u32, + pub opcode_i32remu: u32, + pub opcode_i32rotl: u32, + pub opcode_i32rotr: u32, + pub opcode_i32shl: u32, + pub opcode_i32shrs: u32, + pub opcode_i32shru: u32, + pub opcode_i32store: u32, + pub opcode_i32store16: u32, + pub opcode_i32store8: u32, + pub opcode_i32sub: u32, + pub opcode_i32wrapi64: u32, + pub opcode_i32xor: u32, + pub opcode_i64add: u32, + pub opcode_i64and: u32, + pub opcode_i64clz: u32, + pub opcode_i64const: u32, + pub opcode_i64ctz: u32, + pub opcode_i64divs: u32, + pub opcode_i64divu: u32, + pub opcode_i64eq: u32, + pub opcode_i64eqz: u32, + pub opcode_i64extend16s: u32, + pub opcode_i64extend32s: u32, + pub opcode_i64extend8s: u32, + pub opcode_i64extendi32s: u32, + pub opcode_i64extendi32u: u32, + pub opcode_i64ges: u32, + pub opcode_i64geu: u32, + pub opcode_i64gts: u32, + pub opcode_i64gtu: u32, + pub opcode_i64les: u32, + pub opcode_i64leu: u32, + pub opcode_i64load: u32, + pub opcode_i64load16s: u32, + pub opcode_i64load16u: u32, + pub opcode_i64load32s: u32, + pub opcode_i64load32u: u32, + pub opcode_i64load8s: u32, + pub opcode_i64load8u: u32, + pub opcode_i64lts: u32, + pub opcode_i64ltu: u32, + pub opcode_i64mul: u32, + pub opcode_i64ne: u32, + pub opcode_i64or: u32, + pub opcode_i64popcnt: u32, + pub opcode_i64rems: u32, + pub opcode_i64remu: u32, + pub opcode_i64rotl: u32, + pub opcode_i64rotr: u32, + pub opcode_i64shl: u32, + pub opcode_i64shrs: u32, + pub opcode_i64shru: u32, + pub opcode_i64store: u32, + pub opcode_i64store16: u32, + pub opcode_i64store32: u32, + pub opcode_i64store8: u32, + pub opcode_i64sub: u32, + pub opcode_i64xor: u32, + pub opcode_if: u32, + pub opcode_localget: u32, + pub opcode_localset: u32, + pub opcode_localtee: u32, + pub opcode_loop: u32, + pub opcode_memorygrow: u32, + pub opcode_memorysize: u32, + pub opcode_nop: u32, + pub opcode_reffunc: u32, + pub opcode_refisnull: u32, + pub opcode_refnull: u32, + pub opcode_rethrow: u32, + pub opcode_return: u32, + pub opcode_returncall: u32, + pub opcode_returncallindirect: u32, + pub opcode_select: u32, + pub opcode_tableget: u32, + pub opcode_tablegrow: u32, + pub opcode_tableinit: u32, + pub opcode_tableset: u32, + pub opcode_tablesize: u32, + pub opcode_throw: u32, + pub opcode_try: u32, + pub opcode_typedselect: u32, + pub opcode_unreachable: u32, + pub opcode_unwind: u32, + pub opcode_memorycopy: u32, + pub opcode_memoryfill: u32, + pub opcode_memorycopyperbyte: u32, + pub opcode_memoryfillperbyte: u32, + pub opcode_localallocate: u32, +} + +impl vm_exec_opcode_cost_t { + pub(crate) fn to_opcode_cost(&self) -> OpcodeCost { + OpcodeCost { + opcode_block: self.opcode_block, + opcode_br: self.opcode_br, + opcode_brif: self.opcode_brif, + opcode_brtable: self.opcode_brtable, + opcode_call: self.opcode_call, + opcode_callindirect: self.opcode_callindirect, + opcode_catch: self.opcode_catch, + opcode_catchall: self.opcode_catchall, + opcode_delegate: self.opcode_delegate, + opcode_drop: self.opcode_drop, + opcode_else: self.opcode_else, + opcode_end: self.opcode_end, + opcode_globalget: self.opcode_globalget, + opcode_globalset: self.opcode_globalset, + opcode_i32add: self.opcode_i32add, + opcode_i32and: self.opcode_i32and, + opcode_i32clz: self.opcode_i32clz, + opcode_i32const: self.opcode_i32const, + opcode_i32ctz: self.opcode_i32ctz, + opcode_i32divs: self.opcode_i32divs, + opcode_i32divu: self.opcode_i32divu, + opcode_i32eq: self.opcode_i32eq, + opcode_i32eqz: self.opcode_i32eqz, + opcode_i32extend16s: self.opcode_i32extend16s, + opcode_i32extend8s: self.opcode_i32extend8s, + opcode_i32ges: self.opcode_i32ges, + opcode_i32geu: self.opcode_i32geu, + opcode_i32gts: self.opcode_i32gts, + opcode_i32gtu: self.opcode_i32gtu, + opcode_i32les: self.opcode_i32les, + opcode_i32leu: self.opcode_i32leu, + opcode_i32load: self.opcode_i32load, + opcode_i32load16s: self.opcode_i32load16s, + opcode_i32load16u: self.opcode_i32load16u, + opcode_i32load8s: self.opcode_i32load8s, + opcode_i32load8u: self.opcode_i32load8u, + opcode_i32lts: self.opcode_i32lts, + opcode_i32ltu: self.opcode_i32ltu, + opcode_i32mul: self.opcode_i32mul, + opcode_i32ne: self.opcode_i32ne, + opcode_i32or: self.opcode_i32or, + opcode_i32popcnt: self.opcode_i32popcnt, + opcode_i32rems: self.opcode_i32rems, + opcode_i32remu: self.opcode_i32remu, + opcode_i32rotl: self.opcode_i32rotl, + opcode_i32rotr: self.opcode_i32rotr, + opcode_i32shl: self.opcode_i32shl, + opcode_i32shrs: self.opcode_i32shrs, + opcode_i32shru: self.opcode_i32shru, + opcode_i32store: self.opcode_i32store, + opcode_i32store16: self.opcode_i32store16, + opcode_i32store8: self.opcode_i32store8, + opcode_i32sub: self.opcode_i32sub, + opcode_i32wrapi64: self.opcode_i32wrapi64, + opcode_i32xor: self.opcode_i32xor, + opcode_i64add: self.opcode_i64add, + opcode_i64and: self.opcode_i64and, + opcode_i64clz: self.opcode_i64clz, + opcode_i64const: self.opcode_i64const, + opcode_i64ctz: self.opcode_i64ctz, + opcode_i64divs: self.opcode_i64divs, + opcode_i64divu: self.opcode_i64divu, + opcode_i64eq: self.opcode_i64eq, + opcode_i64eqz: self.opcode_i64eqz, + opcode_i64extend16s: self.opcode_i64extend16s, + opcode_i64extend32s: self.opcode_i64extend32s, + opcode_i64extend8s: self.opcode_i64extend8s, + opcode_i64extendi32s: self.opcode_i64extendi32s, + opcode_i64extendi32u: self.opcode_i64extendi32u, + opcode_i64ges: self.opcode_i64ges, + opcode_i64geu: self.opcode_i64geu, + opcode_i64gts: self.opcode_i64gts, + opcode_i64gtu: self.opcode_i64gtu, + opcode_i64les: self.opcode_i64les, + opcode_i64leu: self.opcode_i64leu, + opcode_i64load: self.opcode_i64load, + opcode_i64load16s: self.opcode_i64load16s, + opcode_i64load16u: self.opcode_i64load16u, + opcode_i64load32s: self.opcode_i64load32s, + opcode_i64load32u: self.opcode_i64load32u, + opcode_i64load8s: self.opcode_i64load8s, + opcode_i64load8u: self.opcode_i64load8u, + opcode_i64lts: self.opcode_i64lts, + opcode_i64ltu: self.opcode_i64ltu, + opcode_i64mul: self.opcode_i64mul, + opcode_i64ne: self.opcode_i64ne, + opcode_i64or: self.opcode_i64or, + opcode_i64popcnt: self.opcode_i64popcnt, + opcode_i64rems: self.opcode_i64rems, + opcode_i64remu: self.opcode_i64remu, + opcode_i64rotl: self.opcode_i64rotl, + opcode_i64rotr: self.opcode_i64rotr, + opcode_i64shl: self.opcode_i64shl, + opcode_i64shrs: self.opcode_i64shrs, + opcode_i64shru: self.opcode_i64shru, + opcode_i64store: self.opcode_i64store, + opcode_i64store16: self.opcode_i64store16, + opcode_i64store32: self.opcode_i64store32, + opcode_i64store8: self.opcode_i64store8, + opcode_i64sub: self.opcode_i64sub, + opcode_i64xor: self.opcode_i64xor, + opcode_if: self.opcode_if, + opcode_localget: self.opcode_localget, + opcode_localset: self.opcode_localset, + opcode_localtee: self.opcode_localtee, + opcode_loop: self.opcode_loop, + opcode_memorygrow: self.opcode_memorygrow, + opcode_memorysize: self.opcode_memorysize, + opcode_nop: self.opcode_nop, + opcode_reffunc: self.opcode_reffunc, + opcode_refisnull: self.opcode_refisnull, + opcode_refnull: self.opcode_refnull, + opcode_rethrow: self.opcode_rethrow, + opcode_return: self.opcode_return, + opcode_returncall: self.opcode_returncall, + opcode_returncallindirect: self.opcode_returncallindirect, + opcode_select: self.opcode_select, + opcode_tableget: self.opcode_tableget, + opcode_tablegrow: self.opcode_tablegrow, + opcode_tableinit: self.opcode_tableinit, + opcode_tableset: self.opcode_tableset, + opcode_tablesize: self.opcode_tablesize, + opcode_throw: self.opcode_throw, + opcode_try: self.opcode_try, + opcode_typedselect: self.opcode_typedselect, + opcode_unreachable: self.opcode_unreachable, + opcode_unwind: self.opcode_unwind, + opcode_memorycopy: self.opcode_memorycopy, + opcode_memoryfill: self.opcode_memoryfill, + opcode_memorycopyperbyte: self.opcode_memorycopyperbyte, + opcode_memoryfillperbyte: self.opcode_memoryfillperbyte, + opcode_localallocate: self.opcode_localallocate, + } + } +} diff --git a/c-api/src/lib.rs b/c-api/src/lib.rs index 1329102..0e28529 100644 --- a/c-api/src/lib.rs +++ b/c-api/src/lib.rs @@ -3,6 +3,7 @@ mod macros; mod basic_types; pub mod capi_breakpoints; +pub mod capi_compilation_options; pub mod capi_error; pub mod capi_executor; pub mod capi_instance; @@ -10,6 +11,7 @@ pub mod capi_instance_cache; pub mod capi_logger; pub mod capi_memory; pub mod capi_metering; +pub mod capi_opcode_cost; pub mod capi_vm_hook_pointers; pub mod capi_vm_hooks; pub mod service_singleton; diff --git a/vm-executor-experimental/src/middlewares.rs b/vm-executor-experimental/src/middlewares.rs index 3d872da..3b6ae11 100644 --- a/vm-executor-experimental/src/middlewares.rs +++ b/vm-executor-experimental/src/middlewares.rs @@ -1,15 +1,17 @@ mod we_breakpoints; mod we_metering; -mod we_metering_helpers; mod we_opcode_control; +mod we_opcode_cost; +mod we_opcode_cost_type; mod we_opcode_trace; mod we_protected_globals; mod we_protected_globals_trait; pub(crate) use we_breakpoints::*; pub(crate) use we_metering::*; -pub use we_metering_helpers::*; pub use we_opcode_control::OpcodeControl; +pub use we_opcode_cost::*; +pub use we_opcode_cost_type::Cost; pub use we_opcode_trace::OpcodeTracer; pub use we_protected_globals::ProtectedGlobals; pub use we_protected_globals_trait::MiddlewareWithProtectedGlobals; diff --git a/vm-executor-experimental/src/middlewares/we_breakpoints.rs b/vm-executor-experimental/src/middlewares/we_breakpoints.rs index aacecd0..70a279c 100644 --- a/vm-executor-experimental/src/middlewares/we_breakpoints.rs +++ b/vm-executor-experimental/src/middlewares/we_breakpoints.rs @@ -6,7 +6,7 @@ use wasmer::wasmparser::Operator; use wasmer::{AsStoreMut, Instance, LocalFunctionIndex}; use wasmer_types::{GlobalIndex, MiddlewareError, ModuleInfo}; -use crate::we_helpers::{create_global_index, get_global_value_u64, is_control_flow_operator}; +use crate::we_helpers::{create_i64_global_index, get_global_value_u64, is_control_flow_operator}; const BREAKPOINT_VALUE: &str = "breakpoint_value"; @@ -77,7 +77,7 @@ impl ModuleMiddleware for Breakpoints { let mut global_index = self.global_index.lock().unwrap(); *global_index = Some(BreakpointsGlobalIndex { - breakpoint_value_global_index: create_global_index( + breakpoint_value_global_index: create_i64_global_index( module_info, BREAKPOINT_VALUE, BREAKPOINT_VALUE_NO_BREAKPOINT as i64, diff --git a/vm-executor-experimental/src/middlewares/we_metering.rs b/vm-executor-experimental/src/middlewares/we_metering.rs index 57a497f..e6196cd 100644 --- a/vm-executor-experimental/src/middlewares/we_metering.rs +++ b/vm-executor-experimental/src/middlewares/we_metering.rs @@ -1,13 +1,13 @@ #![allow(unused)] // TODO: until we activate the local count mechanism use super::{ - BREAKPOINT_VALUE_OUT_OF_GAS, Breakpoints, MiddlewareWithProtectedGlobals, get_local_cost, - get_opcode_cost, + BREAKPOINT_VALUE_OUT_OF_GAS, Breakpoints, Cost, MiddlewareWithProtectedGlobals, get_opcode_cost, }; use crate::we_helpers::{ - create_global_index, get_global_value_u64, is_control_flow_operator, set_global_value_u64, + create_i32_global_index, create_i64_global_index, get_global_value_u64, + is_control_flow_operator, set_global_value_u64, }; -use multiversx_chain_vm_executor::{ExecutorError, OpcodeCost}; +use multiversx_chain_vm_executor::{ExecutorError, OpcodeConfig, OpcodeCost}; use std::mem; use std::sync::{Arc, Mutex}; use wasmer::sys::{FunctionMiddleware, MiddlewareReaderState, ModuleMiddleware}; @@ -17,6 +17,7 @@ use wasmer_types::{GlobalIndex, MiddlewareError, ModuleInfo}; const METERING_POINTS_LIMIT: &str = "metering_points_limit"; const METERING_POINTS_USED: &str = "metering_points_used"; +const METERING_BULK_MEMORY_SIZE_OPERAND_BACKUP: &str = "metering_bulk_memory_size_operand_backup"; const MAX_LOCAL_COUNT: u32 = 4000; const POINTS_LIMIT_INIT: i64 = 0; @@ -24,12 +25,13 @@ const POINTS_LIMIT_INIT: i64 = 0; struct MeteringGlobalIndexes { points_limit_global_index: GlobalIndex, points_used_global_index: GlobalIndex, + bulk_memory_size_operand_backup_global_index: GlobalIndex, } #[derive(Debug)] pub(crate) struct Metering { unmetered_locals: usize, - opcode_cost: Arc, + opcode_config: Arc, breakpoints_middleware: Arc, global_indexes: Mutex>, } @@ -37,12 +39,12 @@ pub(crate) struct Metering { impl Metering { pub(crate) fn new( unmetered_locals: usize, - opcode_cost: Arc, + opcode_config: Arc, breakpoints_middleware: Arc, ) -> Self { Self { unmetered_locals, - opcode_cost, + opcode_config, breakpoints_middleware, global_indexes: Mutex::new(None), } @@ -65,6 +67,15 @@ impl Metering { .unwrap() .points_used_global_index } + + pub fn get_bulk_memory_size_operand_backup_global_index(&self) -> GlobalIndex { + self.global_indexes + .lock() + .unwrap() + .as_ref() + .unwrap() + .bulk_memory_size_operand_backup_global_index + } } unsafe impl Send for Metering {} @@ -78,7 +89,7 @@ impl ModuleMiddleware for Metering { Box::new(FunctionMetering { accumulated_cost: Default::default(), unmetered_locals: self.unmetered_locals, - opcode_cost: self.opcode_cost.clone(), + opcode_config: self.opcode_config.clone(), breakpoints_middleware: self.breakpoints_middleware.clone(), global_indexes: self.global_indexes.lock().unwrap().clone().unwrap(), }) @@ -88,12 +99,17 @@ impl ModuleMiddleware for Metering { let mut global_indexes = self.global_indexes.lock().unwrap(); *global_indexes = Some(MeteringGlobalIndexes { - points_limit_global_index: create_global_index( + points_limit_global_index: create_i64_global_index( module_info, METERING_POINTS_LIMIT, POINTS_LIMIT_INIT, ), - points_used_global_index: create_global_index(module_info, METERING_POINTS_USED, 0), + points_used_global_index: create_i64_global_index(module_info, METERING_POINTS_USED, 0), + bulk_memory_size_operand_backup_global_index: create_i32_global_index( + module_info, + METERING_BULK_MEMORY_SIZE_OPERAND_BACKUP, + 0, + ), }); Ok(()) @@ -104,7 +120,7 @@ impl ModuleMiddleware for Metering { struct FunctionMetering { accumulated_cost: u64, unmetered_locals: usize, - opcode_cost: Arc, + opcode_config: Arc, breakpoints_middleware: Arc, global_indexes: MeteringGlobalIndexes, } @@ -138,6 +154,72 @@ impl FunctionMetering { self.breakpoints_middleware .inject_breakpoint_condition(state, BREAKPOINT_VALUE_OUT_OF_GAS); } + + /// Injects `points_used += size * cost_per_byte` ahead of a `memory.copy`/`memory.fill`, + /// using `size` as it appears on the stack (the raw, attacker-controlled operand), before + /// the real bulk-memory instruction validates it against the actual memory bounds. + /// + /// This only covers the cost of the bytes. The flat cost of the instruction itself goes + /// through the regular accumulator, so that a zero-size copy or fill is never free. + /// + /// Both operators take `size` as their last operand - `memory.copy` is `[dst, src, size]`, + /// `memory.fill` is `[dst, value, size]` - so the same injection works for both. + /// + /// `size` is an `i32` and so is the global it is parked in, so it makes the round trip + /// untouched. Only the multiplication is widened to `i64`, to leave room for the product. + /// + /// The multiplication is plain wrapping `i64` arithmetic (wasm has no trapping or + /// saturating integer multiply, and Wasmer doesn't offer one either), so it can in theory + /// wrap around for a large enough `size`. This is not exploitable under the current setup: + /// - `MAX_MEMORY_PAGES_ALLOWED` (in `we_instance.rs`) caps declared memory at 20 pages + /// (1.25 MiB), enforced at instantiation. + /// - any `size` large enough to matter for overflow (`size * cost_per_byte` approaching + /// `i64::MAX`) necessarily exceeds that 1.25 MiB bound, so the real `memory.copy`/ + /// `memory.fill` that follows this injected code traps on out-of-bounds access and aborts + /// the call immediately — regardless of what `points_used` ended up holding. + /// - a `size` that keeps the copy in-bounds is capped at 1,310,720 bytes, so + /// `size * cost_per_byte` tops out around `5.6e15` for a `u32` cost, far below `i64::MAX`. + fn inject_bulk_memory_cost(&self, state: &mut MiddlewareReaderState, cost_per_byte: u32) { + // backup the bulk memory size + state.extend(&[Operator::GlobalSet { + global_index: self + .global_indexes + .bulk_memory_size_operand_backup_global_index + .as_u32(), + }]); + + // inject bulk memory cost + state.extend(&[ + // size * cost_per_byte + Operator::GlobalGet { + global_index: self + .global_indexes + .bulk_memory_size_operand_backup_global_index + .as_u32(), + }, + Operator::I64ExtendI32U, + Operator::I64Const { + value: cost_per_byte as i64, + }, + Operator::I64Mul, + // points_used += size * cost_per_byte + Operator::GlobalGet { + global_index: self.global_indexes.points_used_global_index.as_u32(), + }, + Operator::I64Add, + Operator::GlobalSet { + global_index: self.global_indexes.points_used_global_index.as_u32(), + }, + ]); + + // bring back the bulk memory size + state.extend(&[Operator::GlobalGet { + global_index: self + .global_indexes + .bulk_memory_size_operand_backup_global_index + .as_u32(), + }]); + } } impl FunctionMiddleware for FunctionMetering { @@ -149,15 +231,28 @@ impl FunctionMiddleware for FunctionMetering { // Get the cost of the current operator, and add it to the accumulator. // This needs to be done before the metering logic, to prevent operators like `Call` from escaping metering in some // corner cases. - let option = get_opcode_cost(&operator, &self.opcode_cost); - match option { - Some(cost) => self.accumulated_cost += cost as u64, - None => { + let cost = get_opcode_cost(&operator, &self.opcode_config); + match cost { + Cost::Illegal => { return Err(MiddlewareError::new( "metering_middleware", format!("Unsupported operator: {operator:?}"), )); } + Cost::Base(base) => self.accumulated_cost += base as u64, + Cost::BulkMemory { base, per_byte } => { + self.accumulated_cost += base as u64; + + // flush what accumulated so far, including the base cost of this operator, + // so that the out of gas check below takes all of it into account + self.inject_points_used_increment(state); + self.accumulated_cost = 0; + + self.inject_bulk_memory_cost(state, per_byte); + + // immediately insert out of gas check as this operation might be expensive + self.inject_out_of_gas_check(state); + } } if is_control_flow_operator(&operator) { @@ -182,7 +277,7 @@ impl FunctionMiddleware for FunctionMetering { // let unmetered_locals = self.unmetered_locals as u32; // if count > unmetered_locals { // let metered_locals = count - unmetered_locals; - // let local_cost = get_local_cost(&self.opcode_cost.lock().unwrap()); + // let local_cost = self.opcode_cost.lock().unwrap().opcode_localallocate; // let metered_locals_cost = metered_locals * local_cost; // self.accumulated_cost += metered_locals_cost as u64; // } diff --git a/vm-executor-experimental/src/middlewares/we_metering_helpers.rs b/vm-executor-experimental/src/middlewares/we_metering_helpers.rs deleted file mode 100644 index f2e895f..0000000 --- a/vm-executor-experimental/src/middlewares/we_metering_helpers.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Code generated by vmhooks generator. DO NOT EDIT. - -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// !!!!!!!!!!!!!!!!!!!!!! AUTO-GENERATED FILE !!!!!!!!!!!!!!!!!!!!!! -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -use multiversx_chain_vm_executor::OpcodeCost; -use wasmer::wasmparser::Operator; - -pub fn get_local_cost(opcode_cost: &OpcodeCost) -> u32 { - opcode_cost.opcode_localallocate -} - -pub fn get_opcode_cost(op: &Operator, opcode_cost: &OpcodeCost) -> Option { - match op { - Operator::Block { .. } => Some(opcode_cost.opcode_block), - Operator::Br { .. } => Some(opcode_cost.opcode_br), - Operator::BrIf { .. } => Some(opcode_cost.opcode_brif), - Operator::BrTable { .. } => Some(opcode_cost.opcode_brtable), - Operator::Call { .. } => Some(opcode_cost.opcode_call), - Operator::CallIndirect { .. } => Some(opcode_cost.opcode_callindirect), - Operator::Catch { .. } => Some(opcode_cost.opcode_catch), - Operator::CatchAll { .. } => Some(opcode_cost.opcode_catchall), - Operator::Delegate { .. } => Some(opcode_cost.opcode_delegate), - Operator::Drop { .. } => Some(opcode_cost.opcode_drop), - Operator::Else { .. } => Some(opcode_cost.opcode_else), - Operator::End { .. } => Some(opcode_cost.opcode_end), - Operator::GlobalGet { .. } => Some(opcode_cost.opcode_globalget), - Operator::GlobalSet { .. } => Some(opcode_cost.opcode_globalset), - Operator::I32Add { .. } => Some(opcode_cost.opcode_i32add), - Operator::I32And { .. } => Some(opcode_cost.opcode_i32and), - Operator::I32Clz { .. } => Some(opcode_cost.opcode_i32clz), - Operator::I32Const { .. } => Some(opcode_cost.opcode_i32const), - Operator::I32Ctz { .. } => Some(opcode_cost.opcode_i32ctz), - Operator::I32DivS { .. } => Some(opcode_cost.opcode_i32divs), - Operator::I32DivU { .. } => Some(opcode_cost.opcode_i32divu), - Operator::I32Eq { .. } => Some(opcode_cost.opcode_i32eq), - Operator::I32Eqz { .. } => Some(opcode_cost.opcode_i32eqz), - Operator::I32Extend16S { .. } => Some(opcode_cost.opcode_i32extend16s), - Operator::I32Extend8S { .. } => Some(opcode_cost.opcode_i32extend8s), - Operator::I32GeS { .. } => Some(opcode_cost.opcode_i32ges), - Operator::I32GeU { .. } => Some(opcode_cost.opcode_i32geu), - Operator::I32GtS { .. } => Some(opcode_cost.opcode_i32gts), - Operator::I32GtU { .. } => Some(opcode_cost.opcode_i32gtu), - Operator::I32LeS { .. } => Some(opcode_cost.opcode_i32les), - Operator::I32LeU { .. } => Some(opcode_cost.opcode_i32leu), - Operator::I32Load { .. } => Some(opcode_cost.opcode_i32load), - Operator::I32Load16S { .. } => Some(opcode_cost.opcode_i32load16s), - Operator::I32Load16U { .. } => Some(opcode_cost.opcode_i32load16u), - Operator::I32Load8S { .. } => Some(opcode_cost.opcode_i32load8s), - Operator::I32Load8U { .. } => Some(opcode_cost.opcode_i32load8u), - Operator::I32LtS { .. } => Some(opcode_cost.opcode_i32lts), - Operator::I32LtU { .. } => Some(opcode_cost.opcode_i32ltu), - Operator::I32Mul { .. } => Some(opcode_cost.opcode_i32mul), - Operator::I32Ne { .. } => Some(opcode_cost.opcode_i32ne), - Operator::I32Or { .. } => Some(opcode_cost.opcode_i32or), - Operator::I32Popcnt { .. } => Some(opcode_cost.opcode_i32popcnt), - Operator::I32RemS { .. } => Some(opcode_cost.opcode_i32rems), - Operator::I32RemU { .. } => Some(opcode_cost.opcode_i32remu), - Operator::I32Rotl { .. } => Some(opcode_cost.opcode_i32rotl), - Operator::I32Rotr { .. } => Some(opcode_cost.opcode_i32rotr), - Operator::I32Shl { .. } => Some(opcode_cost.opcode_i32shl), - Operator::I32ShrS { .. } => Some(opcode_cost.opcode_i32shrs), - Operator::I32ShrU { .. } => Some(opcode_cost.opcode_i32shru), - Operator::I32Store { .. } => Some(opcode_cost.opcode_i32store), - Operator::I32Store16 { .. } => Some(opcode_cost.opcode_i32store16), - Operator::I32Store8 { .. } => Some(opcode_cost.opcode_i32store8), - Operator::I32Sub { .. } => Some(opcode_cost.opcode_i32sub), - Operator::I32WrapI64 { .. } => Some(opcode_cost.opcode_i32wrapi64), - Operator::I32Xor { .. } => Some(opcode_cost.opcode_i32xor), - Operator::I64Add { .. } => Some(opcode_cost.opcode_i64add), - Operator::I64And { .. } => Some(opcode_cost.opcode_i64and), - Operator::I64Clz { .. } => Some(opcode_cost.opcode_i64clz), - Operator::I64Const { .. } => Some(opcode_cost.opcode_i64const), - Operator::I64Ctz { .. } => Some(opcode_cost.opcode_i64ctz), - Operator::I64DivS { .. } => Some(opcode_cost.opcode_i64divs), - Operator::I64DivU { .. } => Some(opcode_cost.opcode_i64divu), - Operator::I64Eq { .. } => Some(opcode_cost.opcode_i64eq), - Operator::I64Eqz { .. } => Some(opcode_cost.opcode_i64eqz), - Operator::I64Extend16S { .. } => Some(opcode_cost.opcode_i64extend16s), - Operator::I64Extend32S { .. } => Some(opcode_cost.opcode_i64extend32s), - Operator::I64Extend8S { .. } => Some(opcode_cost.opcode_i64extend8s), - Operator::I64ExtendI32S { .. } => Some(opcode_cost.opcode_i64extendi32s), - Operator::I64ExtendI32U { .. } => Some(opcode_cost.opcode_i64extendi32u), - Operator::I64GeS { .. } => Some(opcode_cost.opcode_i64ges), - Operator::I64GeU { .. } => Some(opcode_cost.opcode_i64geu), - Operator::I64GtS { .. } => Some(opcode_cost.opcode_i64gts), - Operator::I64GtU { .. } => Some(opcode_cost.opcode_i64gtu), - Operator::I64LeS { .. } => Some(opcode_cost.opcode_i64les), - Operator::I64LeU { .. } => Some(opcode_cost.opcode_i64leu), - Operator::I64Load { .. } => Some(opcode_cost.opcode_i64load), - Operator::I64Load16S { .. } => Some(opcode_cost.opcode_i64load16s), - Operator::I64Load16U { .. } => Some(opcode_cost.opcode_i64load16u), - Operator::I64Load32S { .. } => Some(opcode_cost.opcode_i64load32s), - Operator::I64Load32U { .. } => Some(opcode_cost.opcode_i64load32u), - Operator::I64Load8S { .. } => Some(opcode_cost.opcode_i64load8s), - Operator::I64Load8U { .. } => Some(opcode_cost.opcode_i64load8u), - Operator::I64LtS { .. } => Some(opcode_cost.opcode_i64lts), - Operator::I64LtU { .. } => Some(opcode_cost.opcode_i64ltu), - Operator::I64Mul { .. } => Some(opcode_cost.opcode_i64mul), - Operator::I64Ne { .. } => Some(opcode_cost.opcode_i64ne), - Operator::I64Or { .. } => Some(opcode_cost.opcode_i64or), - Operator::I64Popcnt { .. } => Some(opcode_cost.opcode_i64popcnt), - Operator::I64RemS { .. } => Some(opcode_cost.opcode_i64rems), - Operator::I64RemU { .. } => Some(opcode_cost.opcode_i64remu), - Operator::I64Rotl { .. } => Some(opcode_cost.opcode_i64rotl), - Operator::I64Rotr { .. } => Some(opcode_cost.opcode_i64rotr), - Operator::I64Shl { .. } => Some(opcode_cost.opcode_i64shl), - Operator::I64ShrS { .. } => Some(opcode_cost.opcode_i64shrs), - Operator::I64ShrU { .. } => Some(opcode_cost.opcode_i64shru), - Operator::I64Store { .. } => Some(opcode_cost.opcode_i64store), - Operator::I64Store16 { .. } => Some(opcode_cost.opcode_i64store16), - Operator::I64Store32 { .. } => Some(opcode_cost.opcode_i64store32), - Operator::I64Store8 { .. } => Some(opcode_cost.opcode_i64store8), - Operator::I64Sub { .. } => Some(opcode_cost.opcode_i64sub), - Operator::I64Xor { .. } => Some(opcode_cost.opcode_i64xor), - Operator::If { .. } => Some(opcode_cost.opcode_if), - Operator::LocalGet { .. } => Some(opcode_cost.opcode_localget), - Operator::LocalSet { .. } => Some(opcode_cost.opcode_localset), - Operator::LocalTee { .. } => Some(opcode_cost.opcode_localtee), - Operator::Loop { .. } => Some(opcode_cost.opcode_loop), - Operator::MemoryGrow { .. } => Some(opcode_cost.opcode_memorygrow), - Operator::MemorySize { .. } => Some(opcode_cost.opcode_memorysize), - Operator::Nop { .. } => Some(opcode_cost.opcode_nop), - Operator::RefFunc { .. } => Some(opcode_cost.opcode_reffunc), - Operator::RefIsNull { .. } => Some(opcode_cost.opcode_refisnull), - Operator::RefNull { .. } => Some(opcode_cost.opcode_refnull), - Operator::Rethrow { .. } => Some(opcode_cost.opcode_rethrow), - Operator::Return { .. } => Some(opcode_cost.opcode_return), - Operator::ReturnCall { .. } => Some(opcode_cost.opcode_returncall), - Operator::ReturnCallIndirect { .. } => Some(opcode_cost.opcode_returncallindirect), - Operator::Select { .. } => Some(opcode_cost.opcode_select), - Operator::TableGet { .. } => Some(opcode_cost.opcode_tableget), - Operator::TableGrow { .. } => Some(opcode_cost.opcode_tablegrow), - Operator::TableInit { .. } => Some(opcode_cost.opcode_tableinit), - Operator::TableSet { .. } => Some(opcode_cost.opcode_tableset), - Operator::TableSize { .. } => Some(opcode_cost.opcode_tablesize), - Operator::Throw { .. } => Some(opcode_cost.opcode_throw), - Operator::Try { .. } => Some(opcode_cost.opcode_try), - Operator::TypedSelect { .. } => Some(opcode_cost.opcode_typedselect), - Operator::Unreachable { .. } => Some(opcode_cost.opcode_unreachable), - // Operator::Unwind { .. } => Some(opcode_cost.opcode_unwind), - _ => None, - } -} diff --git a/vm-executor-experimental/src/middlewares/we_opcode_control.rs b/vm-executor-experimental/src/middlewares/we_opcode_control.rs index 2624123..b3ce492 100644 --- a/vm-executor-experimental/src/middlewares/we_opcode_control.rs +++ b/vm-executor-experimental/src/middlewares/we_opcode_control.rs @@ -9,7 +9,7 @@ use wasmer_types::{GlobalIndex, MiddlewareError, ModuleInfo}; use crate::{ // we_breakpoints::{Breakpoints, BREAKPOINT_VALUE_MEMORY_LIMIT}, - we_helpers::create_global_index, + we_helpers::create_i64_global_index, // MiddlewareWithProtectedGlobals, }; @@ -92,12 +92,12 @@ impl ModuleMiddleware for OpcodeControl { let mut global_indexes = self.global_indexes.lock().unwrap(); *global_indexes = Some(OpcodeControlGlobalIndexes { - memory_grow_count_global_index: create_global_index( + memory_grow_count_global_index: create_i64_global_index( module_info, OPCODE_CONTROL_MEMORY_GROW_COUNT, 0, ), - operand_backup_global_index: create_global_index( + operand_backup_global_index: create_i64_global_index( module_info, OPCODE_CONTROL_OPERAND_BACKUP, 0, diff --git a/vm-executor-experimental/src/middlewares/we_opcode_cost.rs b/vm-executor-experimental/src/middlewares/we_opcode_cost.rs new file mode 100644 index 0000000..846ab54 --- /dev/null +++ b/vm-executor-experimental/src/middlewares/we_opcode_cost.rs @@ -0,0 +1,270 @@ +// Code generated by vmhooks generator. DO NOT EDIT. + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!! AUTO-GENERATED FILE !!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +use super::Cost; +use multiversx_chain_vm_executor::{OpcodeConfig, OpcodeCost, OpcodeVersion}; +use wasmer::wasmparser::Operator; + +pub fn get_opcode_cost(op: &Operator, opcode_config: &OpcodeConfig) -> Cost { + match opcode_config.opcode_version { + OpcodeVersion::V1 => get_opcode_cost_v1(op, &opcode_config.opcode_cost), + OpcodeVersion::V2 => get_opcode_cost_v2(op, &opcode_config.opcode_cost), + } +} + +#[rustfmt::skip] +fn get_opcode_cost_v1(op: &Operator, opcode_cost: &OpcodeCost) -> Cost { + match op { + Operator::Block { .. } => Cost::Base(opcode_cost.opcode_block), + Operator::Br { .. } => Cost::Base(opcode_cost.opcode_br), + Operator::BrIf { .. } => Cost::Base(opcode_cost.opcode_brif), + Operator::BrTable { .. } => Cost::Base(opcode_cost.opcode_brtable), + Operator::Call { .. } => Cost::Base(opcode_cost.opcode_call), + Operator::CallIndirect { .. } => Cost::Base(opcode_cost.opcode_callindirect), + Operator::Catch { .. } => Cost::Base(opcode_cost.opcode_catch), + Operator::CatchAll { .. } => Cost::Base(opcode_cost.opcode_catchall), + Operator::Delegate { .. } => Cost::Base(opcode_cost.opcode_delegate), + Operator::Drop { .. } => Cost::Base(opcode_cost.opcode_drop), + Operator::Else { .. } => Cost::Base(opcode_cost.opcode_else), + Operator::End { .. } => Cost::Base(opcode_cost.opcode_end), + Operator::GlobalGet { .. } => Cost::Base(opcode_cost.opcode_globalget), + Operator::GlobalSet { .. } => Cost::Base(opcode_cost.opcode_globalset), + Operator::I32Add { .. } => Cost::Base(opcode_cost.opcode_i32add), + Operator::I32And { .. } => Cost::Base(opcode_cost.opcode_i32and), + Operator::I32Clz { .. } => Cost::Base(opcode_cost.opcode_i32clz), + Operator::I32Const { .. } => Cost::Base(opcode_cost.opcode_i32const), + Operator::I32Ctz { .. } => Cost::Base(opcode_cost.opcode_i32ctz), + Operator::I32DivS { .. } => Cost::Base(opcode_cost.opcode_i32divs), + Operator::I32DivU { .. } => Cost::Base(opcode_cost.opcode_i32divu), + Operator::I32Eq { .. } => Cost::Base(opcode_cost.opcode_i32eq), + Operator::I32Eqz { .. } => Cost::Base(opcode_cost.opcode_i32eqz), + Operator::I32Extend16S { .. } => Cost::Base(opcode_cost.opcode_i32extend16s), + Operator::I32Extend8S { .. } => Cost::Base(opcode_cost.opcode_i32extend8s), + Operator::I32GeS { .. } => Cost::Base(opcode_cost.opcode_i32ges), + Operator::I32GeU { .. } => Cost::Base(opcode_cost.opcode_i32geu), + Operator::I32GtS { .. } => Cost::Base(opcode_cost.opcode_i32gts), + Operator::I32GtU { .. } => Cost::Base(opcode_cost.opcode_i32gtu), + Operator::I32LeS { .. } => Cost::Base(opcode_cost.opcode_i32les), + Operator::I32LeU { .. } => Cost::Base(opcode_cost.opcode_i32leu), + Operator::I32Load { .. } => Cost::Base(opcode_cost.opcode_i32load), + Operator::I32Load16S { .. } => Cost::Base(opcode_cost.opcode_i32load16s), + Operator::I32Load16U { .. } => Cost::Base(opcode_cost.opcode_i32load16u), + Operator::I32Load8S { .. } => Cost::Base(opcode_cost.opcode_i32load8s), + Operator::I32Load8U { .. } => Cost::Base(opcode_cost.opcode_i32load8u), + Operator::I32LtS { .. } => Cost::Base(opcode_cost.opcode_i32lts), + Operator::I32LtU { .. } => Cost::Base(opcode_cost.opcode_i32ltu), + Operator::I32Mul { .. } => Cost::Base(opcode_cost.opcode_i32mul), + Operator::I32Ne { .. } => Cost::Base(opcode_cost.opcode_i32ne), + Operator::I32Or { .. } => Cost::Base(opcode_cost.opcode_i32or), + Operator::I32Popcnt { .. } => Cost::Base(opcode_cost.opcode_i32popcnt), + Operator::I32RemS { .. } => Cost::Base(opcode_cost.opcode_i32rems), + Operator::I32RemU { .. } => Cost::Base(opcode_cost.opcode_i32remu), + Operator::I32Rotl { .. } => Cost::Base(opcode_cost.opcode_i32rotl), + Operator::I32Rotr { .. } => Cost::Base(opcode_cost.opcode_i32rotr), + Operator::I32Shl { .. } => Cost::Base(opcode_cost.opcode_i32shl), + Operator::I32ShrS { .. } => Cost::Base(opcode_cost.opcode_i32shrs), + Operator::I32ShrU { .. } => Cost::Base(opcode_cost.opcode_i32shru), + Operator::I32Store { .. } => Cost::Base(opcode_cost.opcode_i32store), + Operator::I32Store16 { .. } => Cost::Base(opcode_cost.opcode_i32store16), + Operator::I32Store8 { .. } => Cost::Base(opcode_cost.opcode_i32store8), + Operator::I32Sub { .. } => Cost::Base(opcode_cost.opcode_i32sub), + Operator::I32WrapI64 { .. } => Cost::Base(opcode_cost.opcode_i32wrapi64), + Operator::I32Xor { .. } => Cost::Base(opcode_cost.opcode_i32xor), + Operator::I64Add { .. } => Cost::Base(opcode_cost.opcode_i64add), + Operator::I64And { .. } => Cost::Base(opcode_cost.opcode_i64and), + Operator::I64Clz { .. } => Cost::Base(opcode_cost.opcode_i64clz), + Operator::I64Const { .. } => Cost::Base(opcode_cost.opcode_i64const), + Operator::I64Ctz { .. } => Cost::Base(opcode_cost.opcode_i64ctz), + Operator::I64DivS { .. } => Cost::Base(opcode_cost.opcode_i64divs), + Operator::I64DivU { .. } => Cost::Base(opcode_cost.opcode_i64divu), + Operator::I64Eq { .. } => Cost::Base(opcode_cost.opcode_i64eq), + Operator::I64Eqz { .. } => Cost::Base(opcode_cost.opcode_i64eqz), + Operator::I64Extend16S { .. } => Cost::Base(opcode_cost.opcode_i64extend16s), + Operator::I64Extend32S { .. } => Cost::Base(opcode_cost.opcode_i64extend32s), + Operator::I64Extend8S { .. } => Cost::Base(opcode_cost.opcode_i64extend8s), + Operator::I64ExtendI32S { .. } => Cost::Base(opcode_cost.opcode_i64extendi32s), + Operator::I64ExtendI32U { .. } => Cost::Base(opcode_cost.opcode_i64extendi32u), + Operator::I64GeS { .. } => Cost::Base(opcode_cost.opcode_i64ges), + Operator::I64GeU { .. } => Cost::Base(opcode_cost.opcode_i64geu), + Operator::I64GtS { .. } => Cost::Base(opcode_cost.opcode_i64gts), + Operator::I64GtU { .. } => Cost::Base(opcode_cost.opcode_i64gtu), + Operator::I64LeS { .. } => Cost::Base(opcode_cost.opcode_i64les), + Operator::I64LeU { .. } => Cost::Base(opcode_cost.opcode_i64leu), + Operator::I64Load { .. } => Cost::Base(opcode_cost.opcode_i64load), + Operator::I64Load16S { .. } => Cost::Base(opcode_cost.opcode_i64load16s), + Operator::I64Load16U { .. } => Cost::Base(opcode_cost.opcode_i64load16u), + Operator::I64Load32S { .. } => Cost::Base(opcode_cost.opcode_i64load32s), + Operator::I64Load32U { .. } => Cost::Base(opcode_cost.opcode_i64load32u), + Operator::I64Load8S { .. } => Cost::Base(opcode_cost.opcode_i64load8s), + Operator::I64Load8U { .. } => Cost::Base(opcode_cost.opcode_i64load8u), + Operator::I64LtS { .. } => Cost::Base(opcode_cost.opcode_i64lts), + Operator::I64LtU { .. } => Cost::Base(opcode_cost.opcode_i64ltu), + Operator::I64Mul { .. } => Cost::Base(opcode_cost.opcode_i64mul), + Operator::I64Ne { .. } => Cost::Base(opcode_cost.opcode_i64ne), + Operator::I64Or { .. } => Cost::Base(opcode_cost.opcode_i64or), + Operator::I64Popcnt { .. } => Cost::Base(opcode_cost.opcode_i64popcnt), + Operator::I64RemS { .. } => Cost::Base(opcode_cost.opcode_i64rems), + Operator::I64RemU { .. } => Cost::Base(opcode_cost.opcode_i64remu), + Operator::I64Rotl { .. } => Cost::Base(opcode_cost.opcode_i64rotl), + Operator::I64Rotr { .. } => Cost::Base(opcode_cost.opcode_i64rotr), + Operator::I64Shl { .. } => Cost::Base(opcode_cost.opcode_i64shl), + Operator::I64ShrS { .. } => Cost::Base(opcode_cost.opcode_i64shrs), + Operator::I64ShrU { .. } => Cost::Base(opcode_cost.opcode_i64shru), + Operator::I64Store { .. } => Cost::Base(opcode_cost.opcode_i64store), + Operator::I64Store16 { .. } => Cost::Base(opcode_cost.opcode_i64store16), + Operator::I64Store32 { .. } => Cost::Base(opcode_cost.opcode_i64store32), + Operator::I64Store8 { .. } => Cost::Base(opcode_cost.opcode_i64store8), + Operator::I64Sub { .. } => Cost::Base(opcode_cost.opcode_i64sub), + Operator::I64Xor { .. } => Cost::Base(opcode_cost.opcode_i64xor), + Operator::If { .. } => Cost::Base(opcode_cost.opcode_if), + Operator::LocalGet { .. } => Cost::Base(opcode_cost.opcode_localget), + Operator::LocalSet { .. } => Cost::Base(opcode_cost.opcode_localset), + Operator::LocalTee { .. } => Cost::Base(opcode_cost.opcode_localtee), + Operator::Loop { .. } => Cost::Base(opcode_cost.opcode_loop), + Operator::MemoryGrow { .. } => Cost::Base(opcode_cost.opcode_memorygrow), + Operator::MemorySize { .. } => Cost::Base(opcode_cost.opcode_memorysize), + Operator::Nop { .. } => Cost::Base(opcode_cost.opcode_nop), + Operator::RefFunc { .. } => Cost::Base(opcode_cost.opcode_reffunc), + Operator::RefIsNull { .. } => Cost::Base(opcode_cost.opcode_refisnull), + Operator::RefNull { .. } => Cost::Base(opcode_cost.opcode_refnull), + Operator::Rethrow { .. } => Cost::Base(opcode_cost.opcode_rethrow), + Operator::Return { .. } => Cost::Base(opcode_cost.opcode_return), + Operator::ReturnCall { .. } => Cost::Base(opcode_cost.opcode_returncall), + Operator::ReturnCallIndirect { .. } => Cost::Base(opcode_cost.opcode_returncallindirect), + Operator::Select { .. } => Cost::Base(opcode_cost.opcode_select), + Operator::TableGet { .. } => Cost::Base(opcode_cost.opcode_tableget), + Operator::TableGrow { .. } => Cost::Base(opcode_cost.opcode_tablegrow), + Operator::TableInit { .. } => Cost::Base(opcode_cost.opcode_tableinit), + Operator::TableSet { .. } => Cost::Base(opcode_cost.opcode_tableset), + Operator::TableSize { .. } => Cost::Base(opcode_cost.opcode_tablesize), + Operator::Throw { .. } => Cost::Base(opcode_cost.opcode_throw), + Operator::Try { .. } => Cost::Base(opcode_cost.opcode_try), + Operator::TypedSelect { .. } => Cost::Base(opcode_cost.opcode_typedselect), + Operator::Unreachable { .. } => Cost::Base(opcode_cost.opcode_unreachable), + // Operator::Unwind { .. } => Cost::Base(opcode_cost.opcode_unwind), // unwind no longer available in Wasmer 6 + _ => Cost::Illegal, + } +} + +#[rustfmt::skip] +fn get_opcode_cost_v2(op: &Operator, opcode_cost: &OpcodeCost) -> Cost { + match op { + Operator::Block { .. } => Cost::Base(opcode_cost.opcode_block), + Operator::Br { .. } => Cost::Base(opcode_cost.opcode_br), + Operator::BrIf { .. } => Cost::Base(opcode_cost.opcode_brif), + Operator::BrTable { .. } => Cost::Base(opcode_cost.opcode_brtable), + Operator::Call { .. } => Cost::Base(opcode_cost.opcode_call), + Operator::CallIndirect { .. } => Cost::Base(opcode_cost.opcode_callindirect), + Operator::Drop { .. } => Cost::Base(opcode_cost.opcode_drop), + Operator::Else { .. } => Cost::Base(opcode_cost.opcode_else), + Operator::End { .. } => Cost::Base(opcode_cost.opcode_end), + Operator::GlobalGet { .. } => Cost::Base(opcode_cost.opcode_globalget), + Operator::GlobalSet { .. } => Cost::Base(opcode_cost.opcode_globalset), + Operator::I32Add { .. } => Cost::Base(opcode_cost.opcode_i32add), + Operator::I32And { .. } => Cost::Base(opcode_cost.opcode_i32and), + Operator::I32Clz { .. } => Cost::Base(opcode_cost.opcode_i32clz), + Operator::I32Const { .. } => Cost::Base(opcode_cost.opcode_i32const), + Operator::I32Ctz { .. } => Cost::Base(opcode_cost.opcode_i32ctz), + Operator::I32DivS { .. } => Cost::Base(opcode_cost.opcode_i32divs), + Operator::I32DivU { .. } => Cost::Base(opcode_cost.opcode_i32divu), + Operator::I32Eq { .. } => Cost::Base(opcode_cost.opcode_i32eq), + Operator::I32Eqz { .. } => Cost::Base(opcode_cost.opcode_i32eqz), + Operator::I32Extend16S { .. } => Cost::Base(opcode_cost.opcode_i32extend16s), + Operator::I32Extend8S { .. } => Cost::Base(opcode_cost.opcode_i32extend8s), + Operator::I32GeS { .. } => Cost::Base(opcode_cost.opcode_i32ges), + Operator::I32GeU { .. } => Cost::Base(opcode_cost.opcode_i32geu), + Operator::I32GtS { .. } => Cost::Base(opcode_cost.opcode_i32gts), + Operator::I32GtU { .. } => Cost::Base(opcode_cost.opcode_i32gtu), + Operator::I32LeS { .. } => Cost::Base(opcode_cost.opcode_i32les), + Operator::I32LeU { .. } => Cost::Base(opcode_cost.opcode_i32leu), + Operator::I32Load { .. } => Cost::Base(opcode_cost.opcode_i32load), + Operator::I32Load16S { .. } => Cost::Base(opcode_cost.opcode_i32load16s), + Operator::I32Load16U { .. } => Cost::Base(opcode_cost.opcode_i32load16u), + Operator::I32Load8S { .. } => Cost::Base(opcode_cost.opcode_i32load8s), + Operator::I32Load8U { .. } => Cost::Base(opcode_cost.opcode_i32load8u), + Operator::I32LtS { .. } => Cost::Base(opcode_cost.opcode_i32lts), + Operator::I32LtU { .. } => Cost::Base(opcode_cost.opcode_i32ltu), + Operator::I32Mul { .. } => Cost::Base(opcode_cost.opcode_i32mul), + Operator::I32Ne { .. } => Cost::Base(opcode_cost.opcode_i32ne), + Operator::I32Or { .. } => Cost::Base(opcode_cost.opcode_i32or), + Operator::I32Popcnt { .. } => Cost::Base(opcode_cost.opcode_i32popcnt), + Operator::I32RemS { .. } => Cost::Base(opcode_cost.opcode_i32rems), + Operator::I32RemU { .. } => Cost::Base(opcode_cost.opcode_i32remu), + Operator::I32Rotl { .. } => Cost::Base(opcode_cost.opcode_i32rotl), + Operator::I32Rotr { .. } => Cost::Base(opcode_cost.opcode_i32rotr), + Operator::I32Shl { .. } => Cost::Base(opcode_cost.opcode_i32shl), + Operator::I32ShrS { .. } => Cost::Base(opcode_cost.opcode_i32shrs), + Operator::I32ShrU { .. } => Cost::Base(opcode_cost.opcode_i32shru), + Operator::I32Store { .. } => Cost::Base(opcode_cost.opcode_i32store), + Operator::I32Store16 { .. } => Cost::Base(opcode_cost.opcode_i32store16), + Operator::I32Store8 { .. } => Cost::Base(opcode_cost.opcode_i32store8), + Operator::I32Sub { .. } => Cost::Base(opcode_cost.opcode_i32sub), + Operator::I32WrapI64 { .. } => Cost::Base(opcode_cost.opcode_i32wrapi64), + Operator::I32Xor { .. } => Cost::Base(opcode_cost.opcode_i32xor), + Operator::I64Add { .. } => Cost::Base(opcode_cost.opcode_i64add), + Operator::I64And { .. } => Cost::Base(opcode_cost.opcode_i64and), + Operator::I64Clz { .. } => Cost::Base(opcode_cost.opcode_i64clz), + Operator::I64Const { .. } => Cost::Base(opcode_cost.opcode_i64const), + Operator::I64Ctz { .. } => Cost::Base(opcode_cost.opcode_i64ctz), + Operator::I64DivS { .. } => Cost::Base(opcode_cost.opcode_i64divs), + Operator::I64DivU { .. } => Cost::Base(opcode_cost.opcode_i64divu), + Operator::I64Eq { .. } => Cost::Base(opcode_cost.opcode_i64eq), + Operator::I64Eqz { .. } => Cost::Base(opcode_cost.opcode_i64eqz), + Operator::I64Extend16S { .. } => Cost::Base(opcode_cost.opcode_i64extend16s), + Operator::I64Extend32S { .. } => Cost::Base(opcode_cost.opcode_i64extend32s), + Operator::I64Extend8S { .. } => Cost::Base(opcode_cost.opcode_i64extend8s), + Operator::I64ExtendI32S { .. } => Cost::Base(opcode_cost.opcode_i64extendi32s), + Operator::I64ExtendI32U { .. } => Cost::Base(opcode_cost.opcode_i64extendi32u), + Operator::I64GeS { .. } => Cost::Base(opcode_cost.opcode_i64ges), + Operator::I64GeU { .. } => Cost::Base(opcode_cost.opcode_i64geu), + Operator::I64GtS { .. } => Cost::Base(opcode_cost.opcode_i64gts), + Operator::I64GtU { .. } => Cost::Base(opcode_cost.opcode_i64gtu), + Operator::I64LeS { .. } => Cost::Base(opcode_cost.opcode_i64les), + Operator::I64LeU { .. } => Cost::Base(opcode_cost.opcode_i64leu), + Operator::I64Load { .. } => Cost::Base(opcode_cost.opcode_i64load), + Operator::I64Load16S { .. } => Cost::Base(opcode_cost.opcode_i64load16s), + Operator::I64Load16U { .. } => Cost::Base(opcode_cost.opcode_i64load16u), + Operator::I64Load32S { .. } => Cost::Base(opcode_cost.opcode_i64load32s), + Operator::I64Load32U { .. } => Cost::Base(opcode_cost.opcode_i64load32u), + Operator::I64Load8S { .. } => Cost::Base(opcode_cost.opcode_i64load8s), + Operator::I64Load8U { .. } => Cost::Base(opcode_cost.opcode_i64load8u), + Operator::I64LtS { .. } => Cost::Base(opcode_cost.opcode_i64lts), + Operator::I64LtU { .. } => Cost::Base(opcode_cost.opcode_i64ltu), + Operator::I64Mul { .. } => Cost::Base(opcode_cost.opcode_i64mul), + Operator::I64Ne { .. } => Cost::Base(opcode_cost.opcode_i64ne), + Operator::I64Or { .. } => Cost::Base(opcode_cost.opcode_i64or), + Operator::I64Popcnt { .. } => Cost::Base(opcode_cost.opcode_i64popcnt), + Operator::I64RemS { .. } => Cost::Base(opcode_cost.opcode_i64rems), + Operator::I64RemU { .. } => Cost::Base(opcode_cost.opcode_i64remu), + Operator::I64Rotl { .. } => Cost::Base(opcode_cost.opcode_i64rotl), + Operator::I64Rotr { .. } => Cost::Base(opcode_cost.opcode_i64rotr), + Operator::I64Shl { .. } => Cost::Base(opcode_cost.opcode_i64shl), + Operator::I64ShrS { .. } => Cost::Base(opcode_cost.opcode_i64shrs), + Operator::I64ShrU { .. } => Cost::Base(opcode_cost.opcode_i64shru), + Operator::I64Store { .. } => Cost::Base(opcode_cost.opcode_i64store), + Operator::I64Store16 { .. } => Cost::Base(opcode_cost.opcode_i64store16), + Operator::I64Store32 { .. } => Cost::Base(opcode_cost.opcode_i64store32), + Operator::I64Store8 { .. } => Cost::Base(opcode_cost.opcode_i64store8), + Operator::I64Sub { .. } => Cost::Base(opcode_cost.opcode_i64sub), + Operator::I64Xor { .. } => Cost::Base(opcode_cost.opcode_i64xor), + Operator::If { .. } => Cost::Base(opcode_cost.opcode_if), + Operator::LocalGet { .. } => Cost::Base(opcode_cost.opcode_localget), + Operator::LocalSet { .. } => Cost::Base(opcode_cost.opcode_localset), + Operator::LocalTee { .. } => Cost::Base(opcode_cost.opcode_localtee), + Operator::Loop { .. } => Cost::Base(opcode_cost.opcode_loop), + Operator::MemoryGrow { .. } => Cost::Base(opcode_cost.opcode_memorygrow), + Operator::MemorySize { .. } => Cost::Base(opcode_cost.opcode_memorysize), + Operator::Nop { .. } => Cost::Base(opcode_cost.opcode_nop), + Operator::Return { .. } => Cost::Base(opcode_cost.opcode_return), + Operator::ReturnCall { .. } => Cost::Base(opcode_cost.opcode_returncall), + Operator::ReturnCallIndirect { .. } => Cost::Base(opcode_cost.opcode_returncallindirect), + Operator::Select { .. } => Cost::Base(opcode_cost.opcode_select), + Operator::Unreachable { .. } => Cost::Base(opcode_cost.opcode_unreachable), + Operator::MemoryCopy { .. } => Cost::BulkMemory { base: opcode_cost.opcode_memorycopy, per_byte: opcode_cost.opcode_memorycopyperbyte }, + Operator::MemoryFill { .. } => Cost::BulkMemory { base: opcode_cost.opcode_memoryfill, per_byte: opcode_cost.opcode_memoryfillperbyte }, + _ => Cost::Illegal, + } +} diff --git a/vm-executor-experimental/src/middlewares/we_opcode_cost_type.rs b/vm-executor-experimental/src/middlewares/we_opcode_cost_type.rs new file mode 100644 index 0000000..124c276 --- /dev/null +++ b/vm-executor-experimental/src/middlewares/we_opcode_cost_type.rs @@ -0,0 +1,20 @@ +/// What executing one WASM operator costs. +/// +/// Not to be confused with `OpcodeCost`, which is the full cost table, as configured by the +/// gas schedule. This is the cost of one occurrence of one operator. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Cost { + /// The operator is not allowed by the configured opcode version, so it has no cost. + /// + /// Modules that contain it are rejected at compile time. + Illegal, + + /// The operator is charged once, no matter what its operands are. + Base(u32), + + /// A bulk memory operator (`memory.copy`, `memory.fill`), charged once for the operation + /// itself, plus once for every byte it copies or fills. + /// + /// The base cost is what keeps processing zero bytes from being free. + BulkMemory { base: u32, per_byte: u32 }, +} diff --git a/vm-executor-experimental/src/middlewares/we_protected_globals_trait.rs b/vm-executor-experimental/src/middlewares/we_protected_globals_trait.rs index 482cc93..22ac551 100644 --- a/vm-executor-experimental/src/middlewares/we_protected_globals_trait.rs +++ b/vm-executor-experimental/src/middlewares/we_protected_globals_trait.rs @@ -26,6 +26,8 @@ impl MiddlewareWithProtectedGlobals for Metering { vec![ self.get_points_limit_global_index().as_u32(), self.get_points_used_global_index().as_u32(), + self.get_bulk_memory_size_operand_backup_global_index() + .as_u32(), ] } } diff --git a/vm-executor-experimental/src/we_executor.rs b/vm-executor-experimental/src/we_executor.rs index 2489795..dbec4db 100644 --- a/vm-executor-experimental/src/we_executor.rs +++ b/vm-executor-experimental/src/we_executor.rs @@ -1,13 +1,13 @@ use crate::{ExperimentalInstance, ExperimentalVMHooksBuilder}; use multiversx_chain_vm_executor::{ - CompilationOptions, Executor, ExecutorError, Instance, OpcodeCost, check_missing_wasm, + CompilationOptions, Executor, ExecutorError, Instance, OpcodeConfig, check_missing_wasm, }; use std::{fmt, sync::Arc}; pub trait ExperimentalExecutorRuntimeRef: Send + Sync { fn vm_hooks_builder(&self) -> Box; - fn opcode_cost(&self) -> Arc; + fn opcode_config(&self) -> Arc; } /// Executor implementation that produces wasmer instances with correctly injected VM hooks from runtime. @@ -34,7 +34,7 @@ impl ExperimentalExecutor { Box::new( ExperimentalInstance::try_new_instance( self.runtime_ref.vm_hooks_builder(), - self.runtime_ref.opcode_cost(), + self.runtime_ref.opcode_config(), wasm_bytes, compilation_options, ) diff --git a/vm-executor-experimental/src/we_helpers.rs b/vm-executor-experimental/src/we_helpers.rs index be4345b..5ba36c5 100644 --- a/vm-executor-experimental/src/we_helpers.rs +++ b/vm-executor-experimental/src/we_helpers.rs @@ -7,18 +7,37 @@ use wasmer_types::{GlobalIndex, ModuleInfo}; use crate::ExperimentalError; -pub(crate) fn create_global_index( +/// Creates and exports a fresh mutable i32 global in the module and initializes it. +pub(crate) fn create_i32_global_index( + module_info: &mut ModuleInfo, + key: &str, + init: i32, +) -> GlobalIndex { + create_global_index(module_info, key, Type::I32, GlobalInit::I32Const(init)) +} + +/// Creates and exports a fresh mutable i64 global in the module and initializes it. +pub(crate) fn create_i64_global_index( module_info: &mut ModuleInfo, key: &str, init: i64, +) -> GlobalIndex { + create_global_index(module_info, key, Type::I64, GlobalInit::I64Const(init)) +} + +/// Only reachable through the typed constructors above, which are what keeps the declared +/// type and the initializer from disagreeing. +fn create_global_index( + module_info: &mut ModuleInfo, + key: &str, + global_type: Type, + init: GlobalInit, ) -> GlobalIndex { let global_index = module_info .globals - .push(GlobalType::new(Type::I64, Mutability::Var)); + .push(GlobalType::new(global_type, Mutability::Var)); - module_info - .global_initializers - .push(GlobalInit::I64Const(init)); + module_info.global_initializers.push(init); module_info .exports diff --git a/vm-executor-experimental/src/we_instance.rs b/vm-executor-experimental/src/we_instance.rs index d60ba43..00a4b38 100644 --- a/vm-executor-experimental/src/we_instance.rs +++ b/vm-executor-experimental/src/we_instance.rs @@ -6,7 +6,7 @@ use crate::{ExperimentalError, ExperimentalVMHooksBuilder}; use crate::{we_imports::generate_import_object, we_vm_hooks::VMHooksWrapper}; use log::trace; use multiversx_chain_vm_executor::{ - BreakpointValue, CompilationOptions, ExecutorError, Instance, InstanceCallResult, OpcodeCost, + BreakpointValue, CompilationOptions, ExecutorError, Instance, InstanceCallResult, OpcodeConfig, ServiceError, VMHooksEarlyExit, }; use rc_new_cyclic_fallible::rc_new_cyclic_fallible; @@ -74,7 +74,7 @@ fn prepare_wasmer_instance_inner( impl ExperimentalInstance { pub fn try_new_instance( vm_hooks_builder: Box, - opcode_cost: Arc, + opcode_config: Arc, wasm_bytes: &[u8], compilation_options: &CompilationOptions, ) -> Result { @@ -82,7 +82,7 @@ impl ExperimentalInstance { let mut compiler = Singlepass::default(); // Push middlewares - push_middlewares(&mut compiler, compilation_options, opcode_cost); + push_middlewares(&mut compiler, compilation_options, opcode_config); // Create the store let mut store: Store = Store::new(compiler); @@ -151,7 +151,7 @@ fn validate_memory(memory: &wasmer::Memory, store: &wasmer::Store) -> Result<(), fn push_middlewares( compiler: &mut Singlepass, compilation_options: &CompilationOptions, - opcode_cost: Arc, + opcode_config: Arc, ) { // Create breakpoints middleware let breakpoints_middleware = Arc::new(Breakpoints::new()); @@ -167,7 +167,7 @@ fn push_middlewares( // Create metering middleware let metering_middleware = Arc::new(Metering::new( compilation_options.unmetered_locals, - opcode_cost, + opcode_config, breakpoints_middleware.clone(), )); diff --git a/vm-executor-experimental/tests/bulk-memory.wat b/vm-executor-experimental/tests/bulk-memory.wat new file mode 100644 index 0000000..f5997d2 --- /dev/null +++ b/vm-executor-experimental/tests/bulk-memory.wat @@ -0,0 +1,48 @@ +(module + (memory $mem 1) + (func $copyZero (type $void) + (memory.copy + (i32.const 0) ;; destination address + (i32.const 64) ;; source address + (i32.const 0) ;; size of memory region in bytes + ) + ) + (func $copyTen (type $void) + (memory.copy + (i32.const 0) ;; destination address + (i32.const 64) ;; source address + (i32.const 10) ;; size of memory region in bytes + ) + ) + (func $fillZero (type $void) + (memory.fill + (i32.const 0) ;; destination address + (i32.const 7) ;; value to fill with + (i32.const 0) ;; size of memory region in bytes + ) + ) + (func $fillTen (type $void) + (memory.fill + (i32.const 0) ;; destination address + (i32.const 7) ;; value to fill with + (i32.const 10) ;; size of memory region in bytes + ) + ) + ;; same as $copyTen, but with the size coming from a local instead of an + ;; immediate constant, to check that the metering injection gives it back unchanged + (func $copyLocalTen (type $void) (local $size i32) + (local.set $size (i32.const 10)) + (memory.copy + (i32.const 0) ;; destination address + (i32.const 64) ;; source address + (local.get $size) ;; size of memory region in bytes + ) + ) + (type $void (func)) + (export "memory" (memory $mem)) + (export "copyZero" (func $copyZero)) + (export "copyTen" (func $copyTen)) + (export "fillZero" (func $fillZero)) + (export "fillTen" (func $fillTen)) + (export "copyLocalTen" (func $copyLocalTen)) +) diff --git a/vm-executor-experimental/tests/bulk_memory_metering_test.rs b/vm-executor-experimental/tests/bulk_memory_metering_test.rs new file mode 100644 index 0000000..b163c21 --- /dev/null +++ b/vm-executor-experimental/tests/bulk_memory_metering_test.rs @@ -0,0 +1,83 @@ +use std::sync::Arc; + +use multiversx_chain_vm_executor::{ + CompilationOptions, Instance, InstanceCallResult, OpcodeConfig, OpcodeCost, OpcodeVersion, +}; +use multiversx_chain_vm_executor_wasmer_experimental::{ + ExperimentalInstance, ExperimentalVMHooksBuilderDefault, +}; +use wasmer::wat2wasm; + +const BULK_MEMORY_WAT: &[u8] = include_bytes!("bulk-memory.wat"); + +const POINTS_LIMIT: u64 = 1_000_000; +const MEMORY_COPY_COST: u32 = 300; +const MEMORY_COPY_PER_BYTE_COST: u32 = 7; +const MEMORY_FILL_COST: u32 = 400; +const MEMORY_FILL_PER_BYTE_COST: u32 = 11; + +const COMPILATION_OPTIONS: CompilationOptions = CompilationOptions { + unmetered_locals: 0, + max_memory_grow: 0, + max_memory_grow_delta: 0, + opcode_trace: false, +}; + +/// All opcodes are free, except the bulk memory ones, so that `points_used` after a call +/// is exactly the cost of the single `memory.copy`/`memory.fill` in the called function. +/// +/// A fresh instance for every call, since the `Instance` trait offers no way to reset +/// `points_used` back to zero. +fn points_used_by(func_name: &str) -> u64 { + let wasm_bytes = wat2wasm(BULK_MEMORY_WAT).unwrap(); + + let opcode_config = OpcodeConfig { + opcode_version: OpcodeVersion::V2, + opcode_cost: OpcodeCost { + opcode_memorycopy: MEMORY_COPY_COST, + opcode_memorycopyperbyte: MEMORY_COPY_PER_BYTE_COST, + opcode_memoryfill: MEMORY_FILL_COST, + opcode_memoryfillperbyte: MEMORY_FILL_PER_BYTE_COST, + ..Default::default() + }, + }; + + let mut instance = ExperimentalInstance::try_new_instance( + Box::new(ExperimentalVMHooksBuilderDefault), + Arc::new(opcode_config), + &wasm_bytes, + &COMPILATION_OPTIONS, + ) + .unwrap(); + + assert!(matches!( + instance.call(func_name, POINTS_LIMIT), + InstanceCallResult::Ok + )); + + instance.get_points_used().unwrap() +} + +#[test] +fn bulk_memory_zero_bytes_still_costs_the_base() { + assert_eq!(points_used_by("copyZero"), MEMORY_COPY_COST as u64); + assert_eq!(points_used_by("fillZero"), MEMORY_FILL_COST as u64); +} + +#[test] +fn bulk_memory_bytes_cost_on_top_of_the_base() { + assert_eq!( + points_used_by("copyTen"), + (MEMORY_COPY_COST + 10 * MEMORY_COPY_PER_BYTE_COST) as u64 + ); + assert_eq!( + points_used_by("fillTen"), + (MEMORY_FILL_COST + 10 * MEMORY_FILL_PER_BYTE_COST) as u64 + ); +} + +/// The injected metering code takes the size operand off the stack and puts it back. +#[test] +fn bulk_memory_size_operand_survives_the_injection() { + assert_eq!(points_used_by("copyLocalTen"), points_used_by("copyTen")); +} diff --git a/vm-executor-experimental/tests/common/test_instance.rs b/vm-executor-experimental/tests/common/test_instance.rs index b85d306..559c2fa 100644 --- a/vm-executor-experimental/tests/common/test_instance.rs +++ b/vm-executor-experimental/tests/common/test_instance.rs @@ -1,6 +1,6 @@ use std::sync::Arc; -use multiversx_chain_vm_executor::{CompilationOptions, Instance, OpcodeCost}; +use multiversx_chain_vm_executor::{CompilationOptions, Instance, OpcodeConfig}; use multiversx_chain_vm_executor_wasmer_experimental::{ ExperimentalInstance, ExperimentalVMHooksBuilderDefault, }; @@ -19,7 +19,7 @@ pub fn test_instance(wat: &str) -> Box { Box::new( ExperimentalInstance::try_new_instance( Box::new(ExperimentalVMHooksBuilderDefault), - Arc::new(OpcodeCost::default()), + Arc::new(OpcodeConfig::default()), &wasm_bytes, &DUMMY_COMPILATION_OPTIONS, ) diff --git a/vm-executor-wasmer/Cargo.toml b/vm-executor-wasmer/Cargo.toml index 4b31119..e54502b 100644 --- a/vm-executor-wasmer/Cargo.toml +++ b/vm-executor-wasmer/Cargo.toml @@ -33,6 +33,6 @@ wasmer = { git = "https://github.com/multiversx/wasmer", rev = "73f2111abf85700d wasmer-vm = { git = "https://github.com/multiversx/wasmer", rev = "73f2111abf85700dec4f11fb23ece01abc2065e3" } wasmer-types = { git = "https://github.com/multiversx/wasmer", rev = "73f2111abf85700dec4f11fb23ece01abc2065e3" } -log = "0.4" +log = { version = "0.4", features = ["std"] } loupe = "0.1.3" thiserror = "2.0" diff --git a/vm-executor-wasmer/src/lib.rs b/vm-executor-wasmer/src/lib.rs index bfd7055..3a8b7cf 100644 --- a/vm-executor-wasmer/src/lib.rs +++ b/vm-executor-wasmer/src/lib.rs @@ -5,8 +5,9 @@ mod wasmer_helpers; mod wasmer_imports; mod wasmer_instance; mod wasmer_metering; -mod wasmer_metering_helpers; mod wasmer_opcode_control; +mod wasmer_opcode_cost; +mod wasmer_opcode_cost_type; mod wasmer_opcode_trace; mod wasmer_protected_globals; mod wasmer_service; @@ -14,7 +15,8 @@ mod wasmer_vm_hooks; pub use wasmer_executor::*; pub use wasmer_instance::*; -pub use wasmer_metering_helpers::*; +pub use wasmer_opcode_cost::*; +pub use wasmer_opcode_cost_type::Cost; pub use wasmer_service::*; pub use multiversx_chain_vm_executor as executor_interface; diff --git a/vm-executor-wasmer/src/new_traits/wasmer_prod_executor.rs b/vm-executor-wasmer/src/new_traits/wasmer_prod_executor.rs index 6984113..052fef0 100644 --- a/vm-executor-wasmer/src/new_traits/wasmer_prod_executor.rs +++ b/vm-executor-wasmer/src/new_traits/wasmer_prod_executor.rs @@ -1,7 +1,8 @@ +use multiversx_chain_vm_executor::OpcodeConfig; + use crate::WasmerInstance; use crate::executor_interface::{ - CompilationOptions, Executor, ExecutorError, Instance, OpcodeCost, VMHooksLegacy, - check_missing_wasm, + CompilationOptions, Executor, ExecutorError, Instance, VMHooksLegacy, check_missing_wasm, }; use std::{ fmt, @@ -14,7 +15,7 @@ use super::{WasmerProdInstance, WasmerProdInstanceState}; pub trait WasmerProdRuntimeRef: Send + Sync { fn vm_hooks(&self, instance_state: WasmerProdInstanceState) -> Box; - fn opcode_cost(&self) -> Arc>; + fn opcode_config(&self) -> Arc>; } /// Executor implementation that produces wasmer instances with correctly injected VM hooks from runtime. @@ -44,7 +45,7 @@ impl WasmerProdExecutor { WasmerInstance::try_new_instance( Rc::from(vm_hooks), - self.runtime_ref.opcode_cost(), + self.runtime_ref.opcode_config(), wasm_bytes, &compilation_options.to_legacy(), ) diff --git a/vm-executor-wasmer/src/wasmer_breakpoints.rs b/vm-executor-wasmer/src/wasmer_breakpoints.rs index bf8af13..836ce4d 100644 --- a/vm-executor-wasmer/src/wasmer_breakpoints.rs +++ b/vm-executor-wasmer/src/wasmer_breakpoints.rs @@ -10,7 +10,7 @@ use wasmer::{ use wasmer_types::{GlobalIndex, ModuleInfo}; use crate::wasmer_helpers::{ - MiddlewareWithProtectedGlobals, create_global_index, get_global_value_u64, + MiddlewareWithProtectedGlobals, create_i64_global_index, get_global_value_u64, is_control_flow_operator, set_global_value_u64, }; @@ -90,7 +90,7 @@ impl ModuleMiddleware for Breakpoints { let mut global_index = self.global_index.lock().unwrap(); *global_index = Some(BreakpointsGlobalIndex { - breakpoint_value_global_index: create_global_index( + breakpoint_value_global_index: create_i64_global_index( module_info, BREAKPOINT_VALUE, BREAKPOINT_VALUE_NO_BREAKPOINT as i64, diff --git a/vm-executor-wasmer/src/wasmer_executor.rs b/vm-executor-wasmer/src/wasmer_executor.rs index 4d2e97b..167399d 100644 --- a/vm-executor-wasmer/src/wasmer_executor.rs +++ b/vm-executor-wasmer/src/wasmer_executor.rs @@ -1,6 +1,6 @@ use crate::WasmerInstance; use crate::executor_interface::{ - CompilationOptionsLegacy, ExecutorError, ExecutorLegacy, InstanceLegacy, OpcodeCost, + CompilationOptionsLegacy, ExecutorError, ExecutorLegacy, InstanceLegacy, OpcodeConfig, ServiceError, VMHooksLegacy, }; use log::trace; @@ -19,14 +19,14 @@ pub fn force_sighandler_reinstall() { pub struct WasmerExecutorData { vm_hooks: Rc, - opcode_cost: Arc>, + opcode_config: Arc>, } impl WasmerExecutorData { pub fn new(vm_hooks: Box) -> Self { Self { vm_hooks: Rc::from(vm_hooks), - opcode_cost: Arc::new(Mutex::new(OpcodeCost::default())), + opcode_config: Arc::new(Mutex::new(OpcodeConfig::default())), } } @@ -41,9 +41,19 @@ impl WasmerExecutorData { } } - pub fn set_opcode_cost(&mut self, opcode_cost: &OpcodeCost) -> Result<(), ExecutorError> { - self.opcode_cost.lock().unwrap().clone_from(opcode_cost); - Ok(()) + pub fn set_opcode_config(&mut self, opcode_config: OpcodeConfig) -> Result<(), ExecutorError> { + match self.opcode_config.lock() { + Ok(mut opcode_config_ref) => { + *opcode_config_ref = opcode_config; + Ok(()) + } + Err(err) => { + trace!("Failed to acquire lock for setting opcode config: {}", err); + Err(Box::new(ServiceError::new( + "Failed to acquire lock for setting opcode config", + ))) + } + } } } @@ -65,9 +75,9 @@ impl ExecutorLegacy for WasmerExecutor { self.data.borrow_mut().set_vm_hooks_ptr(vm_hooks_ptr) } - fn set_opcode_cost(&mut self, opcode_cost: &OpcodeCost) -> Result<(), ExecutorError> { - trace!("Setting opcode cost..."); - self.data.borrow_mut().set_opcode_cost(opcode_cost) + fn set_opcode_config(&mut self, opcode_config: OpcodeConfig) -> Result<(), ExecutorError> { + trace!("Setting opcode config..."); + self.data.borrow_mut().set_opcode_config(opcode_config) } fn new_instance( @@ -78,7 +88,7 @@ impl ExecutorLegacy for WasmerExecutor { let data = self.data.borrow(); let instance = WasmerInstance::try_new_instance( data.vm_hooks.clone(), - data.opcode_cost.clone(), + data.opcode_config.clone(), wasm_bytes, compilation_options, )?; @@ -93,7 +103,7 @@ impl ExecutorLegacy for WasmerExecutor { let data = self.data.borrow(); let instance = WasmerInstance::try_new_instance_from_cache( data.vm_hooks.clone(), - data.opcode_cost.clone(), + data.opcode_config.clone(), cache_bytes, compilation_options, )?; diff --git a/vm-executor-wasmer/src/wasmer_helpers.rs b/vm-executor-wasmer/src/wasmer_helpers.rs index a3dbc70..5c6c540 100644 --- a/vm-executor-wasmer/src/wasmer_helpers.rs +++ b/vm-executor-wasmer/src/wasmer_helpers.rs @@ -3,7 +3,10 @@ use wasmer::{ }; use wasmer_types::{GlobalIndex, ModuleInfo}; +/// Middleware that marks some globals as protected and therefore not subject to +/// normal runtime mutation. pub trait MiddlewareWithProtectedGlobals { + /// Returns the indices of globals that must remain protected. fn protected_globals(&self) -> Vec; } @@ -13,18 +16,53 @@ impl std::fmt::Debug for dyn MiddlewareWithProtectedGlobals { } } -pub(crate) fn create_global_index( +/// Creates and exports a fresh mutable i32 global in the module and initializes it. +/// +/// # Parameters +/// - `module_info`: the module metadata to update +/// - `key`: the exported name for the global +/// - `init`: the initial value assigned to the global +/// +/// # Returns +/// The index of the newly created global in the module. +pub(crate) fn create_i32_global_index( + module_info: &mut ModuleInfo, + key: &str, + init: i32, +) -> GlobalIndex { + create_global_index(module_info, key, Type::I32, GlobalInit::I32Const(init)) +} + +/// Creates and exports a fresh mutable i64 global in the module and initializes it. +/// +/// # Parameters +/// - `module_info`: the module metadata to update +/// - `key`: the exported name for the global +/// - `init`: the initial value assigned to the global +/// +/// # Returns +/// The index of the newly created global in the module. +pub(crate) fn create_i64_global_index( module_info: &mut ModuleInfo, key: &str, init: i64, +) -> GlobalIndex { + create_global_index(module_info, key, Type::I64, GlobalInit::I64Const(init)) +} + +/// Only reachable through the typed constructors above, which are what keeps the declared +/// type and the initializer from disagreeing. +fn create_global_index( + module_info: &mut ModuleInfo, + key: &str, + global_type: Type, + init: GlobalInit, ) -> GlobalIndex { let global_index = module_info .globals - .push(GlobalType::new(Type::I64, Mutability::Var)); + .push(GlobalType::new(global_type, Mutability::Var)); - module_info - .global_initializers - .push(GlobalInit::I64Const(init)); + module_info.global_initializers.push(init); module_info .exports @@ -33,6 +71,15 @@ pub(crate) fn create_global_index( global_index } +/// Sets the value of a u64 export global on a Wasmer instance. +/// +/// # Parameters +/// - `instance`: the instance that owns the exported global +/// - `global_name`: the name of the exported global +/// - `points`: the new value to assign to the global +/// +/// # Returns +/// `Ok(())` if the update succeeded, otherwise a formatted error message. pub(crate) fn set_global_value_u64( instance: &Instance, global_name: &str, @@ -51,6 +98,15 @@ pub(crate) fn set_global_value_u64( } } +/// Reads the value of a u64 export global from a Wasmer instance. +/// +/// # Parameters +/// - `instance`: the instance that owns the exported global +/// - `global_name`: the name of the exported global +/// +/// # Returns +/// The current value of the global as a `u64`, or an error message if lookup +/// or conversion fails. pub(crate) fn get_global_value_u64(instance: &Instance, global_name: &str) -> Result { let result = instance.exports.get_global(global_name); match result { @@ -65,6 +121,10 @@ pub(crate) fn get_global_value_u64(instance: &Instance, global_name: &str) -> Re } } +/// Returns `true` when the given WebAssembly operator changes control flow. +/// +/// This is used to recognize branching and call-like operations that affect block +/// execution flow and must be treated specially by middleware logic. pub(crate) fn is_control_flow_operator(operator: &Operator) -> bool { matches!( operator, diff --git a/vm-executor-wasmer/src/wasmer_instance.rs b/vm-executor-wasmer/src/wasmer_instance.rs index 6d12986..6ea2033 100644 --- a/vm-executor-wasmer/src/wasmer_instance.rs +++ b/vm-executor-wasmer/src/wasmer_instance.rs @@ -1,6 +1,6 @@ use crate::executor_interface::{ BreakpointValueLegacy, CompilationOptionsLegacy, ExecutorError, InstanceLegacy, MemLength, - MemPtr, OpcodeCost, ServiceError, VMHooksEarlyExit, VMHooksLegacy, + MemPtr, OpcodeConfig, ServiceError, VMHooksEarlyExit, VMHooksLegacy, }; use crate::wasmer_opcode_trace::OpcodeTracer; use crate::wasmer_protected_globals::ProtectedGlobals; @@ -28,7 +28,7 @@ pub struct WasmerInstance { impl WasmerInstance { pub fn try_new_instance( vm_hooks: Rc, - opcode_cost: Arc>, + opcode_config: Arc>, wasm_bytes: &[u8], compilation_options: &CompilationOptionsLegacy, ) -> Result { @@ -36,7 +36,7 @@ impl WasmerInstance { let mut compiler = Singlepass::default(); // Push middlewares - push_middlewares(&mut compiler, compilation_options, opcode_cost); + push_middlewares(&mut compiler, compilation_options, opcode_config); // Create the store let store = Store::new(&Universal::new(compiler).engine()); @@ -74,7 +74,7 @@ impl WasmerInstance { pub fn try_new_instance_from_cache( vm_hooks: Rc, - opcode_cost: Arc>, + opcode_config: Arc>, cache_bytes: &[u8], compilation_options: &CompilationOptionsLegacy, ) -> Result { @@ -82,7 +82,7 @@ impl WasmerInstance { let mut compiler = Singlepass::default(); // Push middlewares - push_middlewares(&mut compiler, compilation_options, opcode_cost); + push_middlewares(&mut compiler, compilation_options, opcode_config); // Create the store let store = Store::new(&Universal::new(compiler).engine()); @@ -181,7 +181,7 @@ fn validate_memory(memory: &wasmer::Memory) -> Result<(), ExecutorError> { fn push_middlewares( compiler: &mut Singlepass, compilation_options: &CompilationOptionsLegacy, - opcode_cost: Arc>, + opcode_config: Arc>, ) { // Create breakpoints middleware let breakpoints_middleware = Arc::new(Breakpoints::new()); @@ -198,7 +198,7 @@ fn push_middlewares( let metering_middleware = Arc::new(Metering::new( compilation_options.gas_limit, compilation_options.unmetered_locals, - opcode_cost, + opcode_config, breakpoints_middleware.clone(), )); diff --git a/vm-executor-wasmer/src/wasmer_metering.rs b/vm-executor-wasmer/src/wasmer_metering.rs index 1b940f8..6661685 100644 --- a/vm-executor-wasmer/src/wasmer_metering.rs +++ b/vm-executor-wasmer/src/wasmer_metering.rs @@ -1,10 +1,11 @@ -use crate::executor_interface::OpcodeCost; +use crate::executor_interface::OpcodeConfig; +use crate::get_opcode_cost; use crate::wasmer_breakpoints::{BREAKPOINT_VALUE_OUT_OF_GAS, Breakpoints}; use crate::wasmer_helpers::{ - MiddlewareWithProtectedGlobals, create_global_index, get_global_value_u64, - is_control_flow_operator, set_global_value_u64, + MiddlewareWithProtectedGlobals, create_i32_global_index, create_i64_global_index, + get_global_value_u64, is_control_flow_operator, set_global_value_u64, }; -use crate::{get_local_cost, get_opcode_cost}; +use crate::wasmer_opcode_cost_type::Cost; use loupe::{MemoryUsage, MemoryUsageTracker}; use std::mem; use std::sync::{Arc, Mutex}; @@ -17,19 +18,21 @@ use wasmer_types::{GlobalIndex, ModuleInfo}; const METERING_POINTS_LIMIT: &str = "metering_points_limit"; const METERING_POINTS_USED: &str = "metering_points_used"; +const METERING_BULK_MEMORY_SIZE_OPERAND_BACKUP: &str = "metering_bulk_memory_size_operand_backup"; const MAX_LOCAL_COUNT: u32 = 4000; #[derive(Clone, Debug, MemoryUsage)] struct MeteringGlobalIndexes { points_limit_global_index: GlobalIndex, points_used_global_index: GlobalIndex, + bulk_memory_size_operand_backup_global_index: GlobalIndex, } #[derive(Debug)] pub(crate) struct Metering { points_limit: u64, unmetered_locals: usize, - opcode_cost: Arc>, + opcode_config: Arc>, breakpoints_middleware: Arc, global_indexes: Mutex>, } @@ -38,13 +41,13 @@ impl Metering { pub(crate) fn new( points_limit: u64, unmetered_locals: usize, - opcode_cost: Arc>, + opcode_config: Arc>, breakpoints_middleware: Arc, ) -> Self { Self { points_limit, unmetered_locals, - opcode_cost, + opcode_config, breakpoints_middleware, global_indexes: Mutex::new(None), } @@ -67,6 +70,15 @@ impl Metering { .unwrap() .points_used_global_index } + + fn get_bulk_memory_size_operand_backup_global_index(&self) -> GlobalIndex { + self.global_indexes + .lock() + .unwrap() + .as_ref() + .unwrap() + .bulk_memory_size_operand_backup_global_index + } } unsafe impl Send for Metering {} @@ -87,7 +99,7 @@ impl ModuleMiddleware for Metering { Box::new(FunctionMetering { accumulated_cost: Default::default(), unmetered_locals: self.unmetered_locals, - opcode_cost: self.opcode_cost.clone(), + opcode_config: self.opcode_config.clone(), breakpoints_middleware: self.breakpoints_middleware.clone(), global_indexes: self.global_indexes.lock().unwrap().clone().unwrap(), }) @@ -99,12 +111,17 @@ impl ModuleMiddleware for Metering { let points_limit = self.points_limit as i64; *global_indexes = Some(MeteringGlobalIndexes { - points_limit_global_index: create_global_index( + points_limit_global_index: create_i64_global_index( module_info, METERING_POINTS_LIMIT, points_limit, ), - points_used_global_index: create_global_index(module_info, METERING_POINTS_USED, 0), + points_used_global_index: create_i64_global_index(module_info, METERING_POINTS_USED, 0), + bulk_memory_size_operand_backup_global_index: create_i32_global_index( + module_info, + METERING_BULK_MEMORY_SIZE_OPERAND_BACKUP, + 0, + ), }); } } @@ -114,6 +131,8 @@ impl MiddlewareWithProtectedGlobals for Metering { vec![ self.get_points_limit_global_index().as_u32(), self.get_points_used_global_index().as_u32(), + self.get_bulk_memory_size_operand_backup_global_index() + .as_u32(), ] } } @@ -122,7 +141,7 @@ impl MiddlewareWithProtectedGlobals for Metering { struct FunctionMetering { accumulated_cost: u64, unmetered_locals: usize, - opcode_cost: Arc>, + opcode_config: Arc>, breakpoints_middleware: Arc, global_indexes: MeteringGlobalIndexes, } @@ -156,6 +175,72 @@ impl FunctionMetering { self.breakpoints_middleware .inject_breakpoint_condition(state, BREAKPOINT_VALUE_OUT_OF_GAS); } + + /// Injects `points_used += size * cost_per_byte` ahead of a `memory.copy`/`memory.fill`, + /// using `size` as it appears on the stack (the raw, attacker-controlled operand), before + /// the real bulk-memory instruction validates it against the actual memory bounds. + /// + /// This only covers the cost of the bytes. The flat cost of the instruction itself goes + /// through the regular accumulator, so that a zero-size copy or fill is never free. + /// + /// Both operators take `size` as their last operand - `memory.copy` is `[dst, src, size]`, + /// `memory.fill` is `[dst, value, size]` - so the same injection works for both. + /// + /// `size` is an `i32` and so is the global it is parked in, so it makes the round trip + /// untouched. Only the multiplication is widened to `i64`, to leave room for the product. + /// + /// The multiplication is plain wrapping `i64` arithmetic (wasm has no trapping or + /// saturating integer multiply, and Wasmer doesn't offer one either), so it can in theory + /// wrap around for a large enough `size`. This is not exploitable under the current setup: + /// - `MAX_MEMORY_PAGES_ALLOWED` (in `wasmer_instance.rs`) caps declared memory at 20 pages + /// (1.25 MiB), enforced at instantiation. + /// - any `size` large enough to matter for overflow (`size * cost_per_byte` approaching + /// `i64::MAX`) necessarily exceeds that 1.25 MiB bound, so the real `memory.copy`/ + /// `memory.fill` that follows this injected code traps on out-of-bounds access and aborts + /// the call immediately — regardless of what `points_used` ended up holding. + /// - a `size` that keeps the copy in-bounds is capped at 1,310,720 bytes, so + /// `size * cost_per_byte` tops out around `5.6e15` for a `u32` cost, far below `i64::MAX`. + fn inject_bulk_memory_cost(&self, state: &mut MiddlewareReaderState, cost_per_byte: u32) { + // backup the bulk memory size + state.extend(&[Operator::GlobalSet { + global_index: self + .global_indexes + .bulk_memory_size_operand_backup_global_index + .as_u32(), + }]); + + // inject bulk memory cost + state.extend(&[ + // size * cost_per_byte + Operator::GlobalGet { + global_index: self + .global_indexes + .bulk_memory_size_operand_backup_global_index + .as_u32(), + }, + Operator::I64ExtendI32U, + Operator::I64Const { + value: cost_per_byte as i64, + }, + Operator::I64Mul, + // points_used += size * cost_per_byte + Operator::GlobalGet { + global_index: self.global_indexes.points_used_global_index.as_u32(), + }, + Operator::I64Add, + Operator::GlobalSet { + global_index: self.global_indexes.points_used_global_index.as_u32(), + }, + ]); + + // bring back the bulk memory size + state.extend(&[Operator::GlobalGet { + global_index: self + .global_indexes + .bulk_memory_size_operand_backup_global_index + .as_u32(), + }]); + } } impl FunctionMiddleware for FunctionMetering { @@ -167,15 +252,28 @@ impl FunctionMiddleware for FunctionMetering { // Get the cost of the current operator, and add it to the accumulator. // This needs to be done before the metering logic, to prevent operators like `Call` from escaping metering in some // corner cases. - let option = get_opcode_cost(&operator, &self.opcode_cost.lock().unwrap()); - match option { - Some(cost) => self.accumulated_cost += cost as u64, - None => { + let cost = get_opcode_cost(&operator, &self.opcode_config.lock().unwrap()); + match cost { + Cost::Illegal => { return Err(MiddlewareError::new( "metering_middleware", format!("Unsupported operator: {operator:?}"), )); } + Cost::Base(base) => self.accumulated_cost += base as u64, + Cost::BulkMemory { base, per_byte } => { + self.accumulated_cost += base as u64; + + // flush what accumulated so far, including the base cost of this operator, + // so that the out of gas check below takes all of it into account + self.inject_points_used_increment(state); + self.accumulated_cost = 0; + + self.inject_bulk_memory_cost(state, per_byte); + + // immediately insert out of gas check as this operation might be expensive + self.inject_out_of_gas_check(state); + } } if is_control_flow_operator(&operator) { @@ -196,7 +294,12 @@ impl FunctionMiddleware for FunctionMetering { let unmetered_locals = self.unmetered_locals as u32; if count > unmetered_locals { let metered_locals = count - unmetered_locals; - let local_cost = get_local_cost(&self.opcode_cost.lock().unwrap()); + let local_cost = self + .opcode_config + .lock() + .unwrap() + .opcode_cost + .opcode_localallocate; let metered_locals_cost = metered_locals * local_cost; self.accumulated_cost += metered_locals_cost as u64; } diff --git a/vm-executor-wasmer/src/wasmer_metering_helpers.rs b/vm-executor-wasmer/src/wasmer_metering_helpers.rs deleted file mode 100644 index dcd1996..0000000 --- a/vm-executor-wasmer/src/wasmer_metering_helpers.rs +++ /dev/null @@ -1,145 +0,0 @@ -// Code generated by vmhooks generator. DO NOT EDIT. - -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! -// !!!!!!!!!!!!!!!!!!!!!! AUTO-GENERATED FILE !!!!!!!!!!!!!!!!!!!!!! -// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - -use crate::executor_interface::OpcodeCost; -use wasmer::wasmparser::Operator; - -pub fn get_local_cost(opcode_cost: &OpcodeCost) -> u32 { - opcode_cost.opcode_localallocate -} - -pub fn get_opcode_cost(op: &Operator, opcode_cost: &OpcodeCost) -> Option { - match op { - Operator::Block { .. } => Some(opcode_cost.opcode_block), - Operator::Br { .. } => Some(opcode_cost.opcode_br), - Operator::BrIf { .. } => Some(opcode_cost.opcode_brif), - Operator::BrTable { .. } => Some(opcode_cost.opcode_brtable), - Operator::Call { .. } => Some(opcode_cost.opcode_call), - Operator::CallIndirect { .. } => Some(opcode_cost.opcode_callindirect), - Operator::Catch { .. } => Some(opcode_cost.opcode_catch), - Operator::CatchAll { .. } => Some(opcode_cost.opcode_catchall), - Operator::Delegate { .. } => Some(opcode_cost.opcode_delegate), - Operator::Drop { .. } => Some(opcode_cost.opcode_drop), - Operator::Else { .. } => Some(opcode_cost.opcode_else), - Operator::End { .. } => Some(opcode_cost.opcode_end), - Operator::GlobalGet { .. } => Some(opcode_cost.opcode_globalget), - Operator::GlobalSet { .. } => Some(opcode_cost.opcode_globalset), - Operator::I32Add { .. } => Some(opcode_cost.opcode_i32add), - Operator::I32And { .. } => Some(opcode_cost.opcode_i32and), - Operator::I32Clz { .. } => Some(opcode_cost.opcode_i32clz), - Operator::I32Const { .. } => Some(opcode_cost.opcode_i32const), - Operator::I32Ctz { .. } => Some(opcode_cost.opcode_i32ctz), - Operator::I32DivS { .. } => Some(opcode_cost.opcode_i32divs), - Operator::I32DivU { .. } => Some(opcode_cost.opcode_i32divu), - Operator::I32Eq { .. } => Some(opcode_cost.opcode_i32eq), - Operator::I32Eqz { .. } => Some(opcode_cost.opcode_i32eqz), - Operator::I32Extend16S { .. } => Some(opcode_cost.opcode_i32extend16s), - Operator::I32Extend8S { .. } => Some(opcode_cost.opcode_i32extend8s), - Operator::I32GeS { .. } => Some(opcode_cost.opcode_i32ges), - Operator::I32GeU { .. } => Some(opcode_cost.opcode_i32geu), - Operator::I32GtS { .. } => Some(opcode_cost.opcode_i32gts), - Operator::I32GtU { .. } => Some(opcode_cost.opcode_i32gtu), - Operator::I32LeS { .. } => Some(opcode_cost.opcode_i32les), - Operator::I32LeU { .. } => Some(opcode_cost.opcode_i32leu), - Operator::I32Load { .. } => Some(opcode_cost.opcode_i32load), - Operator::I32Load16S { .. } => Some(opcode_cost.opcode_i32load16s), - Operator::I32Load16U { .. } => Some(opcode_cost.opcode_i32load16u), - Operator::I32Load8S { .. } => Some(opcode_cost.opcode_i32load8s), - Operator::I32Load8U { .. } => Some(opcode_cost.opcode_i32load8u), - Operator::I32LtS { .. } => Some(opcode_cost.opcode_i32lts), - Operator::I32LtU { .. } => Some(opcode_cost.opcode_i32ltu), - Operator::I32Mul { .. } => Some(opcode_cost.opcode_i32mul), - Operator::I32Ne { .. } => Some(opcode_cost.opcode_i32ne), - Operator::I32Or { .. } => Some(opcode_cost.opcode_i32or), - Operator::I32Popcnt { .. } => Some(opcode_cost.opcode_i32popcnt), - Operator::I32RemS { .. } => Some(opcode_cost.opcode_i32rems), - Operator::I32RemU { .. } => Some(opcode_cost.opcode_i32remu), - Operator::I32Rotl { .. } => Some(opcode_cost.opcode_i32rotl), - Operator::I32Rotr { .. } => Some(opcode_cost.opcode_i32rotr), - Operator::I32Shl { .. } => Some(opcode_cost.opcode_i32shl), - Operator::I32ShrS { .. } => Some(opcode_cost.opcode_i32shrs), - Operator::I32ShrU { .. } => Some(opcode_cost.opcode_i32shru), - Operator::I32Store { .. } => Some(opcode_cost.opcode_i32store), - Operator::I32Store16 { .. } => Some(opcode_cost.opcode_i32store16), - Operator::I32Store8 { .. } => Some(opcode_cost.opcode_i32store8), - Operator::I32Sub { .. } => Some(opcode_cost.opcode_i32sub), - Operator::I32WrapI64 { .. } => Some(opcode_cost.opcode_i32wrapi64), - Operator::I32Xor { .. } => Some(opcode_cost.opcode_i32xor), - Operator::I64Add { .. } => Some(opcode_cost.opcode_i64add), - Operator::I64And { .. } => Some(opcode_cost.opcode_i64and), - Operator::I64Clz { .. } => Some(opcode_cost.opcode_i64clz), - Operator::I64Const { .. } => Some(opcode_cost.opcode_i64const), - Operator::I64Ctz { .. } => Some(opcode_cost.opcode_i64ctz), - Operator::I64DivS { .. } => Some(opcode_cost.opcode_i64divs), - Operator::I64DivU { .. } => Some(opcode_cost.opcode_i64divu), - Operator::I64Eq { .. } => Some(opcode_cost.opcode_i64eq), - Operator::I64Eqz { .. } => Some(opcode_cost.opcode_i64eqz), - Operator::I64Extend16S { .. } => Some(opcode_cost.opcode_i64extend16s), - Operator::I64Extend32S { .. } => Some(opcode_cost.opcode_i64extend32s), - Operator::I64Extend8S { .. } => Some(opcode_cost.opcode_i64extend8s), - Operator::I64ExtendI32S { .. } => Some(opcode_cost.opcode_i64extendi32s), - Operator::I64ExtendI32U { .. } => Some(opcode_cost.opcode_i64extendi32u), - Operator::I64GeS { .. } => Some(opcode_cost.opcode_i64ges), - Operator::I64GeU { .. } => Some(opcode_cost.opcode_i64geu), - Operator::I64GtS { .. } => Some(opcode_cost.opcode_i64gts), - Operator::I64GtU { .. } => Some(opcode_cost.opcode_i64gtu), - Operator::I64LeS { .. } => Some(opcode_cost.opcode_i64les), - Operator::I64LeU { .. } => Some(opcode_cost.opcode_i64leu), - Operator::I64Load { .. } => Some(opcode_cost.opcode_i64load), - Operator::I64Load16S { .. } => Some(opcode_cost.opcode_i64load16s), - Operator::I64Load16U { .. } => Some(opcode_cost.opcode_i64load16u), - Operator::I64Load32S { .. } => Some(opcode_cost.opcode_i64load32s), - Operator::I64Load32U { .. } => Some(opcode_cost.opcode_i64load32u), - Operator::I64Load8S { .. } => Some(opcode_cost.opcode_i64load8s), - Operator::I64Load8U { .. } => Some(opcode_cost.opcode_i64load8u), - Operator::I64LtS { .. } => Some(opcode_cost.opcode_i64lts), - Operator::I64LtU { .. } => Some(opcode_cost.opcode_i64ltu), - Operator::I64Mul { .. } => Some(opcode_cost.opcode_i64mul), - Operator::I64Ne { .. } => Some(opcode_cost.opcode_i64ne), - Operator::I64Or { .. } => Some(opcode_cost.opcode_i64or), - Operator::I64Popcnt { .. } => Some(opcode_cost.opcode_i64popcnt), - Operator::I64RemS { .. } => Some(opcode_cost.opcode_i64rems), - Operator::I64RemU { .. } => Some(opcode_cost.opcode_i64remu), - Operator::I64Rotl { .. } => Some(opcode_cost.opcode_i64rotl), - Operator::I64Rotr { .. } => Some(opcode_cost.opcode_i64rotr), - Operator::I64Shl { .. } => Some(opcode_cost.opcode_i64shl), - Operator::I64ShrS { .. } => Some(opcode_cost.opcode_i64shrs), - Operator::I64ShrU { .. } => Some(opcode_cost.opcode_i64shru), - Operator::I64Store { .. } => Some(opcode_cost.opcode_i64store), - Operator::I64Store16 { .. } => Some(opcode_cost.opcode_i64store16), - Operator::I64Store32 { .. } => Some(opcode_cost.opcode_i64store32), - Operator::I64Store8 { .. } => Some(opcode_cost.opcode_i64store8), - Operator::I64Sub { .. } => Some(opcode_cost.opcode_i64sub), - Operator::I64Xor { .. } => Some(opcode_cost.opcode_i64xor), - Operator::If { .. } => Some(opcode_cost.opcode_if), - Operator::LocalGet { .. } => Some(opcode_cost.opcode_localget), - Operator::LocalSet { .. } => Some(opcode_cost.opcode_localset), - Operator::LocalTee { .. } => Some(opcode_cost.opcode_localtee), - Operator::Loop { .. } => Some(opcode_cost.opcode_loop), - Operator::MemoryGrow { .. } => Some(opcode_cost.opcode_memorygrow), - Operator::MemorySize { .. } => Some(opcode_cost.opcode_memorysize), - Operator::Nop { .. } => Some(opcode_cost.opcode_nop), - Operator::RefFunc { .. } => Some(opcode_cost.opcode_reffunc), - Operator::RefIsNull { .. } => Some(opcode_cost.opcode_refisnull), - Operator::RefNull { .. } => Some(opcode_cost.opcode_refnull), - Operator::Rethrow { .. } => Some(opcode_cost.opcode_rethrow), - Operator::Return { .. } => Some(opcode_cost.opcode_return), - Operator::ReturnCall { .. } => Some(opcode_cost.opcode_returncall), - Operator::ReturnCallIndirect { .. } => Some(opcode_cost.opcode_returncallindirect), - Operator::Select { .. } => Some(opcode_cost.opcode_select), - Operator::TableGet { .. } => Some(opcode_cost.opcode_tableget), - Operator::TableGrow { .. } => Some(opcode_cost.opcode_tablegrow), - Operator::TableInit { .. } => Some(opcode_cost.opcode_tableinit), - Operator::TableSet { .. } => Some(opcode_cost.opcode_tableset), - Operator::TableSize { .. } => Some(opcode_cost.opcode_tablesize), - Operator::Throw { .. } => Some(opcode_cost.opcode_throw), - Operator::Try { .. } => Some(opcode_cost.opcode_try), - Operator::TypedSelect { .. } => Some(opcode_cost.opcode_typedselect), - Operator::Unreachable { .. } => Some(opcode_cost.opcode_unreachable), - Operator::Unwind { .. } => Some(opcode_cost.opcode_unwind), - _ => None, - } -} diff --git a/vm-executor-wasmer/src/wasmer_opcode_control.rs b/vm-executor-wasmer/src/wasmer_opcode_control.rs index 3c74b40..d51021d 100644 --- a/vm-executor-wasmer/src/wasmer_opcode_control.rs +++ b/vm-executor-wasmer/src/wasmer_opcode_control.rs @@ -12,7 +12,7 @@ use wasmer_types::{GlobalIndex, ModuleInfo}; use crate::{ wasmer_breakpoints::{BREAKPOINT_VALUE_MEMORY_LIMIT, Breakpoints}, - wasmer_helpers::{MiddlewareWithProtectedGlobals, create_global_index}, + wasmer_helpers::{MiddlewareWithProtectedGlobals, create_i64_global_index}, }; const OPCODE_CONTROL_MEMORY_GROW_COUNT: &str = "opcode_control_memory_grow_count"; @@ -99,12 +99,12 @@ impl ModuleMiddleware for OpcodeControl { let mut global_indexes = self.global_indexes.lock().unwrap(); *global_indexes = Some(OpcodeControlGlobalIndexes { - memory_grow_count_global_index: create_global_index( + memory_grow_count_global_index: create_i64_global_index( module_info, OPCODE_CONTROL_MEMORY_GROW_COUNT, 0, ), - operand_backup_global_index: create_global_index( + operand_backup_global_index: create_i64_global_index( module_info, OPCODE_CONTROL_OPERAND_BACKUP, 0, diff --git a/vm-executor-wasmer/src/wasmer_opcode_cost.rs b/vm-executor-wasmer/src/wasmer_opcode_cost.rs new file mode 100644 index 0000000..44e1594 --- /dev/null +++ b/vm-executor-wasmer/src/wasmer_opcode_cost.rs @@ -0,0 +1,270 @@ +// Code generated by vmhooks generator. DO NOT EDIT. + +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!! AUTO-GENERATED FILE !!!!!!!!!!!!!!!!!!!!!! +// !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + +use crate::Cost; +use crate::executor_interface::{OpcodeConfig, OpcodeCost, OpcodeVersion}; +use wasmer::wasmparser::Operator; + +pub fn get_opcode_cost(op: &Operator, opcode_config: &OpcodeConfig) -> Cost { + match opcode_config.opcode_version { + OpcodeVersion::V1 => get_opcode_cost_v1(op, &opcode_config.opcode_cost), + OpcodeVersion::V2 => get_opcode_cost_v2(op, &opcode_config.opcode_cost), + } +} + +#[rustfmt::skip] +fn get_opcode_cost_v1(op: &Operator, opcode_cost: &OpcodeCost) -> Cost { + match op { + Operator::Block { .. } => Cost::Base(opcode_cost.opcode_block), + Operator::Br { .. } => Cost::Base(opcode_cost.opcode_br), + Operator::BrIf { .. } => Cost::Base(opcode_cost.opcode_brif), + Operator::BrTable { .. } => Cost::Base(opcode_cost.opcode_brtable), + Operator::Call { .. } => Cost::Base(opcode_cost.opcode_call), + Operator::CallIndirect { .. } => Cost::Base(opcode_cost.opcode_callindirect), + Operator::Catch { .. } => Cost::Base(opcode_cost.opcode_catch), + Operator::CatchAll { .. } => Cost::Base(opcode_cost.opcode_catchall), + Operator::Delegate { .. } => Cost::Base(opcode_cost.opcode_delegate), + Operator::Drop { .. } => Cost::Base(opcode_cost.opcode_drop), + Operator::Else { .. } => Cost::Base(opcode_cost.opcode_else), + Operator::End { .. } => Cost::Base(opcode_cost.opcode_end), + Operator::GlobalGet { .. } => Cost::Base(opcode_cost.opcode_globalget), + Operator::GlobalSet { .. } => Cost::Base(opcode_cost.opcode_globalset), + Operator::I32Add { .. } => Cost::Base(opcode_cost.opcode_i32add), + Operator::I32And { .. } => Cost::Base(opcode_cost.opcode_i32and), + Operator::I32Clz { .. } => Cost::Base(opcode_cost.opcode_i32clz), + Operator::I32Const { .. } => Cost::Base(opcode_cost.opcode_i32const), + Operator::I32Ctz { .. } => Cost::Base(opcode_cost.opcode_i32ctz), + Operator::I32DivS { .. } => Cost::Base(opcode_cost.opcode_i32divs), + Operator::I32DivU { .. } => Cost::Base(opcode_cost.opcode_i32divu), + Operator::I32Eq { .. } => Cost::Base(opcode_cost.opcode_i32eq), + Operator::I32Eqz { .. } => Cost::Base(opcode_cost.opcode_i32eqz), + Operator::I32Extend16S { .. } => Cost::Base(opcode_cost.opcode_i32extend16s), + Operator::I32Extend8S { .. } => Cost::Base(opcode_cost.opcode_i32extend8s), + Operator::I32GeS { .. } => Cost::Base(opcode_cost.opcode_i32ges), + Operator::I32GeU { .. } => Cost::Base(opcode_cost.opcode_i32geu), + Operator::I32GtS { .. } => Cost::Base(opcode_cost.opcode_i32gts), + Operator::I32GtU { .. } => Cost::Base(opcode_cost.opcode_i32gtu), + Operator::I32LeS { .. } => Cost::Base(opcode_cost.opcode_i32les), + Operator::I32LeU { .. } => Cost::Base(opcode_cost.opcode_i32leu), + Operator::I32Load { .. } => Cost::Base(opcode_cost.opcode_i32load), + Operator::I32Load16S { .. } => Cost::Base(opcode_cost.opcode_i32load16s), + Operator::I32Load16U { .. } => Cost::Base(opcode_cost.opcode_i32load16u), + Operator::I32Load8S { .. } => Cost::Base(opcode_cost.opcode_i32load8s), + Operator::I32Load8U { .. } => Cost::Base(opcode_cost.opcode_i32load8u), + Operator::I32LtS { .. } => Cost::Base(opcode_cost.opcode_i32lts), + Operator::I32LtU { .. } => Cost::Base(opcode_cost.opcode_i32ltu), + Operator::I32Mul { .. } => Cost::Base(opcode_cost.opcode_i32mul), + Operator::I32Ne { .. } => Cost::Base(opcode_cost.opcode_i32ne), + Operator::I32Or { .. } => Cost::Base(opcode_cost.opcode_i32or), + Operator::I32Popcnt { .. } => Cost::Base(opcode_cost.opcode_i32popcnt), + Operator::I32RemS { .. } => Cost::Base(opcode_cost.opcode_i32rems), + Operator::I32RemU { .. } => Cost::Base(opcode_cost.opcode_i32remu), + Operator::I32Rotl { .. } => Cost::Base(opcode_cost.opcode_i32rotl), + Operator::I32Rotr { .. } => Cost::Base(opcode_cost.opcode_i32rotr), + Operator::I32Shl { .. } => Cost::Base(opcode_cost.opcode_i32shl), + Operator::I32ShrS { .. } => Cost::Base(opcode_cost.opcode_i32shrs), + Operator::I32ShrU { .. } => Cost::Base(opcode_cost.opcode_i32shru), + Operator::I32Store { .. } => Cost::Base(opcode_cost.opcode_i32store), + Operator::I32Store16 { .. } => Cost::Base(opcode_cost.opcode_i32store16), + Operator::I32Store8 { .. } => Cost::Base(opcode_cost.opcode_i32store8), + Operator::I32Sub { .. } => Cost::Base(opcode_cost.opcode_i32sub), + Operator::I32WrapI64 { .. } => Cost::Base(opcode_cost.opcode_i32wrapi64), + Operator::I32Xor { .. } => Cost::Base(opcode_cost.opcode_i32xor), + Operator::I64Add { .. } => Cost::Base(opcode_cost.opcode_i64add), + Operator::I64And { .. } => Cost::Base(opcode_cost.opcode_i64and), + Operator::I64Clz { .. } => Cost::Base(opcode_cost.opcode_i64clz), + Operator::I64Const { .. } => Cost::Base(opcode_cost.opcode_i64const), + Operator::I64Ctz { .. } => Cost::Base(opcode_cost.opcode_i64ctz), + Operator::I64DivS { .. } => Cost::Base(opcode_cost.opcode_i64divs), + Operator::I64DivU { .. } => Cost::Base(opcode_cost.opcode_i64divu), + Operator::I64Eq { .. } => Cost::Base(opcode_cost.opcode_i64eq), + Operator::I64Eqz { .. } => Cost::Base(opcode_cost.opcode_i64eqz), + Operator::I64Extend16S { .. } => Cost::Base(opcode_cost.opcode_i64extend16s), + Operator::I64Extend32S { .. } => Cost::Base(opcode_cost.opcode_i64extend32s), + Operator::I64Extend8S { .. } => Cost::Base(opcode_cost.opcode_i64extend8s), + Operator::I64ExtendI32S { .. } => Cost::Base(opcode_cost.opcode_i64extendi32s), + Operator::I64ExtendI32U { .. } => Cost::Base(opcode_cost.opcode_i64extendi32u), + Operator::I64GeS { .. } => Cost::Base(opcode_cost.opcode_i64ges), + Operator::I64GeU { .. } => Cost::Base(opcode_cost.opcode_i64geu), + Operator::I64GtS { .. } => Cost::Base(opcode_cost.opcode_i64gts), + Operator::I64GtU { .. } => Cost::Base(opcode_cost.opcode_i64gtu), + Operator::I64LeS { .. } => Cost::Base(opcode_cost.opcode_i64les), + Operator::I64LeU { .. } => Cost::Base(opcode_cost.opcode_i64leu), + Operator::I64Load { .. } => Cost::Base(opcode_cost.opcode_i64load), + Operator::I64Load16S { .. } => Cost::Base(opcode_cost.opcode_i64load16s), + Operator::I64Load16U { .. } => Cost::Base(opcode_cost.opcode_i64load16u), + Operator::I64Load32S { .. } => Cost::Base(opcode_cost.opcode_i64load32s), + Operator::I64Load32U { .. } => Cost::Base(opcode_cost.opcode_i64load32u), + Operator::I64Load8S { .. } => Cost::Base(opcode_cost.opcode_i64load8s), + Operator::I64Load8U { .. } => Cost::Base(opcode_cost.opcode_i64load8u), + Operator::I64LtS { .. } => Cost::Base(opcode_cost.opcode_i64lts), + Operator::I64LtU { .. } => Cost::Base(opcode_cost.opcode_i64ltu), + Operator::I64Mul { .. } => Cost::Base(opcode_cost.opcode_i64mul), + Operator::I64Ne { .. } => Cost::Base(opcode_cost.opcode_i64ne), + Operator::I64Or { .. } => Cost::Base(opcode_cost.opcode_i64or), + Operator::I64Popcnt { .. } => Cost::Base(opcode_cost.opcode_i64popcnt), + Operator::I64RemS { .. } => Cost::Base(opcode_cost.opcode_i64rems), + Operator::I64RemU { .. } => Cost::Base(opcode_cost.opcode_i64remu), + Operator::I64Rotl { .. } => Cost::Base(opcode_cost.opcode_i64rotl), + Operator::I64Rotr { .. } => Cost::Base(opcode_cost.opcode_i64rotr), + Operator::I64Shl { .. } => Cost::Base(opcode_cost.opcode_i64shl), + Operator::I64ShrS { .. } => Cost::Base(opcode_cost.opcode_i64shrs), + Operator::I64ShrU { .. } => Cost::Base(opcode_cost.opcode_i64shru), + Operator::I64Store { .. } => Cost::Base(opcode_cost.opcode_i64store), + Operator::I64Store16 { .. } => Cost::Base(opcode_cost.opcode_i64store16), + Operator::I64Store32 { .. } => Cost::Base(opcode_cost.opcode_i64store32), + Operator::I64Store8 { .. } => Cost::Base(opcode_cost.opcode_i64store8), + Operator::I64Sub { .. } => Cost::Base(opcode_cost.opcode_i64sub), + Operator::I64Xor { .. } => Cost::Base(opcode_cost.opcode_i64xor), + Operator::If { .. } => Cost::Base(opcode_cost.opcode_if), + Operator::LocalGet { .. } => Cost::Base(opcode_cost.opcode_localget), + Operator::LocalSet { .. } => Cost::Base(opcode_cost.opcode_localset), + Operator::LocalTee { .. } => Cost::Base(opcode_cost.opcode_localtee), + Operator::Loop { .. } => Cost::Base(opcode_cost.opcode_loop), + Operator::MemoryGrow { .. } => Cost::Base(opcode_cost.opcode_memorygrow), + Operator::MemorySize { .. } => Cost::Base(opcode_cost.opcode_memorysize), + Operator::Nop { .. } => Cost::Base(opcode_cost.opcode_nop), + Operator::RefFunc { .. } => Cost::Base(opcode_cost.opcode_reffunc), + Operator::RefIsNull { .. } => Cost::Base(opcode_cost.opcode_refisnull), + Operator::RefNull { .. } => Cost::Base(opcode_cost.opcode_refnull), + Operator::Rethrow { .. } => Cost::Base(opcode_cost.opcode_rethrow), + Operator::Return { .. } => Cost::Base(opcode_cost.opcode_return), + Operator::ReturnCall { .. } => Cost::Base(opcode_cost.opcode_returncall), + Operator::ReturnCallIndirect { .. } => Cost::Base(opcode_cost.opcode_returncallindirect), + Operator::Select { .. } => Cost::Base(opcode_cost.opcode_select), + Operator::TableGet { .. } => Cost::Base(opcode_cost.opcode_tableget), + Operator::TableGrow { .. } => Cost::Base(opcode_cost.opcode_tablegrow), + Operator::TableInit { .. } => Cost::Base(opcode_cost.opcode_tableinit), + Operator::TableSet { .. } => Cost::Base(opcode_cost.opcode_tableset), + Operator::TableSize { .. } => Cost::Base(opcode_cost.opcode_tablesize), + Operator::Throw { .. } => Cost::Base(opcode_cost.opcode_throw), + Operator::Try { .. } => Cost::Base(opcode_cost.opcode_try), + Operator::TypedSelect { .. } => Cost::Base(opcode_cost.opcode_typedselect), + Operator::Unreachable { .. } => Cost::Base(opcode_cost.opcode_unreachable), + Operator::Unwind { .. } => Cost::Base(opcode_cost.opcode_unwind), + _ => Cost::Illegal, + } +} + +#[rustfmt::skip] +fn get_opcode_cost_v2(op: &Operator, opcode_cost: &OpcodeCost) -> Cost { + match op { + Operator::Block { .. } => Cost::Base(opcode_cost.opcode_block), + Operator::Br { .. } => Cost::Base(opcode_cost.opcode_br), + Operator::BrIf { .. } => Cost::Base(opcode_cost.opcode_brif), + Operator::BrTable { .. } => Cost::Base(opcode_cost.opcode_brtable), + Operator::Call { .. } => Cost::Base(opcode_cost.opcode_call), + Operator::CallIndirect { .. } => Cost::Base(opcode_cost.opcode_callindirect), + Operator::Drop { .. } => Cost::Base(opcode_cost.opcode_drop), + Operator::Else { .. } => Cost::Base(opcode_cost.opcode_else), + Operator::End { .. } => Cost::Base(opcode_cost.opcode_end), + Operator::GlobalGet { .. } => Cost::Base(opcode_cost.opcode_globalget), + Operator::GlobalSet { .. } => Cost::Base(opcode_cost.opcode_globalset), + Operator::I32Add { .. } => Cost::Base(opcode_cost.opcode_i32add), + Operator::I32And { .. } => Cost::Base(opcode_cost.opcode_i32and), + Operator::I32Clz { .. } => Cost::Base(opcode_cost.opcode_i32clz), + Operator::I32Const { .. } => Cost::Base(opcode_cost.opcode_i32const), + Operator::I32Ctz { .. } => Cost::Base(opcode_cost.opcode_i32ctz), + Operator::I32DivS { .. } => Cost::Base(opcode_cost.opcode_i32divs), + Operator::I32DivU { .. } => Cost::Base(opcode_cost.opcode_i32divu), + Operator::I32Eq { .. } => Cost::Base(opcode_cost.opcode_i32eq), + Operator::I32Eqz { .. } => Cost::Base(opcode_cost.opcode_i32eqz), + Operator::I32Extend16S { .. } => Cost::Base(opcode_cost.opcode_i32extend16s), + Operator::I32Extend8S { .. } => Cost::Base(opcode_cost.opcode_i32extend8s), + Operator::I32GeS { .. } => Cost::Base(opcode_cost.opcode_i32ges), + Operator::I32GeU { .. } => Cost::Base(opcode_cost.opcode_i32geu), + Operator::I32GtS { .. } => Cost::Base(opcode_cost.opcode_i32gts), + Operator::I32GtU { .. } => Cost::Base(opcode_cost.opcode_i32gtu), + Operator::I32LeS { .. } => Cost::Base(opcode_cost.opcode_i32les), + Operator::I32LeU { .. } => Cost::Base(opcode_cost.opcode_i32leu), + Operator::I32Load { .. } => Cost::Base(opcode_cost.opcode_i32load), + Operator::I32Load16S { .. } => Cost::Base(opcode_cost.opcode_i32load16s), + Operator::I32Load16U { .. } => Cost::Base(opcode_cost.opcode_i32load16u), + Operator::I32Load8S { .. } => Cost::Base(opcode_cost.opcode_i32load8s), + Operator::I32Load8U { .. } => Cost::Base(opcode_cost.opcode_i32load8u), + Operator::I32LtS { .. } => Cost::Base(opcode_cost.opcode_i32lts), + Operator::I32LtU { .. } => Cost::Base(opcode_cost.opcode_i32ltu), + Operator::I32Mul { .. } => Cost::Base(opcode_cost.opcode_i32mul), + Operator::I32Ne { .. } => Cost::Base(opcode_cost.opcode_i32ne), + Operator::I32Or { .. } => Cost::Base(opcode_cost.opcode_i32or), + Operator::I32Popcnt { .. } => Cost::Base(opcode_cost.opcode_i32popcnt), + Operator::I32RemS { .. } => Cost::Base(opcode_cost.opcode_i32rems), + Operator::I32RemU { .. } => Cost::Base(opcode_cost.opcode_i32remu), + Operator::I32Rotl { .. } => Cost::Base(opcode_cost.opcode_i32rotl), + Operator::I32Rotr { .. } => Cost::Base(opcode_cost.opcode_i32rotr), + Operator::I32Shl { .. } => Cost::Base(opcode_cost.opcode_i32shl), + Operator::I32ShrS { .. } => Cost::Base(opcode_cost.opcode_i32shrs), + Operator::I32ShrU { .. } => Cost::Base(opcode_cost.opcode_i32shru), + Operator::I32Store { .. } => Cost::Base(opcode_cost.opcode_i32store), + Operator::I32Store16 { .. } => Cost::Base(opcode_cost.opcode_i32store16), + Operator::I32Store8 { .. } => Cost::Base(opcode_cost.opcode_i32store8), + Operator::I32Sub { .. } => Cost::Base(opcode_cost.opcode_i32sub), + Operator::I32WrapI64 { .. } => Cost::Base(opcode_cost.opcode_i32wrapi64), + Operator::I32Xor { .. } => Cost::Base(opcode_cost.opcode_i32xor), + Operator::I64Add { .. } => Cost::Base(opcode_cost.opcode_i64add), + Operator::I64And { .. } => Cost::Base(opcode_cost.opcode_i64and), + Operator::I64Clz { .. } => Cost::Base(opcode_cost.opcode_i64clz), + Operator::I64Const { .. } => Cost::Base(opcode_cost.opcode_i64const), + Operator::I64Ctz { .. } => Cost::Base(opcode_cost.opcode_i64ctz), + Operator::I64DivS { .. } => Cost::Base(opcode_cost.opcode_i64divs), + Operator::I64DivU { .. } => Cost::Base(opcode_cost.opcode_i64divu), + Operator::I64Eq { .. } => Cost::Base(opcode_cost.opcode_i64eq), + Operator::I64Eqz { .. } => Cost::Base(opcode_cost.opcode_i64eqz), + Operator::I64Extend16S { .. } => Cost::Base(opcode_cost.opcode_i64extend16s), + Operator::I64Extend32S { .. } => Cost::Base(opcode_cost.opcode_i64extend32s), + Operator::I64Extend8S { .. } => Cost::Base(opcode_cost.opcode_i64extend8s), + Operator::I64ExtendI32S { .. } => Cost::Base(opcode_cost.opcode_i64extendi32s), + Operator::I64ExtendI32U { .. } => Cost::Base(opcode_cost.opcode_i64extendi32u), + Operator::I64GeS { .. } => Cost::Base(opcode_cost.opcode_i64ges), + Operator::I64GeU { .. } => Cost::Base(opcode_cost.opcode_i64geu), + Operator::I64GtS { .. } => Cost::Base(opcode_cost.opcode_i64gts), + Operator::I64GtU { .. } => Cost::Base(opcode_cost.opcode_i64gtu), + Operator::I64LeS { .. } => Cost::Base(opcode_cost.opcode_i64les), + Operator::I64LeU { .. } => Cost::Base(opcode_cost.opcode_i64leu), + Operator::I64Load { .. } => Cost::Base(opcode_cost.opcode_i64load), + Operator::I64Load16S { .. } => Cost::Base(opcode_cost.opcode_i64load16s), + Operator::I64Load16U { .. } => Cost::Base(opcode_cost.opcode_i64load16u), + Operator::I64Load32S { .. } => Cost::Base(opcode_cost.opcode_i64load32s), + Operator::I64Load32U { .. } => Cost::Base(opcode_cost.opcode_i64load32u), + Operator::I64Load8S { .. } => Cost::Base(opcode_cost.opcode_i64load8s), + Operator::I64Load8U { .. } => Cost::Base(opcode_cost.opcode_i64load8u), + Operator::I64LtS { .. } => Cost::Base(opcode_cost.opcode_i64lts), + Operator::I64LtU { .. } => Cost::Base(opcode_cost.opcode_i64ltu), + Operator::I64Mul { .. } => Cost::Base(opcode_cost.opcode_i64mul), + Operator::I64Ne { .. } => Cost::Base(opcode_cost.opcode_i64ne), + Operator::I64Or { .. } => Cost::Base(opcode_cost.opcode_i64or), + Operator::I64Popcnt { .. } => Cost::Base(opcode_cost.opcode_i64popcnt), + Operator::I64RemS { .. } => Cost::Base(opcode_cost.opcode_i64rems), + Operator::I64RemU { .. } => Cost::Base(opcode_cost.opcode_i64remu), + Operator::I64Rotl { .. } => Cost::Base(opcode_cost.opcode_i64rotl), + Operator::I64Rotr { .. } => Cost::Base(opcode_cost.opcode_i64rotr), + Operator::I64Shl { .. } => Cost::Base(opcode_cost.opcode_i64shl), + Operator::I64ShrS { .. } => Cost::Base(opcode_cost.opcode_i64shrs), + Operator::I64ShrU { .. } => Cost::Base(opcode_cost.opcode_i64shru), + Operator::I64Store { .. } => Cost::Base(opcode_cost.opcode_i64store), + Operator::I64Store16 { .. } => Cost::Base(opcode_cost.opcode_i64store16), + Operator::I64Store32 { .. } => Cost::Base(opcode_cost.opcode_i64store32), + Operator::I64Store8 { .. } => Cost::Base(opcode_cost.opcode_i64store8), + Operator::I64Sub { .. } => Cost::Base(opcode_cost.opcode_i64sub), + Operator::I64Xor { .. } => Cost::Base(opcode_cost.opcode_i64xor), + Operator::If { .. } => Cost::Base(opcode_cost.opcode_if), + Operator::LocalGet { .. } => Cost::Base(opcode_cost.opcode_localget), + Operator::LocalSet { .. } => Cost::Base(opcode_cost.opcode_localset), + Operator::LocalTee { .. } => Cost::Base(opcode_cost.opcode_localtee), + Operator::Loop { .. } => Cost::Base(opcode_cost.opcode_loop), + Operator::MemoryGrow { .. } => Cost::Base(opcode_cost.opcode_memorygrow), + Operator::MemorySize { .. } => Cost::Base(opcode_cost.opcode_memorysize), + Operator::Nop { .. } => Cost::Base(opcode_cost.opcode_nop), + Operator::Return { .. } => Cost::Base(opcode_cost.opcode_return), + Operator::ReturnCall { .. } => Cost::Base(opcode_cost.opcode_returncall), + Operator::ReturnCallIndirect { .. } => Cost::Base(opcode_cost.opcode_returncallindirect), + Operator::Select { .. } => Cost::Base(opcode_cost.opcode_select), + Operator::Unreachable { .. } => Cost::Base(opcode_cost.opcode_unreachable), + Operator::MemoryCopy { .. } => Cost::BulkMemory { base: opcode_cost.opcode_memorycopy, per_byte: opcode_cost.opcode_memorycopyperbyte }, + Operator::MemoryFill { .. } => Cost::BulkMemory { base: opcode_cost.opcode_memoryfill, per_byte: opcode_cost.opcode_memoryfillperbyte }, + _ => Cost::Illegal, + } +} diff --git a/vm-executor-wasmer/src/wasmer_opcode_cost_type.rs b/vm-executor-wasmer/src/wasmer_opcode_cost_type.rs new file mode 100644 index 0000000..124c276 --- /dev/null +++ b/vm-executor-wasmer/src/wasmer_opcode_cost_type.rs @@ -0,0 +1,20 @@ +/// What executing one WASM operator costs. +/// +/// Not to be confused with `OpcodeCost`, which is the full cost table, as configured by the +/// gas schedule. This is the cost of one occurrence of one operator. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum Cost { + /// The operator is not allowed by the configured opcode version, so it has no cost. + /// + /// Modules that contain it are rejected at compile time. + Illegal, + + /// The operator is charged once, no matter what its operands are. + Base(u32), + + /// A bulk memory operator (`memory.copy`, `memory.fill`), charged once for the operation + /// itself, plus once for every byte it copies or fills. + /// + /// The base cost is what keeps processing zero bytes from being free. + BulkMemory { base: u32, per_byte: u32 }, +} diff --git a/vm-executor-wasmer/tests/bulk-memory.wat b/vm-executor-wasmer/tests/bulk-memory.wat new file mode 100644 index 0000000..f5997d2 --- /dev/null +++ b/vm-executor-wasmer/tests/bulk-memory.wat @@ -0,0 +1,48 @@ +(module + (memory $mem 1) + (func $copyZero (type $void) + (memory.copy + (i32.const 0) ;; destination address + (i32.const 64) ;; source address + (i32.const 0) ;; size of memory region in bytes + ) + ) + (func $copyTen (type $void) + (memory.copy + (i32.const 0) ;; destination address + (i32.const 64) ;; source address + (i32.const 10) ;; size of memory region in bytes + ) + ) + (func $fillZero (type $void) + (memory.fill + (i32.const 0) ;; destination address + (i32.const 7) ;; value to fill with + (i32.const 0) ;; size of memory region in bytes + ) + ) + (func $fillTen (type $void) + (memory.fill + (i32.const 0) ;; destination address + (i32.const 7) ;; value to fill with + (i32.const 10) ;; size of memory region in bytes + ) + ) + ;; same as $copyTen, but with the size coming from a local instead of an + ;; immediate constant, to check that the metering injection gives it back unchanged + (func $copyLocalTen (type $void) (local $size i32) + (local.set $size (i32.const 10)) + (memory.copy + (i32.const 0) ;; destination address + (i32.const 64) ;; source address + (local.get $size) ;; size of memory region in bytes + ) + ) + (type $void (func)) + (export "memory" (memory $mem)) + (export "copyZero" (func $copyZero)) + (export "copyTen" (func $copyTen)) + (export "fillZero" (func $fillZero)) + (export "fillTen" (func $fillTen)) + (export "copyLocalTen" (func $copyLocalTen)) +) diff --git a/vm-executor-wasmer/tests/bulk_memory_metering_test.rs b/vm-executor-wasmer/tests/bulk_memory_metering_test.rs new file mode 100644 index 0000000..93a0dec --- /dev/null +++ b/vm-executor-wasmer/tests/bulk_memory_metering_test.rs @@ -0,0 +1,92 @@ +use multiversx_chain_vm_executor::{ + CompilationOptionsLegacy, ExecutorLegacy, InstanceLegacy, OpcodeConfig, OpcodeCost, + OpcodeVersion, VMHooksLegacyDefault, +}; +use multiversx_chain_vm_executor_wasmer::WasmerExecutor; +use wasmer::wat2wasm; + +const BULK_MEMORY_WAT: &[u8] = include_bytes!("bulk-memory.wat"); + +const MEMORY_COPY_COST: u32 = 300; +const MEMORY_COPY_PER_BYTE_COST: u32 = 7; +const MEMORY_FILL_COST: u32 = 400; +const MEMORY_FILL_PER_BYTE_COST: u32 = 11; + +const COMPILATION_OPTIONS: CompilationOptionsLegacy = CompilationOptionsLegacy { + gas_limit: 1_000_000, + unmetered_locals: 0, + max_memory_grow: 0, + max_memory_grow_delta: 0, + opcode_trace: false, + metering: true, + runtime_breakpoints: true, +}; + +/// All opcodes are free, except the bulk memory ones, so that `points_used` after a call +/// is exactly the cost of the single `memory.copy`/`memory.fill` in the called function. +fn bulk_memory_instance() -> Box { + let wasm_bytes = wat2wasm(BULK_MEMORY_WAT).unwrap(); + + let mut executor = WasmerExecutor::new(Box::new(VMHooksLegacyDefault)); + executor + .set_opcode_config(OpcodeConfig { + opcode_version: OpcodeVersion::V2, + opcode_cost: OpcodeCost { + opcode_memorycopy: MEMORY_COPY_COST, + opcode_memorycopyperbyte: MEMORY_COPY_PER_BYTE_COST, + opcode_memoryfill: MEMORY_FILL_COST, + opcode_memoryfillperbyte: MEMORY_FILL_PER_BYTE_COST, + ..Default::default() + }, + }) + .unwrap(); + + executor + .new_instance(&wasm_bytes, &COMPILATION_OPTIONS) + .unwrap() +} + +fn points_used_by(instance: &dyn InstanceLegacy, func_name: &str) -> u64 { + instance.set_points_used(0).unwrap(); + instance.call(func_name).unwrap(); + instance.get_points_used().unwrap() +} + +#[test] +fn bulk_memory_zero_bytes_still_costs_the_base() { + let instance = bulk_memory_instance(); + + assert_eq!( + points_used_by(instance.as_ref(), "copyZero"), + MEMORY_COPY_COST as u64 + ); + assert_eq!( + points_used_by(instance.as_ref(), "fillZero"), + MEMORY_FILL_COST as u64 + ); +} + +#[test] +fn bulk_memory_bytes_cost_on_top_of_the_base() { + let instance = bulk_memory_instance(); + + assert_eq!( + points_used_by(instance.as_ref(), "copyTen"), + (MEMORY_COPY_COST + 10 * MEMORY_COPY_PER_BYTE_COST) as u64 + ); + assert_eq!( + points_used_by(instance.as_ref(), "fillTen"), + (MEMORY_FILL_COST + 10 * MEMORY_FILL_PER_BYTE_COST) as u64 + ); +} + +/// The injected metering code takes the size operand off the stack and puts it back. +#[test] +fn bulk_memory_size_operand_survives_the_injection() { + let instance = bulk_memory_instance(); + + assert_eq!( + points_used_by(instance.as_ref(), "copyLocalTen"), + points_used_by(instance.as_ref(), "copyTen") + ); +} diff --git a/vm-executor-wasmer/tests/memory-copy.wat b/vm-executor-wasmer/tests/memory-copy.wat new file mode 100644 index 0000000..917b4ac --- /dev/null +++ b/vm-executor-wasmer/tests/memory-copy.wat @@ -0,0 +1,18 @@ +(module + (type $void (func)) + (type $finish(func (param i64))) + (import "env" "int64finish" (func $int64finish (type $finish))) + (func $main (type $void) + (memory.copy + (i32.const 42) ;; destination address + (i32.const 1024) ;; source address + (i32.const 2) ;; size of memory region in bytes + ) + (i64.const 0) + (call $int64finish) + ) + (memory $mem 1) + (export "memory" (memory $mem)) + (export "main" (func $main)) + (data (i32.const 1024) "ok") +) diff --git a/vm-executor-wasmer/tests/opcode_version_test.rs b/vm-executor-wasmer/tests/opcode_version_test.rs new file mode 100644 index 0000000..c9b453e --- /dev/null +++ b/vm-executor-wasmer/tests/opcode_version_test.rs @@ -0,0 +1,34 @@ +use multiversx_chain_vm_executor::{ + CompilationOptions, ExecutorLegacy, OpcodeConfig, OpcodeCost, OpcodeVersion, + VMHooksLegacyDefault, +}; +use multiversx_chain_vm_executor_wasmer::WasmerExecutor; +use wasmer::wat2wasm; + +const MEMORY_COPY_WAT: &[u8] = include_bytes!("memory-copy.wat"); + +const DUMMY_COMPILATION_OPTIONS: CompilationOptions = CompilationOptions { + unmetered_locals: 0, + max_memory_grow: 0, + max_memory_grow_delta: 0, + opcode_trace: false, +}; + +#[test] +fn opcode_version_test() { + let wasm_bytes = wat2wasm(MEMORY_COPY_WAT).unwrap(); + + let mut executor = WasmerExecutor::new(Box::new(VMHooksLegacyDefault)); + let result = executor.new_instance(&wasm_bytes, &DUMMY_COMPILATION_OPTIONS.to_legacy()); + assert!(result.is_err()); + + executor + .set_opcode_config(OpcodeConfig { + opcode_version: OpcodeVersion::V2, + opcode_cost: OpcodeCost::default(), + }) + .unwrap(); + + let result = executor.new_instance(&wasm_bytes, &DUMMY_COMPILATION_OPTIONS.to_legacy()); + assert!(result.is_ok()); +} diff --git a/vm-executor/src/breakpoint_value.rs b/vm-executor/src/breakpoint_value.rs index 58af35f..4dcfa4c 100644 --- a/vm-executor/src/breakpoint_value.rs +++ b/vm-executor/src/breakpoint_value.rs @@ -1,9 +1,8 @@ - /// Represents the reason why execution of a WASM instance was interrupted. /// /// Used by the legacy executor interface to communicate breakpoint causes /// between the WASM runtime and the high-level VM. -/// +/// /// TODO: move to wasmer-prod, and hide it from the VM implementation. #[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum BreakpointValueLegacy { diff --git a/vm-executor/src/executor.rs b/vm-executor/src/executor.rs index c2e22f0..0fb094e 100644 --- a/vm-executor/src/executor.rs +++ b/vm-executor/src/executor.rs @@ -1,4 +1,4 @@ -use crate::{CompilationOptionsLegacy, ExecutorError, InstanceLegacy, OpcodeCost}; +use crate::{CompilationOptionsLegacy, ExecutorError, InstanceLegacy, OpcodeConfig}; use std::ffi::c_void; @@ -6,8 +6,8 @@ pub trait ExecutorLegacy { /// Sets the data that can be hold by an instance context. fn set_vm_hooks_ptr(&mut self, vm_hooks_ptr: *mut c_void) -> Result<(), ExecutorError>; - /// Sets the opcode costs for the given executor. - fn set_opcode_cost(&mut self, opcode_cost: &OpcodeCost) -> Result<(), ExecutorError>; + /// Sets the opcode version and costs for the given executor. + fn set_opcode_config(&mut self, opcode_config: OpcodeConfig) -> Result<(), ExecutorError>; /// Creates a new VM executor instance. fn new_instance( diff --git a/vm-executor/src/lib.rs b/vm-executor/src/lib.rs index 95f9a35..070d82d 100644 --- a/vm-executor/src/lib.rs +++ b/vm-executor/src/lib.rs @@ -4,7 +4,9 @@ mod executor; mod instance; mod missing_wasm; mod new_traits; +mod opcode_config; mod opcode_cost; +mod opcode_version; mod service_error; mod service_trait; mod vm_hooks; @@ -15,7 +17,9 @@ pub use executor::ExecutorLegacy; pub use instance::InstanceLegacy; pub use missing_wasm::{MissingWasmError, check_missing_wasm}; pub use new_traits::*; +pub use opcode_config::OpcodeConfig; pub use opcode_cost::OpcodeCost; +pub use opcode_version::OpcodeVersion; pub use service_error::ServiceError; pub use service_trait::*; pub use vm_hooks::{VMHooksLegacy, VMHooksLegacyDefault}; diff --git a/vm-executor/src/missing_wasm.rs b/vm-executor/src/missing_wasm.rs index 40e083d..3592a83 100644 --- a/vm-executor/src/missing_wasm.rs +++ b/vm-executor/src/missing_wasm.rs @@ -20,7 +20,7 @@ pub struct MissingWasmError(String); impl fmt::Display for MissingWasmError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "Contract {}", &self.0) + write!(f, "Contract {}", self.0) } } diff --git a/vm-executor/src/opcode_config.rs b/vm-executor/src/opcode_config.rs new file mode 100644 index 0000000..c899741 --- /dev/null +++ b/vm-executor/src/opcode_config.rs @@ -0,0 +1,16 @@ +use crate::{OpcodeCost, OpcodeVersion}; + +#[derive(Debug, Clone)] +pub struct OpcodeConfig { + pub opcode_version: OpcodeVersion, + pub opcode_cost: OpcodeCost, +} + +impl Default for OpcodeConfig { + fn default() -> Self { + Self { + opcode_version: OpcodeVersion::V1, + opcode_cost: OpcodeCost::default(), + } + } +} diff --git a/vm-executor/src/opcode_cost.rs b/vm-executor/src/opcode_cost.rs index 9c299c8..342ca0e 100644 --- a/vm-executor/src/opcode_cost.rs +++ b/vm-executor/src/opcode_cost.rs @@ -219,8 +219,6 @@ pub struct OpcodeCost { pub opcode_localset: u32, #[serde(rename = "LocalTee", default)] pub opcode_localtee: u32, - #[serde(rename = "LocalAllocate", default)] - pub opcode_localallocate: u32, #[serde(rename = "Loop", default)] pub opcode_loop: u32, #[serde(rename = "MemoryGrow", default)] @@ -265,4 +263,14 @@ pub struct OpcodeCost { pub opcode_unreachable: u32, #[serde(rename = "Unwind", default)] pub opcode_unwind: u32, + #[serde(rename = "MemoryCopy", default)] + pub opcode_memorycopy: u32, + #[serde(rename = "MemoryFill", default)] + pub opcode_memoryfill: u32, + #[serde(rename = "MemoryCopyPerByte", default)] + pub opcode_memorycopyperbyte: u32, + #[serde(rename = "MemoryFillPerByte", default)] + pub opcode_memoryfillperbyte: u32, + #[serde(rename = "LocalAllocate", default)] + pub opcode_localallocate: u32, } diff --git a/vm-executor/src/opcode_version.rs b/vm-executor/src/opcode_version.rs new file mode 100644 index 0000000..fe3464f --- /dev/null +++ b/vm-executor/src/opcode_version.rs @@ -0,0 +1,57 @@ +/// Specifies which set of opcodes should be used by the VM. +/// +/// `V1` is the legacy opcode set that does **not** include support for bulk +/// memory operations. `V2` extends `V1` by adding bulk memory instructions, +/// such as `MemoryCopy` and `MemoryFill`, while dropping these categories +/// from the whitelist, since no contract built with the current SDK +/// toolchain can emit any of them: +/// +/// * legacy exception handling: `Catch`, `CatchAll`, `Delegate`, `Rethrow`, +/// `Throw`, `Try` +/// * reference types: `RefFunc`, `RefIsNull`, `RefNull`, `TypedSelect` +/// * table manipulation: `TableGet`, `TableGrow`, `TableInit`, `TableSet`, +/// `TableSize` +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum OpcodeVersion { + /// Legacy opcode set without bulk memory support. + /// + /// Use this for modules or environments that were compiled or designed + /// before bulk memory operations were introduced, or when compatibility + /// with older tooling is required. + V1, + /// Opcode set with bulk memory support. + /// + /// Use this for modules that rely on bulk memory operations like + /// `MemoryCopy` and `MemoryFill`, or when targeting newer runtimes that + /// support these instructions. + /// + /// Relative to `V1`, `V2` also drops these categories from the + /// whitelist, since no contract built with today's tooling can produce + /// them: + /// + /// * legacy exception handling: `Catch`, `CatchAll`, `Delegate`, + /// `Rethrow`, `Throw`, `Try` + /// * reference types: `RefFunc`, `RefIsNull`, `RefNull`, `TypedSelect` + /// * table manipulation: `TableGet`, `TableGrow`, `TableInit`, + /// `TableSet`, `TableSize` + /// + /// Note: `V2` does **not** add support for `memory.init` and `data.drop`. + V2, +} +impl OpcodeVersion { + /// Converts a numeric opcode version identifier into an `OpcodeVersion`. + /// + /// * `0` maps to [`OpcodeVersion::V1`] (legacy, no bulk memory support). + /// * `1` maps to [`OpcodeVersion::V2`] (with bulk memory operations). + /// + /// Any other value returns `None`. This is typically used when decoding + /// opcode version information from serialized formats or external + /// configuration that represent the version as an integer. + pub fn from_i32(value: i32) -> Option { + match value { + 0 => Some(OpcodeVersion::V1), + 1 => Some(OpcodeVersion::V2), + _ => None, + } + } +} diff --git a/vm-executor/src/service_error.rs b/vm-executor/src/service_error.rs index 576f4e2..3076320 100644 --- a/vm-executor/src/service_error.rs +++ b/vm-executor/src/service_error.rs @@ -14,7 +14,7 @@ impl ServiceError { impl Display for ServiceError { fn fmt(&self, f: &mut Formatter) -> fmt::Result { - write!(f, "{}", &self.message) + write!(f, "{}", self.message) } }