├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README.md ├── benchmarks ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── bench.rs ├── fibonacci-program ├── greeting-elf ├── keccak-elf ├── lib.rs ├── rust-toolchain └── test.rs ├── docs ├── SUMMARY.md ├── encoding │ ├── instruction.md │ └── module.md └── instructions │ ├── 00_unreachable.md │ ├── 01_local_get.md │ ├── 02_local_set.md │ ├── 03_local_tee.md │ ├── 04_br.md │ ├── 05_br_if.md │ ├── 06_br_if_nez.md │ ├── 10_consume_fuel.md │ ├── 11_return.md │ ├── 12_return_if_nez.md │ ├── 13_return_call_internal.md │ ├── 14_return_call.md │ ├── 15_return_call_indirect.md │ ├── 16_call_internal.md │ ├── 17_call.md │ ├── 18_call_indirect.md │ ├── 19_signature_check.md │ ├── 20_drop.md │ ├── 21_select.md │ ├── 22_global_get.md │ ├── 23_global_set.md │ ├── 24_i32_load.md │ ├── 47_memory_size.md │ ├── 48_memory_grow.md │ ├── 49_memory_fill.md │ ├── 50_memory_copy.md │ ├── 51_memory_init.md │ ├── 52_data_drop.md │ ├── 53_table_size.md │ ├── 54_table_grow.md │ ├── 55_table_fill.md │ ├── 56_table_get.md │ ├── 57_table_set.md │ ├── 58_table_copy.md │ ├── 59_table_init.md │ ├── 60_element_drop.md │ ├── 61_ref_func.md │ └── 62_i32_const.md ├── e2e ├── Cargo.toml ├── README.md └── src │ ├── context.rs │ ├── descriptor.rs │ ├── error.rs │ ├── handler.rs │ ├── lib.rs │ ├── local │ └── missing-features │ │ ├── mutable-global-disabled.wast │ │ ├── saturating-float-to-int-disabled.wast │ │ └── sign-extension-disabled.wast │ ├── profile.rs │ └── run.rs ├── legacy ├── Cargo.toml ├── README.md └── src │ ├── arena │ ├── component_vec.rs │ ├── dedup.rs │ ├── guarded.rs │ ├── mod.rs │ └── tests.rs │ ├── core │ ├── host_error.rs │ ├── import_linker.rs │ ├── mod.rs │ ├── nan_preserving_float.rs │ ├── rwasm.rs │ ├── trap.rs │ ├── units.rs │ ├── untyped.rs │ └── value.rs │ ├── engine │ ├── bytecode │ │ ├── instr_meta.rs │ │ ├── mod.rs │ │ ├── stack_height.rs │ │ ├── tests.rs │ │ └── utils.rs │ ├── cache.rs │ ├── code_map.rs │ ├── config.rs │ ├── const_pool.rs │ ├── executor.rs │ ├── func_args.rs │ ├── func_builder │ │ ├── control_frame.rs │ │ ├── control_stack.rs │ │ ├── error.rs │ │ ├── inst_builder.rs │ │ ├── labels.rs │ │ ├── locals_registry.rs │ │ ├── mod.rs │ │ ├── translator.rs │ │ ├── translator_i32.rs │ │ └── value_stack.rs │ ├── func_types.rs │ ├── mod.rs │ ├── resumable.rs │ ├── stack │ │ ├── frames.rs │ │ ├── mod.rs │ │ └── values │ │ │ ├── mod.rs │ │ │ ├── sp.rs │ │ │ └── tests.rs │ ├── tests.rs │ ├── tracer.rs │ └── traits.rs │ ├── error.rs │ ├── externref.rs │ ├── foreach_tuple.rs │ ├── func │ ├── caller.rs │ ├── error.rs │ ├── func_type.rs │ ├── funcref.rs │ ├── into_func.rs │ ├── mod.rs │ └── typed_func.rs │ ├── global.rs │ ├── instance │ ├── builder.rs │ ├── exports.rs │ └── mod.rs │ ├── lib.rs │ ├── limits.rs │ ├── linker.rs │ ├── memory │ ├── buffer.rs │ ├── data.rs │ ├── error.rs │ ├── mod.rs │ └── tests.rs │ ├── module │ ├── builder.rs │ ├── compile │ │ ├── block_type.rs │ │ └── mod.rs │ ├── custom_section.rs │ ├── data.rs │ ├── element.rs │ ├── error.rs │ ├── export.rs │ ├── global.rs │ ├── import.rs │ ├── init_expr.rs │ ├── instantiate │ │ ├── error.rs │ │ ├── mod.rs │ │ ├── pre.rs │ │ └── tests.rs │ ├── mod.rs │ ├── parser.rs │ ├── read.rs │ └── utils.rs │ ├── reftype.rs │ ├── rwasm │ ├── binary_format │ │ ├── drop_keep.rs │ │ ├── instruction.rs │ │ ├── instruction_set.rs │ │ ├── mod.rs │ │ ├── module.rs │ │ ├── number.rs │ │ ├── reader_writer.rs │ │ └── utils.rs │ ├── drop_keep.rs │ ├── instruction_set.rs │ ├── mod.rs │ ├── reduced_module.rs │ ├── segment_builder.rs │ ├── tests.rs │ ├── translator.rs │ └── types.rs │ ├── store.rs │ ├── table │ ├── element.rs │ ├── error.rs │ ├── mod.rs │ └── tests.rs │ └── value.rs ├── rust-toolchain ├── snippets ├── Cargo.toml ├── Makefile ├── extractor.rs ├── lib.rs ├── lib.wasm ├── lib.wat ├── rwasm.rs └── test.rs └── src ├── compiler.rs ├── lib.rs ├── types ├── address_offset.rs ├── block_fuel.rs ├── branch_offset.rs ├── drop_keep.rs ├── error.rs ├── fuel_costs.rs ├── func_ref.rs ├── host_error.rs ├── import_linker.rs ├── import_name.rs ├── instruction.rs ├── instruction_set.rs ├── mod.rs ├── module.rs ├── nan_preserving_float.rs ├── units.rs ├── untyped_value.rs ├── utils.rs └── value.rs └── vm ├── config.rs ├── context.rs ├── data_entity.rs ├── executor.rs ├── handler.rs ├── instr_ptr.rs ├── instruction_table.rs ├── memory.rs ├── mod.rs ├── opcodes.rs ├── rw_op.rs ├── table_entity.rs ├── tracer.rs └── value_stack.rs /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | [*] 3 | indent_style=space 4 | indent_size = 4 5 | end_of_line=lf 6 | charset=utf-8 7 | trim_trailing_whitespace=true 8 | max_line_length=120 9 | insert_final_newline=true 10 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: '/' 5 | schedule: 6 | interval: daily 7 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Rust CI 2 | 3 | on: 4 | push: 5 | branches: [ main, devel ] 6 | paths-ignore: 7 | - "**.md" 8 | - "docs/**" 9 | pull_request: 10 | branches: [ main, devel ] 11 | paths-ignore: 12 | - "**.md" 13 | - "docs/**" 14 | workflow_dispatch: 15 | 16 | env: 17 | CARGO_TERM_COLOR: always 18 | RUST_BACKTRACE: 1 19 | 20 | jobs: 21 | test: 22 | name: Test 23 | runs-on: ubuntu-latest 24 | timeout-minutes: 30 25 | steps: 26 | - uses: actions/checkout@v4 27 | with: 28 | submodules: "recursive" 29 | 30 | - name: Install Rust toolchain 31 | uses: dtolnay/rust-toolchain@master 32 | with: 33 | toolchain: stable 34 | targets: wasm32-unknown-unknown 35 | 36 | - name: Setup cache 37 | uses: Swatinem/rust-cache@v2 38 | 39 | - name: Run tests 40 | run: | 41 | cargo test --no-fail-fast 42 | cargo test --no-fail-fast --manifest-path=./e2e/Cargo.toml 43 | 44 | lint: 45 | name: Lint 46 | runs-on: ubuntu-latest 47 | if: github.event_name == 'workflow_dispatch' 48 | steps: 49 | - uses: actions/checkout@v4 50 | 51 | - name: Install Rust toolchain 52 | uses: dtolnay/rust-toolchain@master 53 | with: 54 | toolchain: stable 55 | components: rustfmt, clippy 56 | targets: wasm32-unknown-unknown 57 | 58 | - name: Check formatting 59 | run: cargo fmt --all -- --check 60 | continue-on-error: true 61 | 62 | - name: Run clippy 63 | run: cargo clippy --all-targets --all-features -- -D warnings 64 | continue-on-error: true -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /target/ 2 | **/*.rs.bk 3 | legacy/Cargo.lock 4 | legacy/spec/target 5 | 6 | **/fuzz/corpus/ 7 | **/fuzz/target/ 8 | **/fuzz/artifacts/ 9 | 10 | /.idea 11 | .vscode 12 | .DS_Store 13 | 14 | /tmp/* 15 | !/tmp/.gitkeep 16 | 17 | target 18 | Cargo.lock -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "e2e/testsuite"] 2 | path = e2e/testsuite 3 | url = https://github.com/fluentlabs-xyz/testsuite-rwasm.git -------------------------------------------------------------------------------- /.rustfmt.toml: -------------------------------------------------------------------------------- 1 | imports_granularity = "Crate" 2 | group_imports = "One" 3 | imports_layout = "HorizontalVertical" 4 | edition = "2021" 5 | 6 | comment_width = 100 7 | max_width = 100 8 | newline_style = "Unix" 9 | #normalize_comments = true 10 | reorder_imports = true 11 | wrap_comments = true 12 | unstable_features = true 13 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwasm" 3 | edition = "2021" 4 | authors = [ 5 | "Dmitry Savonin ", 6 | ] 7 | repository = "https://github.com/fluentlabs-xyz/rwasm" 8 | readme = "README.md" 9 | license = "MIT" 10 | keywords = ["wasm", "webassembly", "interpreter", "vm"] 11 | categories = ["wasm", "no-std", "virtualization"] 12 | 13 | [dependencies] 14 | wasmparser = { version = "0.100.1", package = "wasmparser-nostd", default-features = false } 15 | hashbrown = { version = "0.15.2", features = ["alloc"] } 16 | tiny-keccak = { version = "2.0.2", features = ["keccak"] } 17 | paste = { version = "1.0", default-features = false } 18 | num_enum = { version = "0.7.3", default-features = false } 19 | bytes = { version = "1.10.1", default-features = false } 20 | downcast-rs = { version = "2.0.1", default-features = false, features = ["sync"] } 21 | bincode = { version = "2.0.1", default-features = false, features = ["alloc", "derive"] } 22 | num-traits = { version = "0.2", default-features = false } 23 | bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } 24 | libm = "0.2.1" 25 | 26 | # an rwasm legacy dependency 27 | rwasm-legacy = { path = "./legacy", default-features = false } 28 | 29 | [dev-dependencies] 30 | rand = "0.9.1" 31 | 32 | [features] 33 | default = ["std"] 34 | std = [ 35 | "num_enum/std", 36 | "bytes/std", 37 | "bincode/std", 38 | "num-traits/std", 39 | "bitvec/std", 40 | "rwasm-legacy/std", 41 | ] 42 | more-max-pages = [] 43 | debug-print = [] 44 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: test-specific-cases 2 | 3 | .PHONY: test-specific-cases 4 | test-specific-cases: 5 | cargo test --color=always --no-fail-fast --manifest-path rwasm/Cargo.toml 6 | cargo test --color=always --no-fail-fast --manifest-path e2e/Cargo.toml 7 | -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | lib.wat 4 | lib.wasm -------------------------------------------------------------------------------- /benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "benchmark-fib" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | path = "lib.rs" 9 | 10 | [profile.release] 11 | panic = "abort" 12 | lto = true 13 | opt-level = 3 14 | strip = true 15 | debug = false 16 | debug-assertions = false 17 | rpath = false 18 | codegen-units = 1 19 | 20 | [dependencies] 21 | 22 | [dev-dependencies] 23 | rwasm = { path = ".." } 24 | wasmi = { version = "=0.31.2" } 25 | sp1-core-executor = "4.2.0" 26 | -------------------------------------------------------------------------------- /benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | RUSTFLAGS="-C link-arg=-zstack-size=1024" 2 | 3 | build: 4 | RUSTFLAGS="-C link-arg=-zstack-size=0" cargo b --target=wasm32-unknown-unknown --release --no-default-features 5 | cp ./target/wasm32-unknown-unknown/release/fib.wasm ./lib.wasm 6 | wasm2wat ./lib.wasm > ./lib.wat -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | These benchmarks are not finalized yet. 2 | Here we test the cost of running SP1's RISC-V, Wasmi & rWasm in Ethereum execution environment. 3 | When it's required to load the account from storage, only raw bytes. 4 | 5 | Current results: 6 | 7 | | Test | Native | Risc-V | Wasmi | rWasm | 8 | |---------------|--------|--------|-------|-------| 9 | | Fibonacci(43) | ~12ns | ~7ms | ~6us | ~2us | 10 | 11 | PS: 12 | 13 | 1. Risc-V is slow because of tracing 14 | 2. Wasmi is slow because of Wasm validation rules 15 | 16 | Tested on Apple M3 MAX -------------------------------------------------------------------------------- /benchmarks/bench.rs: -------------------------------------------------------------------------------- 1 | extern crate test; 2 | 3 | use rwasm::Caller; 4 | use sp1_core_executor::{ExecutionState, Executor, ExecutorMode, Program}; 5 | use test::Bencher; 6 | 7 | #[bench] 8 | fn bench_wasmi(b: &mut Bencher) { 9 | b.iter(|| { 10 | use wasmi::{Engine, Linker, Module, Store}; 11 | let wasm = include_bytes!("./lib.wasm"); 12 | let engine = Engine::default(); 13 | let module = Module::new(&engine, &wasm[..]).unwrap(); 14 | let mut store = Store::new(&engine, ()); 15 | let linker = >::new(&engine); 16 | let instance = linker 17 | .instantiate(&mut store, &module) 18 | .unwrap() 19 | .start(&mut store) 20 | .unwrap(); 21 | let _result = instance 22 | .get_typed_func::(&store, "main") 23 | .unwrap() 24 | .call(&mut store, 43) 25 | .unwrap(); 26 | // assert_eq!(result, 433494437); 27 | }); 28 | } 29 | 30 | #[bench] 31 | fn bench_rwasm(b: &mut Bencher) { 32 | use rwasm::{ 33 | legacy::{engine::RwasmConfig, Config, Engine, Module}, 34 | ExecutorConfig, 35 | RwasmExecutor, 36 | RwasmModule, 37 | }; 38 | use std::sync::Arc; 39 | 40 | let wasm = include_bytes!("./lib.wasm"); 41 | 42 | let mut config = Config::default(); 43 | config 44 | .wasm_mutable_global(false) 45 | .wasm_saturating_float_to_int(false) 46 | .wasm_sign_extension(false) 47 | .wasm_multi_value(false) 48 | .wasm_mutable_global(true) 49 | .wasm_saturating_float_to_int(true) 50 | .wasm_sign_extension(true) 51 | .wasm_multi_value(true) 52 | .wasm_bulk_memory(true) 53 | .wasm_reference_types(true) 54 | .wasm_tail_call(true) 55 | .wasm_extended_const(true); 56 | config.rwasm_config(RwasmConfig { 57 | state_router: None, 58 | entrypoint_name: Some("main".to_string()), 59 | import_linker: None, 60 | wrap_import_functions: true, 61 | translate_drop_keep: false, 62 | allow_malformed_entrypoint_func_type: true, 63 | use_32bit_mode: false, 64 | builtins_consume_fuel: false, 65 | }); 66 | let engine = Engine::new(&config); 67 | let wasm_module = Module::new(&engine, &wasm[..]).unwrap(); 68 | let rwasm_module = rwasm::legacy::rwasm::RwasmModule::from_module(&wasm_module); 69 | let mut encoded_rwasm_module = Vec::new(); 70 | use rwasm::legacy::rwasm::BinaryFormat; 71 | rwasm_module 72 | .write_binary_to_vec(&mut encoded_rwasm_module) 73 | .unwrap(); 74 | b.iter(|| { 75 | let rwasm_module = RwasmModule::new(&encoded_rwasm_module); 76 | let mut vm = RwasmExecutor::new(Arc::new(rwasm_module), ExecutorConfig::default(), ()); 77 | Caller::new(&mut vm).stack_push(43); 78 | vm.run().unwrap(); 79 | let _result: i32 = Caller::new(&mut vm).stack_pop_as(); 80 | // assert_eq!(result, 433494437); 81 | // vm.reset_pc(); 82 | }); 83 | } 84 | 85 | #[bench] 86 | fn bench_riscv(b: &mut Bencher) { 87 | b.iter(|| { 88 | let elf = include_bytes!("./fibonacci-program"); 89 | let mut executor = Executor::new(Program::from(elf).unwrap(), Default::default()); 90 | executor.executor_mode = ExecutorMode::Trace; 91 | executor.execute().unwrap(); 92 | executor.state = ExecutionState::new(executor.program.pc_start); 93 | }); 94 | } 95 | 96 | #[bench] 97 | fn bench_native(b: &mut Bencher) { 98 | b.iter(|| { 99 | pub fn main(n: i32) -> i32 { 100 | let (mut a, mut b) = (0, 1); 101 | for _ in 0..n { 102 | let temp = a; 103 | a = b; 104 | b = temp + b; 105 | } 106 | a 107 | } 108 | core::hint::black_box(main(core::hint::black_box(43))); 109 | }); 110 | } 111 | -------------------------------------------------------------------------------- /benchmarks/fibonacci-program: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluentlabs-xyz/rwasm/4e3597f78bf038a39601f5e8e266715b5834123f/benchmarks/fibonacci-program -------------------------------------------------------------------------------- /benchmarks/greeting-elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluentlabs-xyz/rwasm/4e3597f78bf038a39601f5e8e266715b5834123f/benchmarks/greeting-elf -------------------------------------------------------------------------------- /benchmarks/keccak-elf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluentlabs-xyz/rwasm/4e3597f78bf038a39601f5e8e266715b5834123f/benchmarks/keccak-elf -------------------------------------------------------------------------------- /benchmarks/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | #[cfg(test)] 4 | mod bench; 5 | #[cfg(test)] 6 | mod test; 7 | 8 | #[cfg(target_arch = "wasm32")] 9 | #[no_mangle] 10 | pub fn main(n: i32) -> i32 { 11 | let (mut a, mut b) = (0, 1); 12 | for _ in 0..n { 13 | let temp = a; 14 | a = b; 15 | b = temp + b; 16 | } 17 | a 18 | } 19 | -------------------------------------------------------------------------------- /benchmarks/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2025-01-27 -------------------------------------------------------------------------------- /benchmarks/test.rs: -------------------------------------------------------------------------------- 1 | use sp1_core_executor::{ExecutionState, Executor, ExecutorMode, Program}; 2 | 3 | #[test] 4 | fn test_rwasm() { 5 | use rwasm::{ 6 | legacy::{engine::RwasmConfig, Config, Engine, Module}, 7 | Caller, 8 | ExecutorConfig, 9 | RwasmExecutor, 10 | RwasmModule, 11 | }; 12 | use std::sync::Arc; 13 | 14 | let wasm = include_bytes!("./lib.wasm"); 15 | 16 | let mut config = Config::default(); 17 | config 18 | .wasm_mutable_global(false) 19 | .wasm_saturating_float_to_int(false) 20 | .wasm_sign_extension(false) 21 | .wasm_multi_value(false) 22 | .wasm_mutable_global(true) 23 | .wasm_saturating_float_to_int(true) 24 | .wasm_sign_extension(true) 25 | .wasm_multi_value(true) 26 | .wasm_bulk_memory(true) 27 | .wasm_reference_types(true) 28 | .wasm_tail_call(true) 29 | .wasm_extended_const(true); 30 | config.rwasm_config(RwasmConfig { 31 | state_router: None, 32 | entrypoint_name: Some("main".to_string()), 33 | import_linker: None, 34 | wrap_import_functions: true, 35 | translate_drop_keep: false, 36 | allow_malformed_entrypoint_func_type: true, 37 | use_32bit_mode: false, 38 | builtins_consume_fuel: false, 39 | }); 40 | let engine = Engine::new(&config); 41 | let wasm_module = Module::new(&engine, &wasm[..]).unwrap(); 42 | let rwasm_module = rwasm::legacy::rwasm::RwasmModule::from_module(&wasm_module); 43 | let mut encoded_rwasm_module = Vec::new(); 44 | use rwasm::legacy::rwasm::BinaryFormat; 45 | rwasm_module 46 | .write_binary_to_vec(&mut encoded_rwasm_module) 47 | .unwrap(); 48 | let rwasm_module = RwasmModule::new(&encoded_rwasm_module); 49 | let mut vm = RwasmExecutor::new(Arc::new(rwasm_module), ExecutorConfig::default(), ()); 50 | 51 | Caller::new(&mut vm).stack_push(43); 52 | vm.run().unwrap(); 53 | let result: i32 = Caller::new(&mut vm).stack_pop_as(); 54 | assert_eq!(result, 433494437); 55 | vm.reset(None); 56 | } 57 | 58 | #[test] 59 | fn test_riscv() { 60 | let elf = include_bytes!("./keccak-elf"); 61 | let mut executor = Executor::new(Program::from(elf).unwrap(), Default::default()); 62 | executor.executor_mode = ExecutorMode::Simple; 63 | executor.execute().unwrap(); 64 | println!("global clock: {}", executor.state.global_clk); 65 | executor.state = ExecutionState::new(executor.program.pc_start); 66 | } 67 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluentlabs-xyz/rwasm/4e3597f78bf038a39601f5e8e266715b5834123f/docs/SUMMARY.md -------------------------------------------------------------------------------- /docs/instructions/00_unreachable.md: -------------------------------------------------------------------------------- 1 | ## `unreachable` 2 | 3 | ### **Description** 4 | 5 | The `unreachable` instruction immediately **traps** when executed. It is used to indicate that an invalid or undefined 6 | execution path has been reached. This typically results in program termination or an exception being raised. 7 | 8 | ### **Behavior** 9 | 10 | 1. Causes a **trap** (`UnreachableCodeReached`) when executed. 11 | 2. Execution **does not continue** beyond this point. 12 | 3. No registers, memory, or stack operations are performed other than triggering the trap. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **Instruction Pointer (`ip`)**: **Execution halts** due to the trap. 17 | - **Stack Pointer (`SP`)**: **Unchanged** (as no stack operations occur before the trap). 18 | - **Memory**: **Unchanged** (as no memory read/write occurs). 19 | 20 | ### **Stack Changes** 21 | 22 | #### **Before Execution:** 23 | 24 | ``` 25 | [ ... | stack data | SP ] 26 | ``` 27 | 28 | #### **After Execution:** 29 | 30 | - **Execution is halted due to a trap.** 31 | - **No stack changes occur.** 32 | 33 | ### **Operands** 34 | 35 | - **None** (This instruction does not take any operands). 36 | 37 | ### **Notes** 38 | 39 | - `unreachable` is used for debugging, error handling, and enforcing correctness in execution paths. 40 | - Any attempt to execute `unreachable` results in an **immediate trap**. 41 | - This instruction is commonly used in generated WebAssembly code for scenarios that should never happen (e.g., missing 42 | case handling in a `match` statement). -------------------------------------------------------------------------------- /docs/instructions/01_local_get.md: -------------------------------------------------------------------------------- 1 | ## `local_get` 2 | 3 | ### **Description** 4 | 5 | The `local_get` instruction retrieves the value of a local variable at the specified depth from the current stack 6 | position and pushes it onto the stack. 7 | 8 | ### **Behavior** 9 | 10 | 1. Reads the local variable at a depth of `local_depth` from the top of the initialized stack. 11 | 2. Pushes the retrieved value onto the stack. 12 | 3. Increments the instruction pointer (`ip`) to move to the next instruction. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **Instruction Pointer (`ip`)**: Increased by 1. 17 | - **Stack Pointer (`sp`)**: Incremented by 1 due to the push operation. 18 | 19 | ### **Stack Changes** 20 | 21 | #### Before Execution: 22 | 23 | ``` 24 | [ ... | local_n | local_n-1 | ... | local_0 | sp ] 25 | ``` 26 | 27 | (`sp` points to the latest uninitialized stack position.) 28 | 29 | #### After Execution: 30 | 31 | ``` 32 | [ ... | local_n | local_n-1 | ... | local_0 | value | sp ] 33 | ``` 34 | 35 | (`value` is the retrieved local variable at `local_depth`.) 36 | 37 | ### **Operands** 38 | 39 | - `local_depth` (integer): specifies the depth of the local variable to retrieve from the current stack. 40 | 41 | ### **Notes** 42 | 43 | - The instruction does not modify the value of the local variable being accessed. 44 | - The operation is effectively duplicating the local variable onto the stack. -------------------------------------------------------------------------------- /docs/instructions/02_local_set.md: -------------------------------------------------------------------------------- 1 | ## `local_set` 2 | 3 | ### **Description** 4 | 5 | The `local_set` instruction updates the value of a local variable at the specified depth with a new value popped from 6 | the stack. 7 | 8 | ### **Behavior** 9 | 10 | 1. Pops the top value from the stack. 11 | 2. Stores this value in the local variable at a depth of `local_depth` from the current stack position. 12 | 3. Increments the instruction pointer (`ip`) to move to the next instruction. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **Instruction Pointer (`ip`)**: Increased by 1. 17 | - **Stack Pointer (`sp`)**: Decremented by 1 due to the pop operation. 18 | 19 | ### **Stack Changes** 20 | 21 | ##### **Before Execution:** 22 | 23 | ``` 24 | [ ... | local_n | local_n-1 | ... | local_0 | new_value | sp ] 25 | ``` 26 | 27 | (`sp` points to the latest uninitialized stack position.) 28 | 29 | ##### **After Execution:** 30 | 31 | ``` 32 | [ ... | local_n | local_n-1 | ... | new_value | sp ] 33 | ``` 34 | 35 | (`new_value` is stored at `local_depth`, and `sp` moves back by one.) 36 | 37 | ### **Operands** 38 | 39 | - `local_depth` (integer): specifies the depth of the local variable to be updated. 40 | 41 | ### **Notes** 42 | 43 | - The local variable at `local_depth` is **overwritten** with the new value. 44 | - The instruction reduces the stack size by one due to the pop operation. -------------------------------------------------------------------------------- /docs/instructions/03_local_tee.md: -------------------------------------------------------------------------------- 1 | ## `local_tee` 2 | 3 | ### **Description** 4 | 5 | The `local_tee` instruction duplicates the top value of the stack and stores it in a local variable at a specified 6 | depth. Unlike `local_set`, it does not remove the value from the stack. 7 | 8 | ### **Behavior** 9 | 10 | 1. Reads the top value of the stack **without popping** it. 11 | 2. Stores this value in the local variable at a depth of `local_depth` from the current stack position. 12 | 3. Increments the instruction pointer (`ip`) to move to the next instruction. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **Instruction Pointer (`ip`)**: Increased by 1. 17 | - **Stack Pointer (`SP`)**: **Unchanged** (since no value is removed from the stack). 18 | 19 | ### **Stack Changes** 20 | 21 | #### **Before Execution:** 22 | 23 | ``` 24 | [ ... | local_n | local_n-1 | ... | local_0 | value | SP ] 25 | ``` 26 | 27 | (`SP` points to the latest uninitialized stack position.) 28 | 29 | #### **After Execution:** 30 | 31 | ``` 32 | [ ... | local_n | local_n-1 | ... | value | local_0 | value | SP ] 33 | ``` 34 | 35 | (`value` is stored at `local_depth`, but remains on the stack.) 36 | 37 | ### **Operands** 38 | 39 | - `local_depth` (integer): Specifies the depth of the local variable to be updated. 40 | 41 | ### **Notes** 42 | 43 | - The local variable at `local_depth` is **overwritten** with the value from the top of the stack. 44 | - The stack **remains unchanged** because `local_tee` only copies the value rather than popping it. 45 | - Useful for preserving a value while updating a local variable. -------------------------------------------------------------------------------- /docs/instructions/04_br.md: -------------------------------------------------------------------------------- 1 | ## `br` 2 | 3 | ### **Description** 4 | 5 | The `br` instruction performs an **unconditional branch** by modifying the instruction pointer (`ip`). The new 6 | instruction address is determined by adding a signed offset (`branch_offset`) to the current `ip`. 7 | 8 | ### **Behavior** 9 | 10 | 1. Reads the signed `branch_offset` operand. 11 | 2. Modifies the instruction pointer (`ip`) by adding `branch_offset` to its current value. 12 | 3. Execution continues at the new instruction address. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **Instruction Pointer (`ip`)**: Modified by adding `branch_offset`. 17 | - **Stack Pointer (`SP`)**: **Unchanged** (since no values are pushed or popped from the stack). 18 | 19 | ### **Stack Changes** 20 | 21 | #### **Before Execution:** 22 | 23 | ``` 24 | [ ... | stack data | SP ] 25 | ``` 26 | 27 | (`SP` points to the latest uninitialized stack position.) 28 | 29 | #### **After Execution:** 30 | 31 | ``` 32 | [ ... | stack data | SP ] 33 | ``` 34 | 35 | (Stack remains unchanged; only `ip` is updated.) 36 | 37 | ### **Operands** 38 | 39 | - `branch_offset` (signed integer): Specifies the number of instructions to jump forward or backward. 40 | 41 | ### **Notes** 42 | 43 | - This instruction **does not conditionally check** any values; it always jumps. 44 | - It can be used to implement loops or exit constructs. 45 | - A positive `branch_offset` moves execution **forward**, while a negative value moves it **backward**. -------------------------------------------------------------------------------- /docs/instructions/05_br_if.md: -------------------------------------------------------------------------------- 1 | ## `br_if` 2 | 3 | ### **Description** 4 | 5 | The `br_if` instruction performs a **conditional branch** based on the value at the top of the stack. If the condition 6 | is `true`, execution continues with the next instruction. If `false`, execution jumps to the target instruction 7 | specified by `branch_offset`. 8 | 9 | ### **Behavior** 10 | 11 | 1. Pops a boolean condition from the stack. 12 | 2. If the condition is `true`, the instruction pointer (`ip`) is incremented by `1` to continue execution normally. 13 | 3. If the condition is `false`, the instruction pointer (`ip`) is modified by adding `branch_offset`, causing a jump. 14 | 15 | ### **Registers and Memory Changes** 16 | 17 | - **Instruction Pointer (`ip`)**: 18 | - **Increased by `1`** if the condition is `true`. 19 | - **Offset by `branch_offset`** if the condition is `false`. 20 | - **Stack Pointer (`SP`)**: **Decremented by `1`** (as the condition value is popped from the stack). 21 | - **Memory**: **Unchanged** (no read or write operations). 22 | 23 | ### **Stack Changes** 24 | 25 | #### **Before Execution:** 26 | 27 | ``` 28 | [ ... | condition | SP ] 29 | ``` 30 | 31 | (`SP` points to the latest uninitialized stack position.) 32 | 33 | #### **After Execution (Condition = `true`):** 34 | 35 | ``` 36 | [ ... | SP ] 37 | ``` 38 | 39 | (`SP` decreases by `1`, and `ip` moves to the next instruction.) 40 | 41 | #### **After Execution (Condition = `false`):** 42 | 43 | ``` 44 | [ ... | SP ] 45 | ``` 46 | 47 | (`SP` decreases by `1`, and `ip` jumps by `branch_offset`.) 48 | 49 | ### **Operands** 50 | 51 | - `branch_offset` (signed integer): Specifies the number of instructions to jump forward or backward if the condition is 52 | `false`. 53 | 54 | ### **Notes** 55 | 56 | - Unlike `br`, this instruction only branches when the condition is `false`. 57 | - It is useful for implementing conditional control flow structures like `if` statements. -------------------------------------------------------------------------------- /docs/instructions/06_br_if_nez.md: -------------------------------------------------------------------------------- 1 | ## `br_if_nez` 2 | 3 | ### **Description** 4 | 5 | The `br_if_nez` instruction performs a **conditional branch** based on the value at the top of the stack. If the 6 | condition is **nonzero (`true`)**, execution jumps to the instruction at the target offset (`branch_offset`). Otherwise, 7 | execution continues with the next instruction. 8 | 9 | ### **Behavior** 10 | 11 | 1. Pops a boolean condition from the stack. 12 | 2. If the condition is **nonzero (`true`)**, the instruction pointer (`ip`) is modified by adding `branch_offset`, 13 | causing a jump. 14 | 3. If the condition is **zero (`false`)**, the instruction pointer (`ip`) is incremented by `1` to continue execution 15 | normally. 16 | 17 | ### **Registers and Memory Changes** 18 | 19 | - **Instruction Pointer (`ip`)**: 20 | - **Offset by `branch_offset`** if the condition is `true`. 21 | - **Increased by `1`** if the condition is `false`. 22 | - **Stack Pointer (`SP`)**: **Decremented by `1`** (as the condition value is popped from the stack). 23 | - **Memory**: **Unchanged** (no read or write operations). 24 | 25 | ### **Stack Changes** 26 | 27 | #### **Before Execution:** 28 | 29 | ``` 30 | [ ... | condition | SP ] 31 | ``` 32 | 33 | (`SP` points to the latest uninitialized stack position.) 34 | 35 | #### **After Execution (Condition = `true`):** 36 | 37 | ``` 38 | [ ... | SP ] 39 | ``` 40 | 41 | (`SP` decreases by `1`, and `ip` jumps by `branch_offset`.) 42 | 43 | #### **After Execution (Condition = `false`):** 44 | 45 | ``` 46 | [ ... | SP ] 47 | ``` 48 | 49 | (`SP` decreases by `1`, and `ip` moves to the next instruction.) 50 | 51 | ### **Operands** 52 | 53 | - `branch_offset` (signed integer): Specifies the number of instructions to jump forward or backward if the condition is 54 | `true`. 55 | 56 | ### **Notes** 57 | 58 | - This instruction branches **only when the condition is nonzero (`true`)**. 59 | - It is commonly used for loop continuation or conditional jumps where `0` is treated as `false` and any nonzero value 60 | as `true`. -------------------------------------------------------------------------------- /docs/instructions/10_consume_fuel.md: -------------------------------------------------------------------------------- 1 | ## `consume_fuel` Instruction Specification 2 | 3 | ### **Description** 4 | 5 | The `consume_fuel` instruction reduces the available execution fuel by a specified amount (`block_fuel`). If the 6 | required fuel cannot be consumed, execution traps. This instruction is used to enforce computational limits within the 7 | rWasm virtual machine. 8 | 9 | ### **Behavior** 10 | 11 | 1. Attempts to consume the specified `block_fuel` amount from the fuel counter (`fc`). 12 | 2. If there is **sufficient fuel**, it is deducted, and execution proceeds. 13 | 3. If there is **insufficient fuel**, execution traps (`RwasmError::TrapCode`). 14 | 4. If successful, the instruction pointer (`ip`) is incremented by `1` to continue execution. 15 | 16 | ### **Registers and Memory Changes** 17 | 18 | - **instruction pointer (`ip`)**: **Increased by `1`** if execution continues. 19 | - **stack pointer (`sp`)**: **Unchanged** (this instruction does not interact with the stack). 20 | - **fuel counter (`fc`)**: **Decreased** by `block_fuel` if fuel is available. 21 | - **memory**: **Unchanged** (no read or write operations). 22 | 23 | ### **Stack Changes** 24 | 25 | #### **Before Execution:** 26 | 27 | ``` 28 | [ ... | stack data | sp ] 29 | ``` 30 | 31 | #### **After Execution (Fuel Available):** 32 | 33 | ``` 34 | [ ... | stack data | sp ] 35 | ``` 36 | 37 | (`sp` remains unchanged, `ip` is incremented by `1`, and `fc` decreases by `block_fuel`.) 38 | 39 | #### **After Execution (Fuel Exhausted - Trap):** 40 | 41 | - **Execution halts due to a trap.** 42 | 43 | ### **Operands** 44 | 45 | - `block_fuel` (unsigned integer): Specifies the amount of fuel to consume. 46 | 47 | ### **Notes** 48 | 49 | - `consume_fuel` is primarily used for **resource metering**, ensuring that execution does not exceed predefined 50 | computational limits. 51 | - If the required fuel cannot be deducted, the VM **traps** to prevent excessive resource usage. 52 | - This instruction does **not** modify the stack but controls execution flow via fuel consumption. -------------------------------------------------------------------------------- /docs/instructions/11_return.md: -------------------------------------------------------------------------------- 1 | ## `return` 2 | 3 | ### **Description** 4 | 5 | The `return` instruction exits the current function and resumes execution at the caller's instruction pointer (`ip`). If 6 | no caller exists, execution terminates with an exit code of `0`. 7 | 8 | ### **Behavior** 9 | 10 | 1. Pops the caller’s instruction pointer (`ip`) from the call stack. 11 | 2. If a caller exists, execution resumes at the caller’s `ip`. 12 | 3. If no caller exists, execution terminates with an exit code of `0`. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **instruction pointer (`ip`)**: 17 | - **Set to the caller’s `ip`** if returning. 18 | - **If no caller exists, execution stops with an exit code of `0`.** 19 | - **stack pointer (`sp`)**: **Unchanged** (stack cleanup logic is ignored as per instructions). 20 | - **memory**: **Unchanged** (no read or write operations). 21 | 22 | ### **Stack Changes** 23 | 24 | #### **Before Execution:** 25 | 26 | ``` 27 | [ ... | function locals | return values | sp ] 28 | ``` 29 | 30 | #### **After Execution (Caller Exists):** 31 | 32 | ``` 33 | [ ... | caller state | sp ] 34 | ``` 35 | 36 | (`ip` is set to the caller’s `ip`.) 37 | 38 | #### **After Execution (No Caller - Program Exit):** 39 | 40 | ``` 41 | Execution stops with exit code `0`. 42 | ``` 43 | 44 | ### **Operands** 45 | 46 | - **None** (return operation is implicit). 47 | 48 | ### **Notes** 49 | 50 | - If there is no caller, execution **terminates** upon return. 51 | - This instruction is essential for function control flow in WebAssembly. -------------------------------------------------------------------------------- /docs/instructions/12_return_if_nez.md: -------------------------------------------------------------------------------- 1 | ## `return_if_nez` 2 | 3 | ### **Description** 4 | 5 | The `return_if_nez` instruction conditionally returns from the current function if the top stack value is 6 | **nonzero (`true`)**. If the function returns, the instruction pointer (`ip`) is restored to the caller’s location. If 7 | there is no caller, execution terminates with an exit code of `0`. If the condition is `false`, execution proceeds to 8 | the next instruction. 9 | 10 | ### **Behavior** 11 | 12 | 1. Pops the top value from the stack and interprets it as a **boolean condition**. 13 | 2. If the condition is **nonzero (`true`)**: 14 | - Pops the caller's instruction pointer (`ip`) from the call stack. 15 | - If a caller exists, execution resumes at the caller’s `ip`. 16 | - If no caller exists, execution terminates with an exit code of `0`. 17 | 3. If the condition is **zero (`false`)**, increments the instruction pointer (`ip`) by `1` and continues execution. 18 | 19 | ### **Registers and Memory Changes** 20 | 21 | - **instruction pointer (`ip`)**: 22 | - **Set to the caller’s `ip`** if returning. 23 | - **Increased by `1`** if the condition is `false`. 24 | - **stack pointer (`sp`)**: 25 | - **Decremented by `1`** due to popping the condition. 26 | - **memory**: **Unchanged** (no read or write operations). 27 | 28 | ### **Stack Changes** 29 | 30 | #### **Before Execution:** 31 | 32 | ``` 33 | [ ... | return values | condition | sp ] 34 | ``` 35 | 36 | #### **After Execution (Condition = `true`, Caller Exists):** 37 | 38 | ``` 39 | [ ... | caller state | sp ] 40 | ``` 41 | 42 | (`sp` decreases by `1`, and execution resumes at the caller’s `ip`.) 43 | 44 | #### **After Execution (Condition = `true`, No Caller - Program Exit):** 45 | 46 | ``` 47 | Execution stops with exit code `0`. 48 | ``` 49 | 50 | #### **After Execution (Condition = `false`):** 51 | 52 | ``` 53 | [ ... | return values | sp ] 54 | ``` 55 | 56 | (`sp` decreases by `1`, and `ip` moves to the next instruction.) 57 | 58 | ### **Operands** 59 | 60 | - **None** (condition value is popped from the stack). 61 | 62 | ### **Notes** 63 | 64 | - If there is no caller in the call stack, execution **terminates** when returning. 65 | - This instruction enables conditional function returns based on runtime conditions. -------------------------------------------------------------------------------- /docs/instructions/13_return_call_internal.md: -------------------------------------------------------------------------------- 1 | ## `return_call_internal` 2 | 3 | ### **Description** 4 | 5 | The `return_call_internal` instruction performs a **return followed by an internal function call**. It first adjusts the 6 | instruction pointer (`ip`) to transition from the current function, then calls the internal function identified by 7 | `func_idx`. 8 | 9 | ### **Behavior** 10 | 11 | 1. Increments the instruction pointer (`ip`) by `2` to prepare for the function call. 12 | 2. Retrieves the function reference (`instr_ref`) corresponding to `func_idx`. 13 | 3. Prepares the stack for the function call. 14 | 4. Updates `sp` to the new function's stack pointer. 15 | 5. Sets `ip` to the instruction reference (`instr_ref`), transferring control to the target function. 16 | 17 | ### **Registers and Memory Changes** 18 | 19 | - **instruction pointer (`ip`)**: 20 | - **Increased by `2`** before transitioning to the new function. 21 | - **Updated to the function’s instruction reference (`instr_ref`)** to begin execution of the internal function. 22 | - **stack pointer (`sp`)**: 23 | - **Updated to the new function’s stack pointer** after preparing for the call. 24 | - **memory**: **Unchanged** (no read or write operations). 25 | - **fuel counter (`fc`)**: **Unchanged** (this instruction does not consume fuel). 26 | 27 | ### **Stack Changes** 28 | 29 | #### **Before Execution:** 30 | 31 | ``` 32 | [ ... | function arguments | sp ] 33 | ``` 34 | 35 | #### **After Execution (Function Call Transition):** 36 | 37 | ``` 38 | [ ... | new function locals | sp ] 39 | ``` 40 | 41 | (`sp` is updated to the new function’s stack pointer, and `ip` jumps to the function’s first instruction.) 42 | 43 | ### **Operands** 44 | 45 | - `func_idx` (integer): Specifies the internal function to call. 46 | 47 | ### **Notes** 48 | 49 | - This instruction **combines returning from the current function and calling a new one** in a single step. 50 | - Used for efficient tail-call optimizations within WebAssembly execution. 51 | - If `func_idx` is invalid, execution panics. -------------------------------------------------------------------------------- /docs/instructions/14_return_call.md: -------------------------------------------------------------------------------- 1 | ## `return_call` 2 | 3 | ### **Description** 4 | 5 | The `return_call` instruction **returns from the current function and immediately calls an external function** 6 | identified by `func_idx`. This is commonly used for tail-call optimizations. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Increments** the instruction pointer (`ip`) by `2` before making the function call. 11 | 2. **Calls** the external function referenced by `func_idx`. 12 | 3. Execution transitions to the external function. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **instruction pointer (`ip`)**: 17 | - **Increased by `2`** before calling the external function. 18 | - **Execution is transferred to the external function.** 19 | - **stack pointer (`sp`)**: 20 | - **Unchanged** (stack modifications are handled within the external function). 21 | - **memory**: **Unchanged** (this instruction does not directly modify memory). 22 | - **fuel counter (`fc`)**: **Unchanged** (fuel consumption depends on the called function). 23 | 24 | ### **Stack Changes** 25 | 26 | #### **Before Execution:** 27 | 28 | ``` 29 | [ ... | function arguments | sp ] 30 | ``` 31 | 32 | #### **After Execution (External Function Called):** 33 | 34 | ``` 35 | [ ... | new function locals | sp ] 36 | ``` 37 | 38 | (`sp` is updated within the external function, and `ip` moves to the function’s first instruction.) 39 | 40 | ### **Operands** 41 | 42 | - `func_idx` (integer): Specifies the external function to call. 43 | 44 | ### **Notes** 45 | 46 | - The **instruction pointer is incremented before calling the function** to prevent unexpected interruptions. 47 | - Used for **efficient tail-call optimizations** in WebAssembly execution. 48 | - The function call is **external**, meaning it may interact with system APIs or host functions. -------------------------------------------------------------------------------- /docs/instructions/15_return_call_indirect.md: -------------------------------------------------------------------------------- 1 | ## `return_call_indirect` 2 | 3 | ### **Description** 4 | 5 | The `return_call_indirect` instruction **returns from the current function and immediately calls an internal function 6 | indirectly**. The function to be called is determined dynamically from a **table lookup** using an index provided on the 7 | stack. If the index is invalid or refers to `null`, execution traps. 8 | 9 | ### **Behavior** 10 | 11 | 1. **Retrieves** the function index (`func_index`) from the stack. 12 | 2. **Fetches** the corresponding function reference from the table. 13 | 3. **Validates** the function reference: 14 | - If `func_index` is out of bounds, execution traps (`TrapCode::TableOutOfBounds`). 15 | - If `func_index` is `null`, execution traps (`TrapCode::IndirectCallToNull`). 16 | 4. **Adjusts** the function index to match the internal function reference system. 17 | 5. **Calls** the resolved function using an indirect call mechanism. 18 | 19 | ### **Registers and Memory Changes** 20 | 21 | - **instruction pointer (`ip`)**: 22 | - **Set to the resolved function’s instruction pointer.** 23 | - **stack pointer (`sp`)**: 24 | - **Decremented by `1`** due to popping the function index. 25 | - **memory**: **Unchanged** (this instruction does not directly modify memory). 26 | - **fuel counter (`fc`)**: **Unchanged** (fuel consumption depends on the called function). 27 | 28 | ### **Stack Changes** 29 | 30 | #### **Before Execution:** 31 | 32 | ``` 33 | [ ... | function arguments | function index | sp ] 34 | ``` 35 | 36 | #### **After Execution (Function Call Success):** 37 | 38 | ``` 39 | [ ... | new function locals | sp ] 40 | ``` 41 | 42 | (`sp` is updated within the called function, and `ip` moves to the function’s first instruction.) 43 | 44 | #### **After Execution (Invalid Function Index - Trap):** 45 | 46 | - **Execution halts due to a trap (`TableOutOfBounds` or `IndirectCallToNull`).** 47 | 48 | ### **Operands** 49 | 50 | - `signature_idx` (integer): Specifies the function signature for validation. 51 | - `func_index` (stack value): The function index retrieved from the table. 52 | 53 | ### **Notes** 54 | 55 | - This instruction enables **dynamic function dispatch** within WebAssembly. 56 | - If the function index is invalid or the table reference is `null`, execution **traps**. 57 | - Used in scenarios where function calls need to be dynamically resolved. -------------------------------------------------------------------------------- /docs/instructions/16_call_internal.md: -------------------------------------------------------------------------------- 1 | ## `call_internal` 2 | 3 | ### **Description** 4 | 5 | The `call_internal` instruction performs a **direct call to an internal function** identified by `func_idx`. It ensures 6 | proper stack and instruction pointer (`ip`) management while preventing excessive recursion depth. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Increments** the instruction pointer (`ip`) by `1` before making the function call. 11 | 2. **Synchronizes** the stack pointer (`sp`) with the value stack. 12 | 3. **Checks** if the call stack exceeds the maximum recursion depth: 13 | - If it does, execution traps (`TrapCode::StackOverflow`). 14 | 4. **Pushes** the current instruction pointer (`ip`) onto the call stack. 15 | 5. **Fetches** the instruction reference (`instr_ref`) for the function from the function table. 16 | 6. **Prepares** the value stack for the function call. 17 | 7. **Updates** the stack pointer (`sp`) for the new function’s execution. 18 | 8. **Sets** the instruction pointer (`ip`) to `instr_ref` to begin execution of the function. 19 | 20 | ### **Registers and Memory Changes** 21 | 22 | - **instruction pointer (`ip`)**: 23 | - **Increased by `1`** before transitioning to the function. 24 | - **Updated to the function’s instruction reference (`instr_ref`)** upon execution. 25 | - **stack pointer (`sp`)**: 26 | - **Updated** to reflect the new function’s stack frame. 27 | - **memory**: **Unchanged** (this instruction does not directly modify memory). 28 | - **fuel counter (`fc`)**: **Unchanged** (fuel consumption depends on function execution). 29 | 30 | ### **Stack Changes** 31 | 32 | #### **Before Execution:** 33 | 34 | ``` 35 | [ ... | function arguments | sp ] 36 | ``` 37 | 38 | #### **After Execution (Function Call Success):** 39 | 40 | ``` 41 | [ ... | new function locals | sp ] 42 | ``` 43 | 44 | (`sp` is updated to the new function’s stack frame, and `ip` jumps to the function’s first instruction.) 45 | 46 | #### **After Execution (Recursion Depth Exceeded—Trap):** 47 | 48 | - **Execution halts due to a `StackOverflow` trap.** 49 | 50 | ### **Operands** 51 | 52 | - `func_idx` (integer): Specifies the internal function to call. 53 | 54 | ### **Notes** 55 | 56 | - If the recursion depth exceeds `N_MAX_RECURSION_DEPTH`, execution **traps**. 57 | - This instruction **does not consume fuel** directly but affects overall execution. 58 | - Used for **efficient function calls within WebAssembly**. -------------------------------------------------------------------------------- /docs/instructions/17_call.md: -------------------------------------------------------------------------------- 1 | ## `call` 2 | 3 | ### **Description** 4 | 5 | The `call` instruction performs a **direct call to an external function** identified by `func_idx`. It ensures proper 6 | stack synchronization and updates the instruction pointer (`ip`) before the call to prevent interruptions. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Synchronizes** the stack pointer (`sp`) with the value stack. 11 | 2. **Increments** the instruction pointer (`ip`) by `1` before making the function call to prevent execution 12 | interruptions. 13 | 3. **Calls** the external function identified by `func_idx`. 14 | 15 | ### **Registers and Memory Changes** 16 | 17 | - **instruction pointer (`ip`)**: 18 | - **Increased by `1`** before calling the function. 19 | - **Execution is transferred to the external function**. 20 | - **stack pointer (`sp`)**: 21 | - **Unchanged** (stack modifications occur inside the called function). 22 | - **memory**: **Unchanged** (this instruction does not directly modify memory). 23 | - **fuel counter (`fc`)**: **Unchanged** (fuel consumption depends on the execution of the called function). 24 | 25 | ### **Stack Changes** 26 | 27 | #### **Before Execution:** 28 | 29 | ``` 30 | [ ... | function arguments | sp ] 31 | ``` 32 | 33 | #### **After Execution (Function Call Success):** 34 | 35 | ``` 36 | [ ... | new function locals | sp ] 37 | ``` 38 | 39 | (`sp` is updated within the external function, and `ip` moves to the function’s first instruction.) 40 | 41 | ### **Operands** 42 | 43 | - `func_idx` (integer): Specifies the external function to call. 44 | 45 | ### **Notes** 46 | 47 | - The **instruction pointer is incremented before calling the function** to prevent execution from being interrupted 48 | unexpectedly. 49 | - This instruction enables **interaction with host functions and system APIs**. 50 | - The function execution is handled externally and may cause an **interruption** depending on system behavior. -------------------------------------------------------------------------------- /docs/instructions/18_call_indirect.md: -------------------------------------------------------------------------------- 1 | ## `call_indirect` 2 | 3 | ### **Description** 4 | 5 | The `call_indirect` instruction **performs an indirect function call** by looking up a function index in a function 6 | table. The function to be called is determined dynamically based on an index provided on the stack. If the index is out 7 | of bounds or points to a `null` entry, execution traps. 8 | 9 | ### **Behavior** 10 | 11 | 1. **Fetches** the function table index from memory. 12 | 2. **Pops** the function index (`func_index`) from the stack. 13 | 3. **Stores** the expected function signature (`signature_idx`) for validation. 14 | 4. **Resolves** the function index from the table: 15 | - If the index is out of bounds, execution **traps** (`TrapCode::TableOutOfBounds`). 16 | - If the index is `null`, execution **traps** (`TrapCode::IndirectCallToNull`). 17 | 5. **Adjusts** the function index for internal function reference (`func_idx`). 18 | 6. **Increments** the instruction pointer (`ip`) by `2`. 19 | 7. **Synchronizes** the stack pointer (`sp`) with the value stack. 20 | 8. **Checks** recursion depth: 21 | - If it exceeds `N_MAX_RECURSION_DEPTH`, execution **traps** (`TrapCode::StackOverflow`). 22 | 9. **Pushes** the current instruction pointer (`ip`) onto the call stack. 23 | 10. **Fetches** the function's instruction reference (`instr_ref`). 24 | 11. **Prepares** the stack for the function call. 25 | 12. **Updates** the stack pointer (`sp`) for the new function. 26 | 13. **Transfers** execution to the resolved function. 27 | 28 | ### **Registers and Memory Changes** 29 | 30 | - **instruction pointer (`ip`)**: 31 | - **Increased by `2`** before transitioning to the function. 32 | - **Updated to the resolved function’s instruction reference (`instr_ref`)**. 33 | - **stack pointer (`sp`)**: 34 | - **Decremented by `1`** due to popping the function index. 35 | - **Updated** to reflect the new function’s stack frame. 36 | - **memory**: **Unchanged** (this instruction does not directly modify memory). 37 | - **fuel counter (`fc`)**: **Unchanged** (fuel consumption depends on function execution). 38 | 39 | ### **Stack Changes** 40 | 41 | #### **Before Execution:** 42 | 43 | ``` 44 | [ ... | function arguments | function index | sp ] 45 | ``` 46 | 47 | #### **After Execution (Function Call Success):** 48 | 49 | ``` 50 | [ ... | new function locals | sp ] 51 | ``` 52 | 53 | (`sp` is updated to the new function’s stack frame, and `ip` jumps to the function’s first instruction.) 54 | 55 | #### **After Execution (Invalid Function Index - Trap):** 56 | 57 | - **Execution halts due to a `TableOutOfBounds` or `IndirectCallToNull` trap.** 58 | 59 | #### **After Execution (Recursion Depth Exceeded—Trap):** 60 | 61 | - **Execution halts due to a `StackOverflow` trap.** 62 | 63 | ### **Operands** 64 | 65 | - `signature_idx` (integer): Specifies the function signature for validation. 66 | - `func_index` (stack value): The function index retrieved from the table. 67 | 68 | ### **Notes** 69 | 70 | - Enables **dynamic function dispatch** in WebAssembly by calling functions through a function table. 71 | - If the function index is invalid or the table reference is `null`, execution **traps**. 72 | - The function call **must** match the expected signature; otherwise, behavior is undefined. 73 | - If recursion depth exceeds `N_MAX_RECURSION_DEPTH`, execution **traps**. -------------------------------------------------------------------------------- /docs/instructions/19_signature_check.md: -------------------------------------------------------------------------------- 1 | ## `signature_check` 2 | 3 | ### **Description** 4 | 5 | The `signature_check` instruction **verifies that the last called function matches the expected signature**. If the 6 | function signature does not match, execution traps. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Retrieves** the last function signature from `last_signature`. 11 | 2. **Compares** it with the expected `signature_idx`: 12 | - If the signatures **do not match**, execution **traps** (`TrapCode::BadSignature`). 13 | - If the signatures **match** or no previous signature exists, execution continues. 14 | 3. **Increments** the instruction pointer (`ip`) by `1` to proceed. 15 | 16 | ### **Registers and Memory Changes** 17 | 18 | - **instruction pointer (`ip`)**: 19 | - **Increased by `1`** if the signature check passes. 20 | - **Execution traps** if the signature is invalid. 21 | - **stack pointer (`sp`)**: **Unchanged** (this instruction does not modify the stack). 22 | - **memory**: **Unchanged** (this instruction does not interact with memory). 23 | - **fuel counter (`fc`)**: **Unchanged**. 24 | 25 | ### **Stack Changes** 26 | 27 | #### **Before Execution:** 28 | 29 | ``` 30 | [ ... | function arguments | sp ] 31 | ``` 32 | 33 | #### **After Execution (Signature Valid - Execution Continues):** 34 | 35 | ``` 36 | [ ... | function arguments | sp ] 37 | ``` 38 | 39 | (`sp` remains unchanged, and `ip` increments by `1`.) 40 | 41 | #### **After Execution (Signature Mismatch - Trap):** 42 | 43 | - **Execution halts due to a `BadSignature` trap.** 44 | 45 | ### **Operands** 46 | 47 | - `signature_idx` (integer): Expected function signature index. 48 | 49 | ### **Notes** 50 | 51 | - This instruction is **used for indirect function calls** to ensure type safety. 52 | - If the function signature **does not match**, execution **traps** immediately. 53 | - If no previous signature exists, the instruction simply continues execution. -------------------------------------------------------------------------------- /docs/instructions/20_drop.md: -------------------------------------------------------------------------------- 1 | ## `drop` 2 | 3 | ### **Description** 4 | 5 | The `drop` instruction **removes the top value from the stack** without using it. It is typically used to discard unused 6 | computation results. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Pops** the top value from the stack. 11 | 2. **Increments** the instruction pointer (`ip`) by `1` to proceed to the next instruction. 12 | 13 | ### **Registers and Memory Changes** 14 | 15 | - **instruction pointer (`ip`)**: **Increased by `1`**. 16 | - **stack pointer (`sp`)**: **Decremented by `1`** (top stack value is removed). 17 | - **memory**: **Unchanged** (this instruction does not interact with memory). 18 | - **fuel counter (`fc`)**: **Unchanged**. 19 | 20 | ### **Stack Changes** 21 | 22 | #### **Before Execution:** 23 | 24 | ``` 25 | [ ... | value_to_drop | sp ] 26 | ``` 27 | 28 | #### **After Execution:** 29 | 30 | ``` 31 | [ ... | sp ] 32 | ``` 33 | 34 | (`sp` moves down by `1`, effectively removing the top value.) 35 | 36 | ### **Operands** 37 | 38 | - **None** (operates on the top value of the stack). 39 | 40 | ### **Notes** 41 | 42 | - This instruction is **used to discard values** that are no longer needed. 43 | - If the stack is empty before execution, **behavior is undefined**. 44 | - It does **not modify memory** or consume fuel. -------------------------------------------------------------------------------- /docs/instructions/21_select.md: -------------------------------------------------------------------------------- 1 | ## `select` 2 | 3 | ### **Description** 4 | 5 | The `select` instruction **chooses between two values on the stack** based on a condition. It pops a boolean condition 6 | from the stack and returns one of the two preceding values, discarding the other. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Pops** the top three values from the stack: 11 | - `e3`: Condition (boolean). 12 | - `e2`: Value if the condition is `false`. 13 | - `e1`: Value if the condition is `true`. 14 | 2. **Pushes** `e1` back onto the stack if `e3` is `true`, otherwise pushes `e2`. 15 | 3. **Increments** the instruction pointer (`ip`) by `1`. 16 | 17 | ### **Registers and Memory Changes** 18 | 19 | - **instruction pointer (`ip`)**: **Increased by `1`**. 20 | - **stack pointer (`sp`)**: **Decremented by `2`** (removes the condition and one discarded value). 21 | - **memory**: **Unchanged** (this instruction does not interact with memory). 22 | - **fuel counter (`fc`)**: **Unchanged**. 23 | 24 | ### **Stack Changes** 25 | 26 | #### **Before Execution:** 27 | 28 | ``` 29 | [ ... | value_if_true | value_if_false | condition | sp ] 30 | ``` 31 | 32 | #### **After Execution (Condition = `true`):** 33 | 34 | ``` 35 | [ ... | value_if_true | sp ] 36 | ``` 37 | 38 | #### **After Execution (Condition = `false`):** 39 | 40 | ``` 41 | [ ... | value_if_false | sp ] 42 | ``` 43 | 44 | (`sp` moves down by `2`, and only the selected value remains on the stack.) 45 | 46 | ### **Operands** 47 | 48 | - **None** (operates on the top three stack values). 49 | 50 | ### **Notes** 51 | 52 | - If `e3` is **nonzero (`true`)**, `e1` is selected. 53 | - If `e3` is **zero (`false`)**, `e2` is selected. 54 | - **Only one value remains on the stack after execution.** 55 | - If the stack has fewer than three values before execution, **behavior is undefined**. -------------------------------------------------------------------------------- /docs/instructions/22_global_get.md: -------------------------------------------------------------------------------- 1 | ## `global_get` 2 | 3 | ### **Description** 4 | 5 | The `global_get` instruction **retrieves the value of a global variable** and pushes it onto the stack. 6 | 7 | ### **Behavior** 8 | 9 | 1. **Fetches** the value of the global variable at index `global_idx`. 10 | 2. **Pushes** the retrieved value onto the stack. 11 | 3. **Increments** the instruction pointer (`ip`) by `1` to proceed to the next instruction. 12 | 13 | ### **Registers and Memory Changes** 14 | 15 | - **instruction pointer (`ip`)**: **Increased by `1`**. 16 | - **stack pointer (`sp`)**: **Incremented by `1`** (adds the retrieved global value). 17 | - **memory**: **Unchanged** (global variables are stored separately). 18 | - **fuel counter (`fc`)**: **Unchanged**. 19 | 20 | ### **Stack Changes** 21 | 22 | #### **Before Execution:** 23 | 24 | ``` 25 | [ ... | sp ] 26 | ``` 27 | 28 | #### **After Execution:** 29 | 30 | ``` 31 | [ ... | global_value | sp ] 32 | ``` 33 | 34 | (`sp` moves up by `1`, and the retrieved global value is now on top.) 35 | 36 | ### **Operands** 37 | 38 | - `global_idx` (integer): The index of the global variable to fetch. 39 | 40 | ### **Notes** 41 | 42 | - If `global_idx` is invalid, **behavior is undefined**. 43 | - If the global variable is **not initialized**, it returns a **default value**. 44 | - This instruction does **not modify memory** but only reads from global storage. -------------------------------------------------------------------------------- /docs/instructions/23_global_set.md: -------------------------------------------------------------------------------- 1 | ## `global_set` 2 | 3 | ### **Description** 4 | 5 | The `global_set` instruction **updates the value of a global variable** using a value popped from the stack. 6 | 7 | ### **Behavior** 8 | 9 | 1. **Pops** the top value from the stack. 10 | 2. **Stores** the popped value into the global variable at index `global_idx`. 11 | 3. **Increments** the instruction pointer (`ip`) by `1` to proceed to the next instruction. 12 | 13 | ### **Registers and Memory Changes** 14 | 15 | - **instruction pointer (`ip`)**: **Increased by `1`**. 16 | - **stack pointer (`sp`)**: **Decremented by `1`** (removes the top stack value). 17 | - **memory**: **Updated** (modifies the specified global variable). 18 | - **fuel counter (`fc`)**: **Unchanged**. 19 | 20 | ### **Stack Changes** 21 | 22 | #### **Before Execution:** 23 | 24 | ``` 25 | [ ... | new_value | sp ] 26 | ``` 27 | 28 | #### **After Execution:** 29 | 30 | ``` 31 | [ ... | sp ] 32 | ``` 33 | 34 | (`sp` moves down by `1`, as `new_value` is removed and stored in the global variable.) 35 | 36 | ### **Operands** 37 | 38 | - `global_idx` (integer): The index of the global variable to update. 39 | 40 | ### **Notes** 41 | 42 | - If `global_idx` refers to an **immutable** global, **behavior is undefined**. 43 | - If the stack is **empty** before execution, **behavior is undefined**. 44 | - This instruction **modifies memory** by updating global storage. -------------------------------------------------------------------------------- /docs/instructions/24_i32_load.md: -------------------------------------------------------------------------------- 1 | ## `i32_load` 2 | 3 | ### **Description** 4 | 5 | The `i32_load` instruction **loads a 32-bit integer from memory** at a specified offset and pushes it onto the stack. 6 | 7 | ### **Behavior** 8 | 9 | 1. **Pops** the memory address from the stack. 10 | 2. **Computes** the effective address by adding `offset` to the popped address. 11 | 3. **Loads** a 32-bit integer from memory at the computed address. 12 | 4. **Pushes** the loaded value onto the stack. 13 | 5. If the memory access is **out of bounds**, execution **traps** (`TrapCode::MemoryOutOfBounds`). 14 | 15 | ### **Registers and Memory Changes** 16 | 17 | - **instruction pointer (`ip`)**: **Unchanged** (unless a trap occurs). 18 | - **stack pointer (`sp`)**: **Unchanged** in count (one value is popped, one is pushed). 19 | - **memory**: **Read** at the computed memory address. 20 | - **fuel counter (`fc`)**: **Unchanged**. 21 | 22 | ### **Stack Changes** 23 | 24 | #### **Before Execution:** 25 | 26 | ``` 27 | [ ... | address | sp ] 28 | ``` 29 | 30 | #### **After Execution (Successful Load):** 31 | 32 | ``` 33 | [ ... | loaded_value | sp ] 34 | ``` 35 | 36 | (The address is replaced with the loaded 32-bit integer.) 37 | 38 | #### **After Execution (Memory Out of Bounds - Trap):** 39 | 40 | - **Execution halts due to a `MemoryOutOfBounds` trap.** 41 | 42 | ### **Operands** 43 | 44 | - `offset` (integer): A constant offset added to the memory address before loading. 45 | 46 | ### **Notes** 47 | 48 | - **Accessing memory beyond its allocated range results in a trap**. 49 | - The **offset is applied before memory access** and should be within valid bounds. 50 | - **Does not modify memory**, only reads from it. -------------------------------------------------------------------------------- /docs/instructions/47_memory_size.md: -------------------------------------------------------------------------------- 1 | ## `memory_size` Instruction Specification 2 | 3 | ### **Description** 4 | 5 | The `memory_size` instruction **retrieves the current size of linear memory** in **WebAssembly pages** (each page is 64 6 | KiB) and pushes it onto the stack. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Fetches** the current memory size from the `ms` register (measured in pages). 11 | 2. **Pushes** the memory size onto the stack. 12 | 3. **Increments** the instruction pointer (`ip`) by `1`. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **instruction pointer (`ip`)**: **Increased by `1`**. 17 | - **stack pointer (`sp`)**: **Incremented by `1`** (stores the memory size). 18 | - **memory size (`ms`)**: **Read-only** (retrieves the current memory size). 19 | - **memory**: **Unchanged** (this instruction does not modify memory). 20 | - **fuel counter (`fc`)**: **Unchanged**. 21 | 22 | ### **Stack Changes** 23 | 24 | #### **Before Execution:** 25 | 26 | ``` 27 | [ ... | sp ] 28 | ``` 29 | 30 | #### **After Execution:** 31 | 32 | ``` 33 | [ ... | memory_size (in pages) | sp ] 34 | ``` 35 | 36 | (The stack pointer moves up by `1`, and the memory size is pushed onto the stack.) 37 | 38 | ### **Operands** 39 | 40 | - **None** (retrieves the memory size directly from `ms`). 41 | 42 | ### **Notes** 43 | 44 | - Memory size is measured in **WebAssembly pages** (1 page = 64 KiB). 45 | - This instruction **does not modify memory**, only queries its current size. 46 | - Used for **checking available memory** before performing operations that might require additional allocation. -------------------------------------------------------------------------------- /docs/instructions/48_memory_grow.md: -------------------------------------------------------------------------------- 1 | ## `memory_grow` 2 | 3 | ### **Description** 4 | 5 | The `memory_grow` instruction **attempts to increase the size of linear memory** by a given number of pages. It consumes 6 | fuel if a fuel limit is set and pushes the previous memory size onto the stack. If the memory growth fails, it pushes 7 | `u32::MAX`. 8 | 9 | ### **Behavior** 10 | 11 | 1. **Pops** the number of pages to grow (`delta`) from the stack. 12 | 2. **Validates** `delta`: 13 | - If `delta` is invalid (too large to represent in pages), pushes `u32::MAX`, increments `ip` by `1`, and **returns 14 | **. 15 | 3. **If fuel metering is enabled**, it: 16 | - Converts `delta` to bytes. 17 | - **Consumes** fuel (`fc`) based on the number of bytes required. 18 | - If fuel is insufficient, execution **traps**. 19 | 4. **Attempts** to grow memory by `delta` pages: 20 | - If successful, **pushes the previous memory size (in pages)** onto the stack. 21 | - If growth fails, **pushes `u32::MAX`**. 22 | 5. **Increments** the instruction pointer (`ip`) by `1`. 23 | 24 | ### **Registers and Memory Changes** 25 | 26 | - **instruction pointer (`ip`)**: **Increased by `1`**. 27 | - **stack pointer (`sp`)**: **Unchanged in count** (pops `delta`, then pushes the old memory size or `u32::MAX`). 28 | - **memory size (`ms`)**: **Updated** if memory growth succeeds. 29 | - **memory**: **Expanded** if growth is successful. 30 | - **fuel counter (`fc`)**: 31 | - **Decreased** based on the number of bytes added. 32 | - **Unchanged** if no fuel metering is enabled. 33 | 34 | ### **Stack Changes** 35 | 36 | #### **Before Execution:** 37 | 38 | ``` 39 | [ ... | delta | sp ] 40 | ``` 41 | 42 | #### **After Execution (Successful Growth):** 43 | 44 | ``` 45 | [ ... | previous_memory_size | sp ] 46 | ``` 47 | 48 | (`sp` remains the same, but the previous memory size is stored.) 49 | 50 | #### **After Execution (Failed Growth or Invalid `delta`):** 51 | 52 | ``` 53 | [ ... | u32::MAX | sp ] 54 | ``` 55 | 56 | (`sp` remains unchanged, and `u32::MAX` is pushed to indicate failure.) 57 | 58 | #### **After Execution (Insufficient Fuel - Trap):** 59 | 60 | - **Execution halts due to a fuel exhaustion trap.** 61 | 62 | ### **Operands** 63 | 64 | - **None** (operates based on the top stack value). 65 | 66 | ### **Notes** 67 | 68 | - Memory growth is **measured in pages** (1 page = 64 KiB). 69 | - If `delta` is too large to be represented in pages, the instruction **fails gracefully** by pushing `u32::MAX`. 70 | - If memory growth fails (e.g., due to exceeding limits), `u32::MAX` is **pushed to indicate failure**. 71 | - Fuel is **only consumed if a fuel limit is configured**. -------------------------------------------------------------------------------- /docs/instructions/49_memory_fill.md: -------------------------------------------------------------------------------- 1 | ## `memory_fill` 2 | 3 | ### **Description** 4 | 5 | The `memory_fill` instruction **fills a specified memory region with a given byte value**. It reads the destination 6 | address, byte value, and length from the stack and writes the byte value across the specified memory range. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Pops** three values from the stack: 11 | - `d`: The **destination offset** in memory. 12 | - `val`: The **byte value** to write. 13 | - `n`: The **number of bytes** to fill. 14 | 2. **Converts** `d` and `n` to `usize` and `val` to `u8`. 15 | 3. **If fuel metering is enabled**, it: 16 | - **Consumes fuel (`fc`)** based on `n` bytes. 17 | - If fuel is insufficient, execution **traps**. 18 | 4. **Validates** the memory range: 19 | - If the offset and length **exceed available memory**, execution **traps** (`TrapCode::MemoryOutOfBounds`). 20 | 5. **Fills** the memory at `[d..d+n]` with `val`. 21 | 6. **Logs** memory changes if tracing is enabled. 22 | 7. **Increments** the instruction pointer (`ip`) by `1`. 23 | 24 | ### **Registers and Memory Changes** 25 | 26 | - **instruction pointer (`ip`)**: **Increased by `1`**. 27 | - **stack pointer (`sp`)**: **Decremented by `3`** (pops `d`, `val`, and `n`). 28 | - **memory size (`ms`)**: **Unchanged**. 29 | - **memory**: **Modified** in the specified range. 30 | - **fuel counter (`fc`)**: 31 | - **Decreased** based on `n` bytes if fuel metering is enabled. 32 | - **Unchanged** if fuel metering is disabled. 33 | 34 | ### **Stack Changes** 35 | 36 | #### **Before Execution:** 37 | 38 | ``` 39 | [ ... | destination | byte_value | length | sp ] 40 | ``` 41 | 42 | #### **After Execution (Successful Fill):** 43 | 44 | ``` 45 | [ ... | sp ] 46 | ``` 47 | 48 | (`sp` moves down by `3`, as the parameters are removed.) 49 | 50 | #### **After Execution (Memory Out of Bounds - Trap):** 51 | 52 | - **Execution halts due to a `MemoryOutOfBounds` trap.** 53 | 54 | #### **After Execution (Insufficient Fuel - Trap):** 55 | 56 | - **Execution halts due to a fuel exhaustion trap.** 57 | 58 | ### **Operands** 59 | 60 | - **None** (operates based on the top three stack values). 61 | 62 | ### **Notes** 63 | 64 | - **Writing outside allocated memory results in a trap**. 65 | - If `n` is `0`, **no memory is modified**. 66 | - If fuel metering is enabled and there is **not enough fuel to write `n` bytes, execution traps**. 67 | - The instruction **does not modify `ms`** but writes directly to memory. -------------------------------------------------------------------------------- /docs/instructions/50_memory_copy.md: -------------------------------------------------------------------------------- 1 | ## `memory_copy` 2 | 3 | ### **Description** 4 | 5 | The `memory_copy` instruction **copies a specified number of bytes from one memory location to another**. It reads the 6 | destination offset, source offset, and length from the stack and performs a memory copy operation. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Pops** three values from the stack: 11 | - `d`: The **destination offset** in memory. 12 | - `s`: The **source offset** in memory. 13 | - `n`: The **number of bytes** to copy. 14 | 2. **Converts** `s`, `d`, and `n` to `usize`. 15 | 3. **If fuel metering is enabled**, it: 16 | - **Consumes fuel (`fc`)** based on `n` bytes. 17 | - If fuel is insufficient, execution **traps**. 18 | 4. **Validates** the memory range: 19 | - If the source or destination range **exceeds available memory**, execution **traps** ( 20 | `TrapCode::MemoryOutOfBounds`). 21 | 5. **Performs** the memory copy from `[s..s+n]` to `[d..d+n]`. 22 | 6. **Logs** memory changes if tracing is enabled. 23 | 7. **Increments** the instruction pointer (`ip`) by `1`. 24 | 25 | ### **Registers and Memory Changes** 26 | 27 | - **instruction pointer (`ip`)**: **Increased by `1`**. 28 | - **stack pointer (`sp`)**: **Decremented by `3`** (pops `d`, `s`, and `n`). 29 | - **memory size (`ms`)**: **Unchanged**. 30 | - **memory**: **Modified** at the destination range. 31 | - **fuel counter (`fc`)**: 32 | - **Decreased** based on `n` bytes if fuel metering is enabled. 33 | - **Unchanged** if fuel metering is disabled. 34 | 35 | ### **Stack Changes** 36 | 37 | #### **Before Execution:** 38 | 39 | ``` 40 | [ ... | destination | source | length | sp ] 41 | ``` 42 | 43 | #### **After Execution (Successful Copy):** 44 | 45 | ``` 46 | [ ... | sp ] 47 | ``` 48 | 49 | (`sp` moves down by `3`, as the parameters are removed.) 50 | 51 | #### **After Execution (Memory Out of Bounds - Trap):** 52 | 53 | - **Execution halts due to a `MemoryOutOfBounds` trap.** 54 | 55 | #### **After Execution (Insufficient Fuel - Trap):** 56 | 57 | - **Execution halts due to a fuel exhaustion trap.** 58 | 59 | ### **Operands** 60 | 61 | - **None** (operates based on the top three stack values). 62 | 63 | ### **Notes** 64 | 65 | - **Copying outside allocated memory results in a trap**. 66 | - If `n` is `0`, **no memory is modified**. 67 | - If the **source and destination regions overlap**, the copy is performed **correctly** using `copy_within`. 68 | - The instruction **does not modify `ms`** but writes directly to memory. -------------------------------------------------------------------------------- /docs/instructions/51_memory_init.md: -------------------------------------------------------------------------------- 1 | ## `memory_init` 2 | 3 | ### **Description** 4 | 5 | The `memory_init` instruction **copies data from a specified data segment into linear memory**. It reads the destination 6 | offset, source offset, and length from the stack and performs a memory initialization operation. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Checks** if the data segment at `data_segment_idx` is empty. 11 | 2. **Pops** three values from the stack: 12 | - `d`: The **destination offset** in memory. 13 | - `s`: The **source offset** in the data segment. 14 | - `n`: The **number of bytes** to copy. 15 | 3. **Converts** `s`, `d`, and `n` to `usize`. 16 | 4. **If fuel metering is enabled**, it: 17 | - **Consumes fuel (`fc`)** based on `n` bytes. 18 | - If fuel is insufficient, execution **traps**. 19 | 5. **Validates** the memory range: 20 | - If the destination range **exceeds available memory**, execution **traps** (`TrapCode::MemoryOutOfBounds`). 21 | - If the source offset **exceeds the data segment bounds**, execution **traps**. 22 | 6. **Copies** `n` bytes from the data segment into memory at `[d..d+n]`. 23 | 7. **Logs** memory changes if tracing is enabled. 24 | 8. **Increments** the instruction pointer (`ip`) by `1`. 25 | 26 | ### **Registers and Memory Changes** 27 | 28 | - **instruction pointer (`ip`)**: **Increased by `1`**. 29 | - **stack pointer (`sp`)**: **Decremented by `3`** (pops `d`, `s`, and `n`). 30 | - **memory size (`ms`)**: **Unchanged**. 31 | - **memory**: **Modified** in the destination range. 32 | - **fuel counter (`fc`)**: 33 | - **Decreased** based on `n` bytes if fuel metering is enabled. 34 | - **Unchanged** if fuel metering is disabled. 35 | 36 | ### **Stack Changes** 37 | 38 | #### **Before Execution:** 39 | 40 | ``` 41 | [ ... | destination | source | length | sp ] 42 | ``` 43 | 44 | #### **After Execution (Successful Initialization):** 45 | 46 | ``` 47 | [ ... | sp ] 48 | ``` 49 | 50 | (`sp` moves down by `3`, as the parameters are removed.) 51 | 52 | #### **After Execution (Memory Out of Bounds - Trap):** 53 | 54 | - **Execution halts due to a `MemoryOutOfBounds` trap.** 55 | 56 | #### **After Execution (Insufficient Fuel - Trap):** 57 | 58 | - **Execution halts due to a fuel exhaustion trap.** 59 | 60 | ### **Operands** 61 | 62 | - `data_segment_idx` (integer): Specifies the data segment from which to copy. 63 | 64 | ### **Notes** 65 | 66 | - **Copying outside allocated memory or data segment bounds results in a trap**. 67 | - If `n` is `0`, **no memory is modified**. 68 | - If the data segment is **empty**, the instruction reads an empty buffer. 69 | - The instruction **does not modify `ms`** but writes directly to memory. -------------------------------------------------------------------------------- /docs/instructions/52_data_drop.md: -------------------------------------------------------------------------------- 1 | ## `data_drop` 2 | 3 | ### **Description** 4 | 5 | The `data_drop` instruction **marks a specified data segment as dropped**, making it unavailable for future memory 6 | initialization operations. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Resolves** the data segment using `data_segment_idx`. 11 | 2. **Drops** the contents of the data segment, making it inaccessible. 12 | 3. **Increments** the instruction pointer (`ip`) by `1` to proceed to the next instruction. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **instruction pointer (`ip`)**: **Increased by `1`**. 17 | - **stack pointer (`sp`)**: **Unchanged** (this instruction does not modify the stack). 18 | - **memory size (`ms`)**: **Unchanged**. 19 | - **memory**: **Unchanged** (only marks the data segment as dropped). 20 | - **fuel counter (`fc`)**: **Unchanged**. 21 | 22 | ### **Stack Changes** 23 | 24 | #### **Before Execution:** 25 | 26 | ``` 27 | [ ... | sp ] 28 | ``` 29 | 30 | #### **After Execution:** 31 | 32 | ``` 33 | [ ... | sp ] 34 | ``` 35 | 36 | (The stack remains unchanged.) 37 | 38 | ### **Operands** 39 | 40 | - `data_segment_idx` (integer): The index of the data segment to drop. 41 | 42 | ### **Notes** 43 | 44 | - **Dropped data segments can no longer be used in `memory_init` operations**. 45 | - The instruction does **not** modify memory but only marks data as unavailable. 46 | - Attempting to use a dropped data segment behaves as empty data segments. -------------------------------------------------------------------------------- /docs/instructions/53_table_size.md: -------------------------------------------------------------------------------- 1 | ## `table_size` 2 | 3 | ### **Description** 4 | 5 | The `table_size` instruction **retrieves the current size of a specified table** and pushes it onto the stack. 6 | 7 | ### **Behavior** 8 | 9 | 1. **Fetches** the table at `table_idx`. 10 | - If the table does not exist, execution **traps** (`unresolved table segment`). 11 | 2. **Retrieves** the current size of the table. 12 | 3. **Pushes** the table size onto the stack. 13 | 4. **Increments** the instruction pointer (`ip`) by `1`. 14 | 15 | ### **Registers and Memory Changes** 16 | 17 | - **instruction pointer (`ip`)**: **Increased by `1`**. 18 | - **stack pointer (`sp`)**: **Incremented by `1`** (stores the table size). 19 | - **memory size (`ms`)**: **Unchanged**. 20 | - **memory**: **Unchanged** (tables are separate from linear memory). 21 | - **fuel counter (`fc`)**: **Unchanged**. 22 | 23 | ### **Stack Changes** 24 | 25 | #### **Before Execution:** 26 | 27 | ``` 28 | [ ... | sp ] 29 | ``` 30 | 31 | #### **After Execution:** 32 | 33 | ``` 34 | [ ... | table_size | sp ] 35 | ``` 36 | 37 | (The stack pointer moves up by `1`, and the table size is pushed onto the stack.) 38 | 39 | ### **Operands** 40 | 41 | - `table_idx` (integer): The index of the table to fetch the size from. 42 | 43 | ### **Notes** 44 | 45 | - If `table_idx` refers to an **uninitialized table**, execution **traps**. 46 | - This instruction **does not modify memory or table contents**, only retrieves the size. 47 | - The returned size is measured in **table elements**, not bytes. -------------------------------------------------------------------------------- /docs/instructions/54_table_grow.md: -------------------------------------------------------------------------------- 1 | ## `table_grow` 2 | 3 | ### **Description** 4 | 5 | The `table_grow` instruction **attempts to increase the size of a table** by a specified number of elements. If 6 | successful, it pushes the previous table size onto the stack; otherwise, it pushes `u32::MAX`. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Pops** two values from the stack: 11 | - `init`: The initial value to fill the new table entries. 12 | - `delta`: The number of elements to add to the table. 13 | 2. **Converts** `delta` to `u32`. 14 | 3. **If fuel metering is enabled**, it: 15 | - **Consumes fuel (`fc`)** based on `delta` elements. 16 | - If fuel is insufficient, execution **traps**. 17 | 4. **Attempts** to grow the table: 18 | - If successful, **pushes the previous table size** onto the stack. 19 | - If unsuccessful due to an invalid growth request, **pushes `u32::MAX`**. 20 | - If growth fails due to a trap condition, execution **traps**. 21 | 5. **Logs** the table size change if tracing is enabled. 22 | 6. **Increments** the instruction pointer (`ip`) by `1`. 23 | 24 | ### **Registers and Memory Changes** 25 | 26 | - **instruction pointer (`ip`)**: **Increased by `1`**. 27 | - **stack pointer (`sp`)**: **Unchanged in count** (pops `delta` and `init`, then pushes the previous table size or 28 | `u32::MAX`). 29 | - **memory size (`ms`)**: **Unchanged**. 30 | - **memory**: **Unchanged** (table storage is separate). 31 | - **fuel counter (`fc`)**: 32 | - **Decreased** based on `delta` elements if fuel metering is enabled. 33 | - **Unchanged** if fuel metering is disabled. 34 | 35 | ### **Stack Changes** 36 | 37 | #### **Before Execution:** 38 | 39 | ``` 40 | [ ... | init | delta | sp ] 41 | ``` 42 | 43 | #### **After Execution (Successful Growth):** 44 | 45 | ``` 46 | [ ... | previous_table_size | sp ] 47 | ``` 48 | 49 | (The previous table size is stored, `sp` remains at the same level.) 50 | 51 | #### **After Execution (Failed Growth - Invalid Request):** 52 | 53 | ``` 54 | [ ... | u32::MAX | sp ] 55 | ``` 56 | 57 | (`sp` remains at the same level, and `u32::MAX` is pushed to indicate failure.) 58 | 59 | #### **After Execution (Insufficient Fuel - Trap):** 60 | 61 | - **Execution halts due to a fuel exhaustion trap.** 62 | 63 | #### **After Execution (Trap Condition - Table Growth Failure):** 64 | 65 | - **Execution halts due to a table-related trap.** 66 | 67 | ### **Operands** 68 | 69 | - `table_idx` (integer): The index of the table to grow. 70 | 71 | ### **Notes** 72 | 73 | - **Attempting to grow beyond the maximum table size results in failure (`u32::MAX`).** 74 | - If `delta` is `0`, **no changes occur, and the previous table size is returned**. 75 | - If fuel metering is enabled and there is **not enough fuel for `delta` elements, execution traps**. 76 | - The instruction **does not modify `ms`** but affects table storage. -------------------------------------------------------------------------------- /docs/instructions/55_table_fill.md: -------------------------------------------------------------------------------- 1 | ## `table_fill` 2 | 3 | ### **Description** 4 | 5 | The `table_fill` instruction **fills a specified range of elements in a table** with a given value. It reads the 6 | starting index, value, and count from the stack and performs the fill operation. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Pops** three values from the stack: 11 | - `i`: The **starting index** in the table. 12 | - `val`: The **value** to be written into the table. 13 | - `n`: The **number of table entries** to fill. 14 | 2. **If fuel metering is enabled**, it: 15 | - **Consumes fuel (`fc`)** based on `n` elements. 16 | - If fuel is insufficient, execution **traps**. 17 | 3. **Attempts** to fill `n` table entries starting from index `i` with `val`: 18 | - If `i + n` **exceeds the table size**, execution **traps**. 19 | - If successful, the table entries are updated. 20 | 4. **Increments** the instruction pointer (`ip`) by `1`. 21 | 22 | ### **Registers and Memory Changes** 23 | 24 | - **instruction pointer (`ip`)**: **Increased by `1`**. 25 | - **stack pointer (`sp`)**: **Decremented by `3`** (pops `i`, `val`, and `n`). 26 | - **memory size (`ms`)**: **Unchanged**. 27 | - **memory**: **Unchanged** (tables are separate from linear memory). 28 | - **fuel counter (`fc`)**: 29 | - **Decreased** based on `n` elements if fuel metering is enabled. 30 | - **Unchanged** if fuel metering is disabled. 31 | 32 | ### **Stack Changes** 33 | 34 | #### **Before Execution:** 35 | 36 | ``` 37 | [ ... | index | value | count | sp ] 38 | ``` 39 | 40 | #### **After Execution (Successful Fill):** 41 | 42 | ``` 43 | [ ... | sp ] 44 | ``` 45 | 46 | (`sp` moves down by `3`, as the parameters are removed.) 47 | 48 | #### **After Execution (Table Out of Bounds - Trap):** 49 | 50 | - **Execution halts due to a table access trap.** 51 | 52 | #### **After Execution (Insufficient Fuel - Trap):** 53 | 54 | - **Execution halts due to a fuel exhaustion trap.** 55 | 56 | ### **Operands** 57 | 58 | - `table_idx` (integer): The index of the table to fill. 59 | 60 | ### **Notes** 61 | 62 | - **Writing outside the table bounds results in a trap**. 63 | - If `n` is `0`, **no table entries are modified**. 64 | - If fuel metering is enabled and there is **not enough fuel for `n` elements, execution traps**. 65 | - The instruction **does not modify `ms`** but affects table storage. -------------------------------------------------------------------------------- /docs/instructions/56_table_get.md: -------------------------------------------------------------------------------- 1 | ## `table_get` 2 | 3 | ### **Description** 4 | 5 | The `table_get` instruction **retrieves an element from a specified table** at a given index and pushes it onto the 6 | stack. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Pops** the index (`index`) from the stack. 11 | 2. **Retrieves** the element at `index` from the table identified by `table_idx`: 12 | - If `index` is **out of bounds**, execution **traps** (`TrapCode::TableOutOfBounds`). 13 | - If the retrieval is successful, the element is pushed onto the stack. 14 | 3. **Increments** the instruction pointer (`ip`) by `1`. 15 | 16 | ### **Registers and Memory Changes** 17 | 18 | - **instruction pointer (`ip`)**: **Increased by `1`**. 19 | - **stack pointer (`sp`)**: **Unchanged in count** (pops `index`, then pushes the retrieved value). 20 | - **memory size (`ms`)**: **Unchanged**. 21 | - **memory**: **Unchanged** (tables are separate from linear memory). 22 | - **fuel counter (`fc`)**: **Unchanged**. 23 | 24 | ### **Stack Changes** 25 | 26 | #### **Before Execution:** 27 | 28 | ``` 29 | [ ... | index | sp ] 30 | ``` 31 | 32 | #### **After Execution (Successful Retrieval):** 33 | 34 | ``` 35 | [ ... | value | sp ] 36 | ``` 37 | 38 | (`index` is replaced by the retrieved value.) 39 | 40 | #### **After Execution (Table Out of Bounds - Trap):** 41 | 42 | - **Execution halts due to a `TableOutOfBounds` trap.** 43 | 44 | ### **Operands** 45 | 46 | - `table_idx` (integer): The index of the table from which to retrieve the value. 47 | 48 | ### **Notes** 49 | 50 | - **Attempting to access an index beyond the table’s size results in a trap**. 51 | - The instruction **does not modify `ms`** but operates on table storage. 52 | - Used for **retrieving function references or other stored elements** from tables. -------------------------------------------------------------------------------- /docs/instructions/57_table_set.md: -------------------------------------------------------------------------------- 1 | ## `table_set` 2 | 3 | ### **Description** 4 | 5 | The `table_set` instruction **stores a value in a specified table** at a given index. If the index is out of bounds, 6 | execution traps. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Pops** two values from the stack: 11 | - `index`: The **table index** where the value will be stored. 12 | - `value`: The **value** to store in the table. 13 | 2. **Attempts** to set `value` at `index` in the table identified by `table_idx`: 14 | - If `index` is **out of bounds**, execution **traps** (`TrapCode::TableOutOfBounds`). 15 | - If successful, `value` is stored at `index`. 16 | 3. **Logs** the table change if tracing is enabled. 17 | 4. **Increments** the instruction pointer (`ip`) by `1`. 18 | 19 | ### **Registers and Memory Changes** 20 | 21 | - **instruction pointer (`ip`)**: **Increased by `1`**. 22 | - **stack pointer (`sp`)**: **Decremented by `2`** (pops `index` and `value`). 23 | - **memory size (`ms`)**: **Unchanged**. 24 | - **memory**: **Unchanged** (tables are separate from linear memory). 25 | - **fuel counter (`fc`)**: **Unchanged**. 26 | 27 | ### **Stack Changes** 28 | 29 | #### **Before Execution:** 30 | 31 | ``` 32 | [ ... | index | value | sp ] 33 | ``` 34 | 35 | #### **After Execution (Successful Set):** 36 | 37 | ``` 38 | [ ... | sp ] 39 | ``` 40 | 41 | (`sp` moves down by `2`, as the parameters are removed.) 42 | 43 | #### **After Execution (Table Out of Bounds - Trap):** 44 | 45 | - **Execution halts due to a `TableOutOfBounds` trap.** 46 | 47 | ### **Operands** 48 | 49 | - `table_idx` (integer): The index of the table to store the value. 50 | 51 | ### **Notes** 52 | 53 | - **Writing outside the table bounds results in a trap**. 54 | - The instruction **does not modify `ms`** but operates on table storage. 55 | - Used for **storing function references or other values** in tables. -------------------------------------------------------------------------------- /docs/instructions/58_table_copy.md: -------------------------------------------------------------------------------- 1 | ## `table_copy` 2 | 3 | ### **Description** 4 | 5 | The `table_copy` instruction **copies elements from one table to another (or within the same table)**. It reads the 6 | destination index, source index, and count from the stack and performs the copy operation. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Fetches** the source table index (`src_table_idx`) from memory. 11 | 2. **Pops** three values from the stack: 12 | - `d`: The **destination index** in the destination table. 13 | - `s`: The **source index** in the source table. 14 | - `n`: The **number of table elements** to copy. 15 | 3. **Converts** `s`, `d`, and `n` to `u32`. 16 | 4. **If fuel metering is enabled**, it: 17 | - **Consumes fuel (`fc`)** based on `n` elements. 18 | - If fuel is insufficient, execution **traps**. 19 | 5. **Validates** the source and destination indices: 20 | - If `s + n` or `d + n` **exceeds the table size**, execution **traps**. 21 | 6. **Performs** the table copy: 22 | - If the source and destination tables **are different**, it copies elements between tables. 23 | - If they **are the same**, it performs an internal `copy_within` operation. 24 | 7. **Increments** the instruction pointer (`ip`) by `2`. 25 | 26 | ### **Registers and Memory Changes** 27 | 28 | - **instruction pointer (`ip`)**: **Increased by `2`**. 29 | - **stack pointer (`sp`)**: **Decremented by `3`** (pops `d`, `s`, and `n`). 30 | - **memory size (`ms`)**: **Unchanged**. 31 | - **memory**: **Unchanged** (tables are separate from linear memory). 32 | - **fuel counter (`fc`)**: 33 | - **Decreased** based on `n` elements if fuel metering is enabled. 34 | - **Unchanged** if fuel metering is disabled. 35 | 36 | ### **Stack Changes** 37 | 38 | #### **Before Execution:** 39 | 40 | ``` 41 | [ ... | destination | source | count | sp ] 42 | ``` 43 | 44 | #### **After Execution (Successful Copy):** 45 | 46 | ``` 47 | [ ... | sp ] 48 | ``` 49 | 50 | (`sp` moves down by `3`, as the parameters are removed.) 51 | 52 | #### **After Execution (Table Out of Bounds - Trap):** 53 | 54 | - **Execution halts due to a table access trap.** 55 | 56 | #### **After Execution (Insufficient Fuel - Trap):** 57 | 58 | - **Execution halts due to a fuel exhaustion trap.** 59 | 60 | ### **Operands** 61 | 62 | - `dst_table_idx` (integer): The index of the destination table. 63 | - `src_table_idx` (retrieved from memory): The index of the source table. 64 | 65 | ### **Notes** 66 | 67 | - **Copying outside allocated table bounds results in a trap**. 68 | - If `n` is `0`, **no table elements are modified**. 69 | - If fuel metering is enabled and there is **not enough fuel for `n` elements, execution traps**. 70 | - The instruction **does not modify `ms`** but affects table storage. -------------------------------------------------------------------------------- /docs/instructions/59_table_init.md: -------------------------------------------------------------------------------- 1 | ## `table_init` 2 | 3 | ### **Description** 4 | 5 | The `table_init` instruction **copies elements from an element segment into a table**. It reads the destination index, 6 | source index, and number of elements from the stack and initializes the table. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Fetches** the table index (`table_idx`) from memory. 11 | 2. **Pops** three values from the stack: 12 | - `d`: The **destination index** in the table. 13 | - `s`: The **source index** in the element segment. 14 | - `n`: The **number of elements** to copy. 15 | 3. **Converts** `s`, `d`, and `n` to `u32`. 16 | 4. **If fuel metering is enabled**, it: 17 | - **Consumes fuel (`fc`)** based on `n` elements. 18 | - If fuel is insufficient, execution **traps**. 19 | 5. **Validates** the element segment: 20 | - If the segment is **empty**, an empty placeholder is used. 21 | - If the element segment has been dropped, execution **traps**. 22 | 6. **Validates** the source and destination indices: 23 | - If `s + n` or `d + n` **exceeds the segment or table size**, execution **traps**. 24 | 7. **Copies** `n` elements from the element segment into the table. 25 | 8. **Increments** the instruction pointer (`ip`) by `2`. 26 | 27 | ### **Registers and Memory Changes** 28 | 29 | - **instruction pointer (`ip`)**: **Increased by `2`**. 30 | - **stack pointer (`sp`)**: **Decremented by `3`** (pops `d`, `s`, and `n`). 31 | - **memory size (`ms`)**: **Unchanged**. 32 | - **memory**: **Unchanged** (tables are separate from linear memory). 33 | - **fuel counter (`fc`)**: 34 | - **Decreased** based on `n` elements if fuel metering is enabled. 35 | - **Unchanged** if fuel metering is disabled. 36 | 37 | ### **Stack Changes** 38 | 39 | #### **Before Execution:** 40 | 41 | ``` 42 | [ ... | destination | source | count | sp ] 43 | ``` 44 | 45 | #### **After Execution (Successful Initialization):** 46 | 47 | ``` 48 | [ ... | sp ] 49 | ``` 50 | 51 | (`sp` moves down by `3`, as the parameters are removed.) 52 | 53 | #### **After Execution (Table or Segment Out of Bounds - Trap):** 54 | 55 | - **Execution halts due to a table or segment access trap.** 56 | 57 | #### **After Execution (Insufficient Fuel - Trap):** 58 | 59 | - **Execution halts due to a fuel exhaustion trap.** 60 | 61 | ### **Operands** 62 | 63 | - `element_segment_idx` (integer): The index of the element segment to initialize the table. 64 | 65 | ### **Notes** 66 | 67 | - **Copying outside allocated table bounds or element segment bounds results in a trap**. 68 | - If `n` is `0`, **no table elements are modified**. 69 | - If fuel metering is enabled and there is **not enough fuel for `n` elements, execution traps**. 70 | - **Dropped element segments cannot be accessed**. 71 | - The instruction **does not modify `ms`** but affects table storage. -------------------------------------------------------------------------------- /docs/instructions/60_element_drop.md: -------------------------------------------------------------------------------- 1 | ## `element_drop` 2 | 3 | ### **Description** 4 | 5 | The `element_drop` instruction **marks a specified element segment as dropped**, making it unavailable for future table 6 | initialization operations. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Resolves** the element segment using `element_segment_idx`. 11 | 2. **Drops** the contents of the element segment, making it inaccessible. 12 | 3. **Increments** the instruction pointer (`ip`) by `1` to proceed to the next instruction. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **instruction pointer (`ip`)**: **Increased by `1`**. 17 | - **stack pointer (`sp`)**: **Unchanged** (this instruction does not modify the stack). 18 | - **memory size (`ms`)**: **Unchanged**. 19 | - **memory**: **Unchanged** (only marks the element segment as dropped). 20 | - **fuel counter (`fc`)**: **Unchanged**. 21 | 22 | ### **Stack Changes** 23 | 24 | #### **Before Execution:** 25 | 26 | ``` 27 | [ ... | sp ] 28 | ``` 29 | 30 | #### **After Execution:** 31 | 32 | ``` 33 | [ ... | sp ] 34 | ``` 35 | 36 | (The stack remains unchanged.) 37 | 38 | ### **Operands** 39 | 40 | - `element_segment_idx` (integer): The index of the element segment to drop. 41 | 42 | ### **Notes** 43 | 44 | - **Dropped element segments can no longer be used in `table_init` operations**. 45 | - The instruction does **not** modify memory but only marks the element segment as unavailable. 46 | - Attempting to use a dropped element segment **may result in a trap** in future instructions. -------------------------------------------------------------------------------- /docs/instructions/61_ref_func.md: -------------------------------------------------------------------------------- 1 | ## `ref_func` 2 | 3 | ### **Description** 4 | 5 | The `ref_func` instruction **pushes a reference to a function onto the stack**. This function reference can be used for 6 | indirect function calls or stored in a table. 7 | 8 | ### **Behavior** 9 | 10 | 1. **Computes** the function reference by adding `FUNC_REF_OFFSET` to `func_idx`. 11 | 2. **Pushes** the computed function reference onto the stack. 12 | 3. **Increments** the instruction pointer (`ip`) by `1` to proceed to the next instruction. 13 | 14 | ### **Registers and Memory Changes** 15 | 16 | - **instruction pointer (`ip`)**: **Increased by `1`**. 17 | - **stack pointer (`sp`)**: **Incremented by `1`** (stores the function reference). 18 | - **memory size (`ms`)**: **Unchanged**. 19 | - **memory**: **Unchanged** (function references do not modify memory). 20 | - **fuel counter (`fc`)**: **Unchanged**. 21 | 22 | ### **Stack Changes** 23 | 24 | #### **Before Execution:** 25 | 26 | ``` 27 | [ ... | sp ] 28 | ``` 29 | 30 | #### **After Execution:** 31 | 32 | ``` 33 | [ ... | function_reference | sp ] 34 | ``` 35 | 36 | (The stack pointer moves up by `1`, and the function reference is pushed onto the stack.) 37 | 38 | ### **Operands** 39 | 40 | - `func_idx` (integer): The index of the function whose reference is being retrieved. 41 | 42 | ### **Notes** 43 | 44 | - **Function references are used in indirect function calls or stored in tables**. 45 | - This instruction does **not** call the function, only pushes its reference. 46 | - The instruction **does not modify `ms`** or memory, as function references exist separately. -------------------------------------------------------------------------------- /docs/instructions/62_i32_const.md: -------------------------------------------------------------------------------- 1 | ## `i32_const` 2 | 3 | ### **Description** 4 | 5 | The `i32_const` instruction **pushes a constant 32-bit integer onto the stack**. 6 | 7 | ### **Behavior** 8 | 9 | 1. **Pushes** the immediate value (`untyped_value`) onto the stack. 10 | 2. **Increments** the instruction pointer (`ip`) by `1` to proceed to the next instruction. 11 | 12 | ### **Registers and Memory Changes** 13 | 14 | - **instruction pointer (`ip`)**: **Increased by `1`**. 15 | - **stack pointer (`sp`)**: **Incremented by `1`** (stores the constant value). 16 | - **memory size (`ms`)**: **Unchanged**. 17 | - **memory**: **Unchanged** (constants are stored on the stack, not in memory). 18 | - **fuel counter (`fc`)**: **Unchanged**. 19 | 20 | ### **Stack Changes** 21 | 22 | #### **Before Execution:** 23 | 24 | ``` 25 | [ ... | sp ] 26 | ``` 27 | 28 | #### **After Execution:** 29 | 30 | ``` 31 | [ ... | constant_value | sp ] 32 | ``` 33 | 34 | (The stack pointer moves up by `1`, and the constant value is pushed onto the stack.) 35 | 36 | ### **Operands** 37 | 38 | - `untyped_value` (i32): The 32-bit integer constant to be pushed onto the stack. 39 | 40 | ### **Notes** 41 | 42 | - This instruction **does not modify memory** and is only used to load immediate values onto the stack. 43 | - Commonly used in expressions and arithmetic operations. 44 | - The instruction **does not modify `ms`** or consume fuel. -------------------------------------------------------------------------------- /e2e/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwasm-e2e" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | rwasm = { path = ".." } 8 | rwasm-legacy = { path = "../legacy" } 9 | anyhow = "1.0.71" 10 | wast = "=62.0.0" 11 | 12 | [features] 13 | default = ["e2e"] 14 | e2e = ["rwasm-legacy/e2e"] 15 | debug-print = ["rwasm/debug-print"] 16 | -------------------------------------------------------------------------------- /e2e/README.md: -------------------------------------------------------------------------------- 1 | In this crate we store rewritten WASMI's e2e spec tests from WebAssembly to test rWASM codegen and compilation. 2 | Right now it passes 99% of cases except 4 very tricky corner cases with global variables exports that we can't fully support. 3 | Bringing support of these features makes not much sense for sandbox environment that Fluent runs. -------------------------------------------------------------------------------- /e2e/src/descriptor.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | fmt::{self, Display}, 3 | fs, 4 | }; 5 | use wast::token::Span; 6 | 7 | /// The desciptor of a Wasm spec test suite run. 8 | #[derive(Debug)] 9 | pub struct TestDescriptor { 10 | /// The path of the Wasm spec test `.wast` file. 11 | path: String, 12 | /// The contents of the Wasm spec test `.wast` file. 13 | file: String, 14 | } 15 | 16 | impl TestDescriptor { 17 | /// Creates a new Wasm spec [`TestDescriptor`]. 18 | /// 19 | /// # Errors 20 | /// 21 | /// If the corresponding Wasm test spec file cannot properly be read. 22 | pub fn new(name: &str) -> Self { 23 | let path = format!("{name}.wast"); 24 | let file = fs::read_to_string(&path) 25 | .unwrap_or_else(|error| panic!("{path}, failed to read `.wast` test file: {error}")); 26 | Self { path, file } 27 | } 28 | 29 | /// Returns the path of the Wasm spec test `.wast` file. 30 | pub fn path(&self) -> &str { 31 | &self.path 32 | } 33 | 34 | /// Returns the contents of the Wasm spec test `.wast` file. 35 | pub fn file(&self) -> &str { 36 | &self.file 37 | } 38 | 39 | /// Creates a [`TestSpan`] which can be used to print the location within the `.wast` test file. 40 | pub fn spanned(&self, span: Span) -> TestSpan { 41 | TestSpan { 42 | path: self.path(), 43 | contents: self.file(), 44 | span, 45 | } 46 | } 47 | } 48 | 49 | /// Useful for printing the location where the `.wast` parse is located. 50 | #[derive(Debug)] 51 | pub struct TestSpan<'a> { 52 | /// The file path of the `.wast` test. 53 | path: &'a str, 54 | /// The file contents of the `.wast` test. 55 | contents: &'a str, 56 | /// The line and column within the `.wast` test file. 57 | span: Span, 58 | } 59 | 60 | impl Display for TestSpan<'_> { 61 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 62 | let (line, col) = self.span.linecol_in(self.contents); 63 | write!(f, "{}:{line}:{col}", self.path) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /e2e/src/error.rs: -------------------------------------------------------------------------------- 1 | use rwasm::RwasmError; 2 | use rwasm_legacy::Error as WasmiError; 3 | use std::{error::Error, fmt, fmt::Display}; 4 | 5 | /// Errors that may occur upon Wasm spec test suite execution. 6 | #[derive(Debug)] 7 | pub enum TestError { 8 | Wasmi(WasmiError), 9 | Rwasm(RwasmError), 10 | InstanceNotRegistered { 11 | name: String, 12 | }, 13 | NoModuleInstancesFound, 14 | FuncNotFound { 15 | module_name: Option, 16 | func_name: String, 17 | }, 18 | GlobalNotFound { 19 | module_name: Option, 20 | global_name: String, 21 | }, 22 | } 23 | 24 | impl Error for TestError {} 25 | 26 | impl Display for TestError { 27 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 28 | match self { 29 | Self::InstanceNotRegistered { name } => { 30 | write!(f, "missing module instance with name: {name}") 31 | } 32 | Self::NoModuleInstancesFound => { 33 | write!(f, "found no module instances registered so far") 34 | } 35 | Self::FuncNotFound { 36 | module_name, 37 | func_name, 38 | } => { 39 | write!(f, "missing func exported as: {module_name:?}::{func_name}",) 40 | } 41 | Self::GlobalNotFound { 42 | module_name, 43 | global_name, 44 | } => { 45 | write!( 46 | f, 47 | "missing global variable exported as: {module_name:?}::{global_name}", 48 | ) 49 | } 50 | Self::Wasmi(wasmi_error) => Display::fmt(wasmi_error, f), 51 | Self::Rwasm(rwasm_error) => Display::fmt(rwasm_error, f), 52 | } 53 | } 54 | } 55 | 56 | impl From for TestError 57 | where 58 | E: Into, 59 | { 60 | fn from(error: E) -> Self { 61 | Self::Wasmi(error.into()) 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /e2e/src/handler.rs: -------------------------------------------------------------------------------- 1 | use rwasm::{Caller, RwasmError, SyscallHandler}; 2 | 3 | pub const ENTRYPOINT_FUNC_IDX: u32 = u32::MAX; 4 | 5 | pub const FUNC_PRINT: u32 = 100; 6 | pub const FUNC_PRINT_I32: u32 = 101; 7 | pub const FUNC_PRINT_I64: u32 = 102; 8 | pub const FUNC_PRINT_F32: u32 = 103; 9 | pub const FUNC_PRINT_F64: u32 = 104; 10 | pub const FUNC_PRINT_I32_F32: u32 = 105; 11 | pub const FUNC_PRINT_I64_F64: u32 = 106; 12 | 13 | #[derive(Default)] 14 | pub struct TestingContext { 15 | pub program_counter: u32, 16 | pub state: u32, 17 | } 18 | 19 | pub(crate) fn testing_context_syscall_handler( 20 | mut caller: Caller, 21 | func_idx: u32, 22 | ) -> Result<(), RwasmError> { 23 | match func_idx { 24 | FUNC_PRINT => { 25 | println!("print"); 26 | Ok(()) 27 | } 28 | FUNC_PRINT_I32 => { 29 | let value = i32::from(caller.stack_pop()); 30 | println!("print: {value}"); 31 | Ok(()) 32 | } 33 | FUNC_PRINT_I64 => { 34 | let value = i64::from(caller.stack_pop()); 35 | println!("print: {value}"); 36 | Ok(()) 37 | } 38 | FUNC_PRINT_F32 => { 39 | let value = f32::from(caller.stack_pop()); 40 | println!("print: {value}"); 41 | Ok(()) 42 | } 43 | FUNC_PRINT_F64 => { 44 | let value = f64::from(caller.stack_pop()); 45 | println!("print: {value}"); 46 | Ok(()) 47 | } 48 | FUNC_PRINT_I32_F32 => { 49 | let (v0, v1) = caller.stack_pop2(); 50 | println!("print: {:?} {:?}", i32::from(v0), f32::from(v1)); 51 | Ok(()) 52 | } 53 | FUNC_PRINT_I64_F64 => { 54 | let (v0, v1) = caller.stack_pop2(); 55 | println!("print: {:?} {:?}", i64::from(v0), f64::from(v1)); 56 | Ok(()) 57 | } 58 | u32::MAX => { 59 | // yeah, dirty, but this is how we remember the program counter to reset, 60 | // since we're 100% sure the function is called using `Call` 61 | // that we can safely deduct 1 from PC (for `ReturnCall` we need to deduct 2) 62 | caller.context_mut().program_counter = caller.vm().program_counter() - 1; 63 | // push state value into the stack 64 | caller.stack_push(caller.context().state); 65 | Ok(()) 66 | } 67 | _ => todo!("not implemented syscall handler"), 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /e2e/src/local/missing-features/mutable-global-disabled.wast: -------------------------------------------------------------------------------- 1 | (assert_invalid 2 | (module 3 | (import "m0" "g0" (global (mut i32))) 4 | ) 5 | "mutable global support is not enabled" 6 | ) 7 | 8 | (assert_invalid 9 | (module 10 | (global $g0 (mut i32) (i32.const 0)) 11 | (export "g0" (global $g0)) 12 | ) 13 | "mutable global support is not enabled" 14 | ) 15 | -------------------------------------------------------------------------------- /e2e/src/local/missing-features/saturating-float-to-int-disabled.wast: -------------------------------------------------------------------------------- 1 | (assert_invalid 2 | (module 3 | (func (param f32) (result i32) 4 | local.get 0 5 | i32.trunc_sat_f32_s 6 | ) 7 | ) 8 | "saturating float to int conversions support is not enabled" 9 | ) 10 | 11 | (assert_invalid 12 | (module 13 | (func (param f32) (result i32) 14 | local.get 0 15 | i32.trunc_sat_f32_u 16 | ) 17 | ) 18 | "saturating float to int conversions support is not enabled" 19 | ) 20 | 21 | (assert_invalid 22 | (module 23 | (func (param f64) (result i32) 24 | local.get 0 25 | i32.trunc_sat_f64_s 26 | ) 27 | ) 28 | "saturating float to int conversions support is not enabled" 29 | ) 30 | 31 | (assert_invalid 32 | (module 33 | (func (param f64) (result i32) 34 | local.get 0 35 | i32.trunc_sat_f64_u 36 | ) 37 | ) 38 | "saturating float to int conversions support is not enabled" 39 | ) 40 | 41 | (assert_invalid 42 | (module 43 | (func (param f32) (result i64) 44 | local.get 0 45 | i64.trunc_sat_f32_s 46 | ) 47 | ) 48 | "saturating float to int conversions support is not enabled" 49 | ) 50 | 51 | (assert_invalid 52 | (module 53 | (func (param f32) (result i64) 54 | local.get 0 55 | i64.trunc_sat_f32_u 56 | ) 57 | ) 58 | "saturating float to int conversions support is not enabled" 59 | ) 60 | 61 | (assert_invalid 62 | (module 63 | (func (param f64) (result i64) 64 | local.get 0 65 | i64.trunc_sat_f64_s 66 | ) 67 | ) 68 | "saturating float to int conversions support is not enabled" 69 | ) 70 | 71 | (assert_invalid 72 | (module 73 | (func (param f64) (result i64) 74 | local.get 0 75 | i64.trunc_sat_f64_u 76 | ) 77 | ) 78 | "saturating float to int conversions support is not enabled" 79 | ) 80 | -------------------------------------------------------------------------------- /e2e/src/local/missing-features/sign-extension-disabled.wast: -------------------------------------------------------------------------------- 1 | (assert_invalid 2 | (module 3 | (func (param i32) (result i32) 4 | local.get 0 5 | i32.extend8_s 6 | ) 7 | ) 8 | "sign extension operations support is not enabled" 9 | ) 10 | 11 | (assert_invalid 12 | (module 13 | (func (param i32) (result i32) 14 | local.get 0 15 | i32.extend16_s 16 | ) 17 | ) 18 | "sign extension operations support is not enabled" 19 | ) 20 | 21 | (assert_invalid 22 | (module 23 | (func (param i64) (result i64) 24 | local.get 0 25 | i64.extend8_s 26 | ) 27 | ) 28 | "sign extension operations support is not enabled" 29 | ) 30 | 31 | (assert_invalid 32 | (module 33 | (func (param i64) (result i64) 34 | local.get 0 35 | i64.extend16_s 36 | ) 37 | ) 38 | "sign extension operations support is not enabled" 39 | ) 40 | 41 | (assert_invalid 42 | (module 43 | (func (param i64) (result i64) 44 | local.get 0 45 | i64.extend32_s 46 | ) 47 | ) 48 | "sign extension operations support is not enabled" 49 | ) 50 | -------------------------------------------------------------------------------- /e2e/src/profile.rs: -------------------------------------------------------------------------------- 1 | /// Test profiles collected during the Wasm spec test run. 2 | #[derive(Debug, Default)] 3 | pub struct TestProfile { 4 | /// The total amount of executed `.wast` directives. 5 | directives: usize, 6 | /// The amount of executed [`WasmDirective::Module`]. 7 | module: usize, 8 | /// The amount of executed [`WasmDirective::QuoteModule`]. 9 | quote_module: usize, 10 | /// The amount of executed [`WasmDirective::AssertMalformed`]. 11 | assert_malformed: usize, 12 | /// The amount of executed [`WasmDirective::AssertInvalid`]. 13 | assert_invalid: usize, 14 | /// The amount of executed [`WasmDirective::Register`]. 15 | register: usize, 16 | /// The amount of executed [`WasmDirective::Invoke`]. 17 | invoke: usize, 18 | /// The amount of executed [`WasmDirective::AssertTrap`]. 19 | assert_trap: usize, 20 | /// The amount of executed [`WasmDirective::AssertReturn`]. 21 | assert_return: usize, 22 | /// The amount of executed [`WasmDirective::AssertExhaustion`]. 23 | assert_exhaustion: usize, 24 | /// The amount of executed [`WasmDirective::AssertUnlinkable`]. 25 | assert_unlinkable: usize, 26 | /// The amount of executed [`WasmDirective::AssertException`]. 27 | assert_exception: usize, 28 | } 29 | 30 | impl TestProfile { 31 | /// Bumps the amount of directives. 32 | pub fn bump_directives(&mut self) { 33 | self.directives += 1; 34 | } 35 | 36 | /// Bumps the amount of [`WasmDirective::Module`] directives. 37 | pub fn bump_module(&mut self) { 38 | self.module += 1; 39 | } 40 | 41 | /// Bumps the amount of [`WasmDirective::QuoteModule`] directives. 42 | pub fn bump_quote_module(&mut self) { 43 | self.quote_module += 1; 44 | } 45 | 46 | /// Bumps the amount of [`WasmDirective::AssertMalformed`] directives. 47 | pub fn bump_assert_malformed(&mut self) { 48 | self.assert_malformed += 1; 49 | } 50 | 51 | /// Bumps the amount of [`WasmDirective::AssertInvalid`] directives. 52 | pub fn bump_assert_invalid(&mut self) { 53 | self.assert_invalid += 1; 54 | } 55 | 56 | /// Bumps the amount of [`WasmDirective::Register`] directives. 57 | pub fn bump_register(&mut self) { 58 | self.register += 1; 59 | } 60 | 61 | /// Bumps the amount of [`WasmDirective::Invoke`] directives. 62 | pub fn bump_invoke(&mut self) { 63 | self.invoke += 1; 64 | } 65 | 66 | /// Bumps the amount of [`WasmDirective::AssertTrap`] directives. 67 | pub fn bump_assert_trap(&mut self) { 68 | self.assert_trap += 1; 69 | } 70 | 71 | /// Bumps the amount of [`WasmDirective::AssertReturn`] directives. 72 | pub fn bump_assert_return(&mut self) { 73 | self.assert_return += 1; 74 | } 75 | 76 | /// Bumps the amount of [`WasmDirective::AssertExhaustion`] directives. 77 | pub fn bump_assert_exhaustion(&mut self) { 78 | self.assert_exhaustion += 1; 79 | } 80 | 81 | /// Bumps the amount of [`WasmDirective::AssertUnlinkable`] directives. 82 | pub fn bump_assert_unlinkable(&mut self) { 83 | self.assert_unlinkable += 1; 84 | } 85 | 86 | /// Bumps the amount of [`WasmDirective::AssertException`] directives. 87 | pub fn bump_assert_exception(&mut self) { 88 | self.assert_exception += 1; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /legacy/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rwasm-legacy" 3 | version = "0.30.0" 4 | documentation = "" 5 | description = "rwasm" 6 | edition = "2021" 7 | 8 | [dependencies] 9 | wasmparser = { version = "0.100.1", package = "wasmparser-nostd", default-features = false } 10 | spin = { version = "0.9", default-features = false, features = [ 11 | "mutex", 12 | "spin_mutex", 13 | "rwlock", 14 | ] } 15 | smallvec = { version = "1.10.0", features = ["union"] } 16 | libm = "0.2.1" 17 | num-traits = { version = "0.2", default-features = false } 18 | downcast-rs = { version = "1.2.0", default-features = false } 19 | paste = "1" 20 | byteorder = { version = "1.5.0", default-features = false } 21 | hashbrown = { version = "0.15.2", features = ["alloc"] } 22 | 23 | # strum is used only with test cfg 24 | strum = { version = "0.27.1", optional = true } 25 | strum_macros = { version = "0.27.1", optional = true } 26 | 27 | [dev-dependencies] 28 | hex-literal = "0.4.1" 29 | wat = "1" 30 | assert_matches = "1.5" 31 | wast = "52.0" 32 | anyhow = "1.0" 33 | criterion = { version = "0.4", default-features = false } 34 | rand = "0.8.2" 35 | 36 | [features] 37 | default = ["std"] 38 | # Use `no-default-features` for a `no_std` build. 39 | std = ["num-traits/std", "downcast-rs/std", "byteorder/std", "dep:strum", "dep:strum_macros"] 40 | print-trace = ["std"] 41 | e2e = [] 42 | -------------------------------------------------------------------------------- /legacy/src/arena/guarded.rs: -------------------------------------------------------------------------------- 1 | use crate::arena::ArenaIndex; 2 | 3 | /// A guarded entity. 4 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] 5 | pub struct GuardedEntity { 6 | guard_idx: GuardIdx, 7 | entity_idx: EntityIdx, 8 | } 9 | 10 | impl GuardedEntity { 11 | /// Creates a new [`GuardedEntity`]. 12 | pub fn new(guard_idx: GuardIdx, entity_idx: EntityIdx) -> Self { 13 | Self { 14 | guard_idx, 15 | entity_idx, 16 | } 17 | } 18 | } 19 | 20 | impl GuardedEntity 21 | where 22 | GuardIdx: ArenaIndex, 23 | EntityIdx: ArenaIndex, 24 | { 25 | /// Returns the entity index of the [`GuardedEntity`]. 26 | /// 27 | /// Return `None` if the `guard_index` does not match. 28 | pub fn entity_index(&self, guard_index: GuardIdx) -> Option { 29 | if self.guard_idx.into_usize() != guard_index.into_usize() { 30 | return None; 31 | } 32 | Some(self.entity_idx) 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /legacy/src/core/host_error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Debug, Display}; 2 | use downcast_rs::{impl_downcast, DowncastSync}; 3 | 4 | /// Trait that allows the host to return custom error. 5 | /// 6 | /// It should be useful for representing custom traps, 7 | /// troubles at instantiation time or other host specific conditions. 8 | /// 9 | /// Types that implement this trait can automatically be converted to `wasmi::Error` and 10 | /// `wasmi::Trap` and will be represented as a boxed `HostError`. You can then use the various 11 | /// methods on `wasmi::Error` to get your custom error type back 12 | /// 13 | /// # Examples 14 | /// 15 | /// ```rust 16 | /// use std::fmt; 17 | /// use crate::rwasm::core::{Trap, HostError}; 18 | /// 19 | /// #[derive(Debug, Copy, Clone)] 20 | /// struct MyError { 21 | /// code: u32, 22 | /// } 23 | /// 24 | /// impl fmt::Display for MyError { 25 | /// fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 26 | /// write!(f, "MyError, code={}", self.code) 27 | /// } 28 | /// } 29 | /// 30 | /// impl HostError for MyError { } 31 | /// 32 | /// fn failable_fn() -> Result<(), Trap> { 33 | /// let my_error = MyError { code: 42 }; 34 | /// // Note how you can just convert your errors to `wasmi::Error` 35 | /// Err(my_error.into()) 36 | /// } 37 | /// 38 | /// // Get a reference to the concrete error 39 | /// match failable_fn() { 40 | /// Err(trap) => { 41 | /// let my_error: &MyError = trap.downcast_ref().unwrap(); 42 | /// assert_eq!(my_error.code, 42); 43 | /// } 44 | /// _ => panic!(), 45 | /// } 46 | /// 47 | /// // get the concrete error itself 48 | /// match failable_fn() { 49 | /// Err(err) => { 50 | /// let my_error = match err.downcast_ref::() { 51 | /// Some(host_error) => host_error.clone(), 52 | /// None => panic!("expected host error `MyError` but found: {}", err), 53 | /// }; 54 | /// assert_eq!(my_error.code, 42); 55 | /// } 56 | /// _ => panic!(), 57 | /// } 58 | /// ``` 59 | pub trait HostError: 'static + Display + Debug + DowncastSync {} 60 | impl_downcast!(HostError); 61 | -------------------------------------------------------------------------------- /legacy/src/core/import_linker.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::ValueType, module::ImportName}; 2 | use hashbrown::HashMap; 3 | use crate::engine::bytecode::Instruction; 4 | 5 | #[derive(Debug, Default, Clone)] 6 | pub struct ImportLinker { 7 | func_by_name: HashMap, 8 | } 9 | 10 | #[derive(Debug, Clone)] 11 | pub struct ImportLinkerEntity { 12 | pub func_idx: u32, 13 | pub fuel_procedure: &'static [Instruction], 14 | pub params: &'static [ValueType], 15 | pub result: &'static [ValueType], 16 | } 17 | 18 | impl From for ImportLinker 19 | where 20 | I: IntoIterator, 21 | { 22 | fn from(iter: I) -> Self { 23 | Self { 24 | func_by_name: HashMap::from_iter(iter), 25 | } 26 | } 27 | } 28 | 29 | impl ImportLinker { 30 | pub fn insert_function( 31 | &mut self, 32 | import_name: ImportName, 33 | func_idx: u32, 34 | fuel_procedure: &'static [Instruction], 35 | params: &'static [ValueType], 36 | result: &'static [ValueType], 37 | ) { 38 | let last_value = self.func_by_name.insert( 39 | import_name, 40 | ImportLinkerEntity { 41 | func_idx, 42 | fuel_procedure, 43 | params, 44 | result, 45 | }, 46 | ); 47 | assert!(last_value.is_none(), "rwasm: import linker name collision"); 48 | } 49 | 50 | pub fn resolve_by_import_name(&self, import_name: &ImportName) -> Option<&ImportLinkerEntity> { 51 | self.func_by_name.get(import_name) 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /legacy/src/core/mod.rs: -------------------------------------------------------------------------------- 1 | #![warn( 2 | clippy::cast_lossless, 3 | clippy::missing_errors_doc, 4 | clippy::used_underscore_binding, 5 | clippy::redundant_closure_for_method_calls, 6 | clippy::type_repetition_in_bounds, 7 | clippy::inconsistent_struct_constructor, 8 | clippy::default_trait_access, 9 | clippy::map_unwrap_or, 10 | clippy::items_after_statements 11 | )] 12 | 13 | mod host_error; 14 | mod import_linker; 15 | mod nan_preserving_float; 16 | mod rwasm; 17 | mod trap; 18 | mod units; 19 | mod untyped; 20 | mod value; 21 | 22 | #[cfg(not(feature = "std"))] 23 | extern crate alloc; 24 | 25 | #[cfg(feature = "std")] 26 | extern crate std as alloc; 27 | 28 | use self::value::{ 29 | ArithmeticOps, 30 | ExtendInto, 31 | Float, 32 | Integer, 33 | LittleEndianConvert, 34 | SignExtendFrom, 35 | TruncateSaturateInto, 36 | TryTruncateInto, 37 | WrapInto, 38 | }; 39 | pub use self::{ 40 | host_error::HostError, 41 | import_linker::*, 42 | nan_preserving_float::{F32, F64}, 43 | rwasm::*, 44 | trap::{Trap, TrapCode}, 45 | units::{Bytes, Pages}, 46 | untyped::{DecodeUntypedSlice, EncodeUntypedSlice, UntypedError, UntypedValue}, 47 | value::ValueType, 48 | }; 49 | -------------------------------------------------------------------------------- /legacy/src/core/rwasm.rs: -------------------------------------------------------------------------------- 1 | /// This constant is driven by WebAssembly standard, default 2 | /// memory page size is 64kB 3 | pub const N_BYTES_PER_MEMORY_PAGE: u32 = 65536; 4 | 5 | /// We have a hard limit for max possible memory used 6 | /// that is equal to ~64mB 7 | pub const N_MAX_MEMORY_PAGES: u32 = 1024; 8 | /// To optimize proving process we have to limit max 9 | /// number of pages, tables, etc. We found 1024 is enough. 10 | pub const N_MAX_TABLES: usize = 1024; 11 | pub const N_MAX_TABLE_ELEMENTS: u32 = 1024; 12 | 13 | pub const N_MAX_STACK_HEIGHT: usize = 4096; 14 | pub const N_MAX_RECURSION_DEPTH: usize = 1024; 15 | 16 | /// Max possible amount of data segments 17 | pub const N_MAX_DATA_SEGMENTS: usize = 1024; 18 | pub const N_MAX_ELEM_SEGMENTS: usize = 1024; 19 | pub const N_MAX_GLOBALS: usize = 1024; 20 | -------------------------------------------------------------------------------- /legacy/src/engine/bytecode/instr_meta.rs: -------------------------------------------------------------------------------- 1 | use crate::engine::bytecode::Instruction; 2 | 3 | type InstrByteLen = usize; 4 | type CommitByteLen = usize; 5 | type IsSigned = bool; 6 | 7 | impl Instruction { 8 | pub const MAX_BYTE_LEN: usize = 8; 9 | pub fn store_instr_meta(instr: &Instruction) -> InstrByteLen { 10 | match instr { 11 | Instruction::I32Store(_) => 4, 12 | Instruction::I32Store8(_) => 1, 13 | Instruction::I32Store16(_) => 2, 14 | Instruction::I64Store(_) => 8, 15 | Instruction::I64Store8(_) => 1, 16 | Instruction::I64Store16(_) => 2, 17 | Instruction::I64Store32(_) => 4, 18 | Instruction::F32Store(_) => 4, 19 | Instruction::F64Store(_) => 8, 20 | _ => unreachable!("unsupported opcode {:?}", instr), 21 | } 22 | } 23 | 24 | pub fn load_instr_meta(instr: &Instruction) -> (InstrByteLen, CommitByteLen, IsSigned) { 25 | match instr { 26 | Instruction::I32Load(_) => (4, 4, false), 27 | Instruction::I64Load(_) => (8, 8, false), 28 | Instruction::F32Load(_) => (4, 4, false), 29 | Instruction::F64Load(_) => (8, 8, false), 30 | Instruction::I32Load8S(_) => (4, 1, true), 31 | Instruction::I32Load8U(_) => (4, 1, false), 32 | Instruction::I32Load16S(_) => (4, 2, true), 33 | Instruction::I32Load16U(_) => (4, 2, false), 34 | Instruction::I64Load8S(_) => (8, 1, true), 35 | Instruction::I64Load8U(_) => (8, 1, false), 36 | Instruction::I64Load16S(_) => (8, 2, true), 37 | Instruction::I64Load16U(_) => (8, 2, false), 38 | Instruction::I64Load32S(_) => (8, 4, true), 39 | Instruction::I64Load32U(_) => (8, 4, false), 40 | _ => unreachable!("unsupported opcode {:?}", instr), 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /legacy/src/engine/bytecode/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use core::mem::size_of; 3 | 4 | #[test] 5 | fn size_of_instruction() { 6 | assert_eq!(size_of::(), 16); 7 | assert_eq!(size_of::(), 4); 8 | assert_eq!(size_of::(), 4); 9 | assert_eq!(size_of::(), 4); 10 | assert_eq!(size_of::(), 4); 11 | assert_eq!(size_of::(), 4); 12 | assert_eq!(size_of::(), 4); 13 | assert_eq!(size_of::(), 4); 14 | assert_eq!(size_of::(), 4); 15 | assert_eq!(size_of::(), 4); 16 | assert_eq!(size_of::(), 4); 17 | assert_eq!(size_of::(), 4); 18 | } 19 | -------------------------------------------------------------------------------- /legacy/src/engine/func_builder/control_stack.rs: -------------------------------------------------------------------------------- 1 | use super::ControlFrame; 2 | use alloc::vec::Vec; 3 | 4 | /// The stack of control flow frames. 5 | #[derive(Debug, Default)] 6 | pub struct ControlFlowStack { 7 | frames: Vec, 8 | } 9 | 10 | impl ControlFlowStack { 11 | /// Resets the [`ControlFlowStack`] to allow for reuse. 12 | pub fn reset(&mut self) { 13 | self.frames.clear() 14 | } 15 | 16 | /// Returns `true` if `relative_depth` points to the first control flow frame. 17 | pub fn is_root(&self, relative_depth: u32) -> bool { 18 | debug_assert!(!self.is_empty()); 19 | relative_depth as usize == self.len() - 1 20 | } 21 | 22 | /// Returns the current depth of the stack of the [`ControlFlowStack`]. 23 | pub fn len(&self) -> usize { 24 | self.frames.len() 25 | } 26 | 27 | /// Returns `true` if the [`ControlFlowStack`] is empty. 28 | pub fn is_empty(&self) -> bool { 29 | self.frames.len() == 0 30 | } 31 | 32 | /// Pushes a new control flow frame to the [`ControlFlowStack`]. 33 | pub fn push_frame(&mut self, frame: T) 34 | where 35 | T: Into, 36 | { 37 | self.frames.push(frame.into()) 38 | } 39 | 40 | /// Pops the last control flow frame from the [`ControlFlowStack`]. 41 | /// 42 | /// # Panics 43 | /// 44 | /// If the [`ControlFlowStack`] is empty. 45 | pub fn pop_frame(&mut self) -> ControlFrame { 46 | self.frames 47 | .pop() 48 | .expect("tried to pop control flow frame from empty control flow stack") 49 | } 50 | 51 | /// Returns the last control flow frame on the control stack. 52 | pub fn last(&self) -> &ControlFrame { 53 | self.frames.last().expect( 54 | "tried to exclusively peek the last control flow \ 55 | frame from an empty control flow stack", 56 | ) 57 | } 58 | 59 | /// Returns a shared reference to the control flow frame at the given `depth`. 60 | /// 61 | /// A `depth` of 0 is equal to calling [`ControlFlowStack::last`]. 62 | /// 63 | /// # Panics 64 | /// 65 | /// If `depth` exceeds the length of the stack of control flow frames. 66 | pub fn nth_back(&self, depth: u32) -> &ControlFrame { 67 | let len = self.len(); 68 | self.frames 69 | .iter() 70 | .nth_back(depth as usize) 71 | .unwrap_or_else(|| { 72 | panic!( 73 | "tried to peek the {depth}-th control flow frame \ 74 | but there are only {len} control flow frames", 75 | ) 76 | }) 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /legacy/src/engine/func_builder/locals_registry.rs: -------------------------------------------------------------------------------- 1 | /// A registry where local variables of a function are registered and resolved. 2 | /// 3 | /// # Note 4 | /// 5 | /// Note that in WebAssembly function parameters are also local variables. 6 | /// 7 | /// The locals registry efficiently registers and resolves local variables. 8 | /// The problem is that the Wasm specification allows to encode up to `u32::MAX` 9 | /// local variables in a small and constant space via the binary encoding. 10 | /// Therefore we need a way to efficiently cope with this worst-case scenario 11 | /// in order to protect the `wasmi` interpreter against exploitations. 12 | /// 13 | /// This implementation allows to access local variables in this worst-case 14 | /// scenario with a worst time complexity of O(log n) and space requirement 15 | /// of O(m + n) where n is the number of registered groups of local variables 16 | /// and m is the number of actually used local variables. 17 | /// 18 | /// Besides that local variable usages are cached to further minimize potential 19 | /// exploitation impact. 20 | #[derive(Debug, Default)] 21 | pub struct LocalsRegistry { 22 | /// The amount of registered local variables. 23 | len_registered: u32, 24 | types_registered: u32, 25 | } 26 | 27 | impl LocalsRegistry { 28 | /// Returns the number of registered local variables. 29 | /// 30 | /// # Note 31 | /// 32 | /// Since in WebAssembly function parameters are also local variables 33 | /// this function actually returns the amount of function parameters 34 | /// and explicitly defined local variables. 35 | pub fn len_registered(&self) -> u32 { 36 | self.len_registered 37 | } 38 | 39 | pub fn types_registered(&self) -> u32 { 40 | self.types_registered 41 | } 42 | 43 | /// Registers an `amount` of local variables. 44 | /// 45 | /// # Panics 46 | /// 47 | /// If too many local variables have been registered. 48 | pub fn register_locals(&mut self, amount: u32) { 49 | if amount == 0 { 50 | return; 51 | } 52 | self.len_registered = self.len_registered.checked_add(amount).unwrap_or_else(|| { 53 | panic!( 54 | "tried to register too many local variables for function: got {}, additional {amount}", 55 | self.len_registered 56 | ) 57 | }); 58 | } 59 | 60 | pub fn register_types(&mut self, amount: u32) { 61 | if amount == 0 { 62 | return; 63 | } 64 | self.types_registered = self.types_registered.checked_add(amount).unwrap_or_else(|| { 65 | panic!( 66 | "tried to register too many local variables for function: got {}, additional {amount}", 67 | self.types_registered 68 | ) 69 | }); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /legacy/src/engine/func_builder/value_stack.rs: -------------------------------------------------------------------------------- 1 | use core::cmp; 2 | 3 | /// The current height of the emulated Wasm value stack. 4 | #[derive(Debug, Default, Copy, Clone)] 5 | pub struct ValueStackHeight { 6 | /// The current height of the emulated value stack of the translated function. 7 | /// 8 | /// # Note 9 | /// 10 | /// This does not include input parameters and local variables. 11 | height: u32, 12 | /// The maximum height of the emulated value stack of the translated function. 13 | /// 14 | /// # Note 15 | /// 16 | /// This does not include input parameters and local variables. 17 | max_height: u32, 18 | } 19 | 20 | impl ValueStackHeight { 21 | /// Returns the current length of the emulated value stack. 22 | /// 23 | /// # Note 24 | /// 25 | /// This does not include input parameters and local variables. 26 | pub fn height(&self) -> u32 { 27 | self.height 28 | } 29 | 30 | /// Returns the maximum value stack height. 31 | /// 32 | /// # Note 33 | /// 34 | /// This does not include input parameters and local variables. 35 | pub fn max_stack_height(&self) -> u32 { 36 | self.max_height 37 | } 38 | 39 | /// Updates the pinned maximum value stack height. 40 | fn update_max_height(&mut self) { 41 | self.max_height = cmp::max(self.height, self.max_height); 42 | } 43 | 44 | /// Pushes an `amount` of values to the emulated value stack. 45 | pub fn push_n(&mut self, amount: u32) { 46 | self.height += amount; 47 | self.update_max_height(); 48 | } 49 | 50 | /// Pushes a value to the emulated value stack. 51 | pub fn push(&mut self) { 52 | self.push_n(1) 53 | } 54 | 55 | /// Pops an `amount` of elements from the emulated value stack. 56 | pub fn pop_n(&mut self, amount: u32) { 57 | debug_assert!(amount <= self.height); 58 | self.height -= amount; 59 | } 60 | 61 | /// Pops 1 element from the emulated value stack. 62 | pub fn pop1(&mut self) { 63 | self.pop_n(1) 64 | } 65 | 66 | /// Pops 2 elements from the emulated value stack. 67 | pub fn pop2(&mut self) { 68 | self.pop_n(2) 69 | } 70 | 71 | /// Pops 3 elements from the emulated value stack. 72 | pub fn pop3(&mut self) { 73 | self.pop_n(3) 74 | } 75 | 76 | /// Shrinks the emulated value stack to the given height. 77 | /// 78 | /// # Panics 79 | /// 80 | /// If the value stack height already is below the height since this 81 | /// usually indicates a bug in the translation of the Wasm to `wasmi` 82 | /// bytecode procedures. 83 | pub fn shrink_to(&mut self, new_height: u32) { 84 | assert!(new_height <= self.height); 85 | self.height = new_height; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /legacy/src/engine/stack/frames.rs: -------------------------------------------------------------------------------- 1 | //! Data structures to represent the Wasm call stack during execution. 2 | 3 | use super::{err_stack_overflow, DEFAULT_MAX_RECURSION_DEPTH}; 4 | use crate::{core::TrapCode, engine::code_map::InstructionPtr, Instance}; 5 | use alloc::vec::Vec; 6 | 7 | /// A function frame of a function on the call stack. 8 | #[derive(Debug, Copy, Clone)] 9 | pub struct FuncFrame { 10 | /// The pointer to the currently executed instruction. 11 | ip: InstructionPtr, 12 | /// The instance in which the function has been defined. 13 | /// 14 | /// # Note 15 | /// 16 | /// The instance is used to inspect and manipulate with data that is 17 | /// non-local to the function such as linear memories, global variables 18 | /// and tables. 19 | instance: Instance, 20 | } 21 | 22 | impl FuncFrame { 23 | /// Creates a new [`FuncFrame`]. 24 | pub fn new(ip: InstructionPtr, instance: &Instance) -> Self { 25 | Self { 26 | ip, 27 | instance: *instance, 28 | } 29 | } 30 | 31 | /// Returns the current instruction pointer. 32 | pub fn ip(&self) -> InstructionPtr { 33 | self.ip 34 | } 35 | 36 | /// Returns the instance of the [`FuncFrame`]. 37 | pub fn instance(&self) -> &Instance { 38 | &self.instance 39 | } 40 | } 41 | 42 | /// The live function call stack storing the live function activation frames. 43 | #[derive(Debug)] 44 | pub struct CallStack { 45 | /// The call stack featuring the function frames in order. 46 | frames: Vec, 47 | /// The maximum allowed depth of the `frames` stack. 48 | recursion_limit: usize, 49 | } 50 | 51 | impl Default for CallStack { 52 | fn default() -> Self { 53 | Self::new(DEFAULT_MAX_RECURSION_DEPTH) 54 | } 55 | } 56 | 57 | impl CallStack { 58 | /// Creates a new [`CallStack`] using the given recursion limit. 59 | pub fn new(recursion_limit: usize) -> Self { 60 | Self { 61 | frames: Vec::new(), 62 | recursion_limit, 63 | } 64 | } 65 | 66 | /// Initializes the [`CallStack`] given the Wasm function. 67 | pub fn init(&mut self, ip: InstructionPtr, instance: &Instance) { 68 | self.reset(); 69 | self.frames.push(FuncFrame::new(ip, instance)); 70 | } 71 | 72 | /// Pushes a Wasm caller function onto the [`CallStack`]. 73 | #[inline] 74 | pub fn push(&mut self, caller: FuncFrame) -> Result<(), TrapCode> { 75 | if self.len() == self.recursion_limit { 76 | return Err(err_stack_overflow()); 77 | } 78 | self.frames.push(caller); 79 | Ok(()) 80 | } 81 | 82 | /// Pops the last [`FuncFrame`] from the [`CallStack`] if any. 83 | #[inline] 84 | pub fn pop(&mut self) -> Option { 85 | self.frames.pop() 86 | } 87 | 88 | /// Peeks the last [`FuncFrame`] from the [`CallStack`] if any. 89 | #[inline] 90 | pub fn peek(&self) -> Option<&FuncFrame> { 91 | self.frames.last() 92 | } 93 | 94 | /// Returns the amount of function frames on the [`CallStack`]. 95 | #[inline] 96 | fn len(&self) -> usize { 97 | self.frames.len() 98 | } 99 | 100 | /// Clears the [`CallStack`] entirely. 101 | /// 102 | /// # Note 103 | /// 104 | /// This is required since sometimes execution can halt in the middle of 105 | /// function execution which leaves the [`CallStack`] in an unspecified 106 | /// state. Therefore the [`CallStack`] is required to be reset before 107 | /// function execution happens. 108 | pub fn reset(&mut self) { 109 | self.frames.clear(); 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /legacy/src/engine/stack/values/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | use crate::engine::DropKeep; 3 | 4 | fn drop_keep(drop: usize, keep: usize) -> DropKeep { 5 | DropKeep::new(drop, keep).unwrap() 6 | } 7 | 8 | impl FromIterator for ValueStack { 9 | fn from_iter(iter: I) -> Self 10 | where 11 | I: IntoIterator, 12 | { 13 | let mut stack = ValueStack::default(); 14 | stack.extend(iter); 15 | stack 16 | } 17 | } 18 | 19 | impl<'a> IntoIterator for &'a ValueStack { 20 | type Item = &'a UntypedValue; 21 | type IntoIter = core::slice::Iter<'a, UntypedValue>; 22 | 23 | fn into_iter(self) -> Self::IntoIter { 24 | self.entries[0..self.stack_ptr].iter() 25 | } 26 | } 27 | 28 | impl ValueStack { 29 | pub fn iter(&self) -> core::slice::Iter { 30 | self.into_iter() 31 | } 32 | } 33 | 34 | #[test] 35 | fn drop_keep_works() { 36 | fn assert_drop_keep(stack: &ValueStack, drop_keep: DropKeep, expected: E) 37 | where 38 | E: IntoIterator, 39 | E::Item: Into, 40 | { 41 | let mut s = stack.clone(); 42 | let mut sp = s.stack_ptr(); 43 | sp.drop_keep(drop_keep); 44 | s.sync_stack_ptr(sp); 45 | let expected: Vec<_> = expected.into_iter().map(Into::into).collect(); 46 | let actual: Vec<_> = s.iter().copied().collect(); 47 | assert_eq!(actual, expected, "test failed for {drop_keep:?}"); 48 | } 49 | 50 | let test_inputs = [1, 2, 3, 4, 5, 6]; 51 | let stack = test_inputs 52 | .into_iter() 53 | .map(UntypedValue::from) 54 | .collect::(); 55 | 56 | // Drop is always 0 but keep varies: 57 | for keep in 0..stack.len() { 58 | // Assert that nothing was changed since nothing was dropped. 59 | assert_drop_keep(&stack, drop_keep(0, keep), test_inputs); 60 | } 61 | 62 | // Drop is always 1 but keep varies: 63 | assert_drop_keep(&stack, drop_keep(1, 0), [1, 2, 3, 4, 5]); 64 | assert_drop_keep(&stack, drop_keep(1, 1), [1, 2, 3, 4, 6]); 65 | assert_drop_keep(&stack, drop_keep(1, 2), [1, 2, 3, 5, 6]); 66 | assert_drop_keep(&stack, drop_keep(1, 3), [1, 2, 4, 5, 6]); 67 | assert_drop_keep(&stack, drop_keep(1, 4), [1, 3, 4, 5, 6]); 68 | assert_drop_keep(&stack, drop_keep(1, 5), [2, 3, 4, 5, 6]); 69 | 70 | // Drop is always 2 but keep varies: 71 | assert_drop_keep(&stack, drop_keep(2, 0), [1, 2, 3, 4]); 72 | assert_drop_keep(&stack, drop_keep(2, 1), [1, 2, 3, 6]); 73 | assert_drop_keep(&stack, drop_keep(2, 2), [1, 2, 5, 6]); 74 | assert_drop_keep(&stack, drop_keep(2, 3), [1, 4, 5, 6]); 75 | assert_drop_keep(&stack, drop_keep(2, 4), [3, 4, 5, 6]); 76 | 77 | // Drop is always 3 but keep varies: 78 | assert_drop_keep(&stack, drop_keep(3, 0), [1, 2, 3]); 79 | assert_drop_keep(&stack, drop_keep(3, 1), [1, 2, 6]); 80 | assert_drop_keep(&stack, drop_keep(3, 2), [1, 5, 6]); 81 | assert_drop_keep(&stack, drop_keep(3, 3), [4, 5, 6]); 82 | 83 | // Drop is always 4 but keep varies: 84 | assert_drop_keep(&stack, drop_keep(4, 0), [1, 2]); 85 | assert_drop_keep(&stack, drop_keep(4, 1), [1, 6]); 86 | assert_drop_keep(&stack, drop_keep(4, 2), [5, 6]); 87 | 88 | // Drop is always 5 but keep varies: 89 | assert_drop_keep(&stack, drop_keep(5, 0), [1]); 90 | assert_drop_keep(&stack, drop_keep(5, 1), [6]); 91 | 92 | // Drop is always 6. 93 | assert_drop_keep(&stack, drop_keep(6, 0), iter::repeat(0).take(0)); 94 | } 95 | -------------------------------------------------------------------------------- /legacy/src/engine/traits.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::UntypedValue, value::WithType, Value}; 2 | use core::{iter, slice}; 3 | 4 | /// Types implementing this trait may be used as parameters for function execution. 5 | /// 6 | /// # Note 7 | /// 8 | /// - This is generically implemented by `&[Value]` and tuples of `T: WasmType` types. 9 | /// - Using this trait allows to customize the parameters entrypoint for efficient function 10 | /// execution via the [`Engine`]. 11 | /// 12 | /// [`Engine`]: [`crate::Engine`] 13 | pub trait CallParams { 14 | /// The iterator over the parameter values. 15 | type Params: ExactSizeIterator; 16 | 17 | /// Feeds the parameter values from the caller. 18 | fn call_params(self) -> Self::Params; 19 | } 20 | 21 | impl<'a> CallParams for &'a [Value] { 22 | type Params = CallParamsValueIter<'a>; 23 | 24 | #[inline] 25 | fn call_params(self) -> Self::Params { 26 | CallParamsValueIter { 27 | iter: self.iter().cloned(), 28 | } 29 | } 30 | } 31 | 32 | /// An iterator over the [`UntypedValue`] call parameters. 33 | #[derive(Debug)] 34 | pub struct CallParamsValueIter<'a> { 35 | iter: iter::Cloned>, 36 | } 37 | 38 | impl<'a> Iterator for CallParamsValueIter<'a> { 39 | type Item = UntypedValue; 40 | 41 | #[inline] 42 | fn size_hint(&self) -> (usize, Option) { 43 | self.iter.size_hint() 44 | } 45 | 46 | #[inline] 47 | fn next(&mut self) -> Option { 48 | self.iter.next().map(UntypedValue::from) 49 | } 50 | } 51 | 52 | impl ExactSizeIterator for CallParamsValueIter<'_> {} 53 | 54 | /// Types implementing this trait may be used as results for function execution. 55 | /// 56 | /// # Note 57 | /// 58 | /// - This is generically implemented by `&mut [Value]` and indirectly for tuples of `T: WasmType`. 59 | /// - Using this trait allows to customize the parameters entrypoint for efficient function 60 | /// execution via the [`Engine`]. 61 | /// 62 | /// [`Engine`]: [`crate::Engine`] 63 | pub trait CallResults { 64 | /// The type of the returned results value. 65 | type Results; 66 | 67 | /// Feeds the result values back to the caller. 68 | /// 69 | /// # Panics 70 | /// 71 | /// If the given `results` do not match the expected amount. 72 | fn call_results(self, results: &[UntypedValue]) -> Self::Results; 73 | } 74 | 75 | impl<'a> CallResults for &'a mut [Value] { 76 | type Results = (); 77 | 78 | fn call_results(self, results: &[UntypedValue]) -> Self::Results { 79 | assert_eq!(self.len(), results.len()); 80 | self.iter_mut().zip(results).for_each(|(dst, src)| { 81 | *dst = src.with_type(dst.ty()); 82 | }) 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /legacy/src/error.rs: -------------------------------------------------------------------------------- 1 | use super::errors::{ 2 | FuelError, 3 | FuncError, 4 | GlobalError, 5 | InstantiationError, 6 | LinkerError, 7 | MemoryError, 8 | ModuleError, 9 | TableError, 10 | }; 11 | use crate::core::Trap; 12 | use core::{fmt, fmt::Display}; 13 | 14 | /// An error that may occur upon operating on Wasm modules or module instances. 15 | #[derive(Debug)] 16 | #[non_exhaustive] 17 | pub enum Error { 18 | /// A global variable error. 19 | Global(GlobalError), 20 | /// A linear memory error. 21 | Memory(MemoryError), 22 | /// A table error. 23 | Table(TableError), 24 | /// A linker error. 25 | Linker(LinkerError), 26 | /// A module instantiation error. 27 | Instantiation(InstantiationError), 28 | /// A module compilation, validation and translation error. 29 | Module(ModuleError), 30 | /// A store error. 31 | Store(FuelError), 32 | /// A function error. 33 | Func(FuncError), 34 | /// A trap as defined by the WebAssembly specification. 35 | Trap(Trap), 36 | } 37 | 38 | #[cfg(feature = "std")] 39 | impl std::error::Error for Error {} 40 | 41 | impl Display for Error { 42 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 43 | match self { 44 | Self::Trap(error) => Display::fmt(error, f), 45 | Self::Global(error) => Display::fmt(error, f), 46 | Self::Memory(error) => Display::fmt(error, f), 47 | Self::Table(error) => Display::fmt(error, f), 48 | Self::Linker(error) => Display::fmt(error, f), 49 | Self::Func(error) => Display::fmt(error, f), 50 | Self::Instantiation(error) => Display::fmt(error, f), 51 | Self::Module(error) => Display::fmt(error, f), 52 | Self::Store(error) => Display::fmt(error, f), 53 | } 54 | } 55 | } 56 | 57 | impl From for Error { 58 | fn from(error: Trap) -> Self { 59 | Self::Trap(error) 60 | } 61 | } 62 | 63 | impl From for Error { 64 | fn from(error: GlobalError) -> Self { 65 | Self::Global(error) 66 | } 67 | } 68 | 69 | impl From for Error { 70 | fn from(error: MemoryError) -> Self { 71 | Self::Memory(error) 72 | } 73 | } 74 | 75 | impl From for Error { 76 | fn from(error: TableError) -> Self { 77 | Self::Table(error) 78 | } 79 | } 80 | 81 | impl From for Error { 82 | fn from(error: LinkerError) -> Self { 83 | Self::Linker(error) 84 | } 85 | } 86 | 87 | impl From for Error { 88 | fn from(error: InstantiationError) -> Self { 89 | Self::Instantiation(error) 90 | } 91 | } 92 | 93 | impl From for Error { 94 | fn from(error: ModuleError) -> Self { 95 | Self::Module(error) 96 | } 97 | } 98 | 99 | impl From for Error { 100 | fn from(error: FuelError) -> Self { 101 | Self::Store(error) 102 | } 103 | } 104 | 105 | impl From for Error { 106 | fn from(error: FuncError) -> Self { 107 | Self::Func(error) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /legacy/src/foreach_tuple.rs: -------------------------------------------------------------------------------- 1 | /// Macro to help implement generic trait implementations for tuple types. 2 | macro_rules! for_each_tuple { 3 | ($mac:ident) => { 4 | $mac!( 0 ); 5 | $mac!( 1 T1); 6 | $mac!( 2 T1 T2); 7 | $mac!( 3 T1 T2 T3); 8 | $mac!( 4 T1 T2 T3 T4); 9 | $mac!( 5 T1 T2 T3 T4 T5); 10 | $mac!( 6 T1 T2 T3 T4 T5 T6); 11 | $mac!( 7 T1 T2 T3 T4 T5 T6 T7); 12 | $mac!( 8 T1 T2 T3 T4 T5 T6 T7 T8); 13 | $mac!( 9 T1 T2 T3 T4 T5 T6 T7 T8 T9); 14 | $mac!(10 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10); 15 | $mac!(11 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11); 16 | $mac!(12 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12); 17 | $mac!(13 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13); 18 | $mac!(14 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14); 19 | $mac!(15 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15); 20 | $mac!(16 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16); 21 | } 22 | } 23 | 24 | #[doc(inline)] 25 | pub(crate) use for_each_tuple; 26 | -------------------------------------------------------------------------------- /legacy/src/func/error.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt, fmt::Display}; 2 | 3 | /// Errors that can occur upon type checking function signatures. 4 | #[derive(Debug)] 5 | pub enum FuncError { 6 | /// The exported function could not be found. 7 | ExportedFuncNotFound, 8 | /// A function parameter did not match the required type. 9 | MismatchingParameterType, 10 | /// Specified an incorrect number of parameters. 11 | MismatchingParameterLen, 12 | /// A function result did not match the required type. 13 | MismatchingResultType, 14 | /// Specified an incorrect number of results. 15 | MismatchingResultLen, 16 | } 17 | 18 | impl Display for FuncError { 19 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 20 | match self { 21 | FuncError::ExportedFuncNotFound => { 22 | write!(f, "could not find exported function") 23 | } 24 | FuncError::MismatchingParameterType => { 25 | write!(f, "encountered incorrect function parameter type") 26 | } 27 | FuncError::MismatchingParameterLen => { 28 | write!(f, "encountered an incorrect number of parameters") 29 | } 30 | FuncError::MismatchingResultType => { 31 | write!(f, "encountered incorrect function result type") 32 | } 33 | FuncError::MismatchingResultLen => { 34 | write!(f, "encountered an incorrect number of results") 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /legacy/src/memory/buffer.rs: -------------------------------------------------------------------------------- 1 | use alloc::{vec, vec::Vec}; 2 | 3 | /// A `Vec`-based byte buffer implementation. 4 | /// 5 | /// # Note 6 | /// 7 | /// This is less efficient than the byte buffer implementation that is 8 | /// based on actual OS provided virtual memory but it is a safe fallback 9 | /// solution fitting any platform. 10 | #[derive(Debug)] 11 | pub struct ByteBuffer { 12 | bytes: Vec, 13 | } 14 | 15 | impl ByteBuffer { 16 | /// Creates a new byte buffer with the given initial length. 17 | /// 18 | /// # Errors 19 | /// 20 | /// - If the initial length is 0. 21 | /// - If the initial length exceeds the maximum supported limit. 22 | pub fn new(initial_len: usize) -> Self { 23 | Self { 24 | bytes: vec![0x00_u8; initial_len], 25 | } 26 | } 27 | 28 | /// Grows the byte buffer to the given `new_size`. 29 | /// 30 | /// # Panics 31 | /// 32 | /// If the current size of the [`ByteBuffer`] is larger than `new_size`. 33 | pub fn grow(&mut self, new_size: usize) { 34 | assert!(new_size >= self.len()); 35 | self.bytes.resize(new_size, 0x00_u8); 36 | } 37 | 38 | /// Returns the length of the byte buffer in bytes. 39 | pub fn len(&self) -> usize { 40 | self.bytes.len() 41 | } 42 | 43 | /// Returns a shared slice to the bytes underlying to the byte buffer. 44 | pub fn data(&self) -> &[u8] { 45 | &self.bytes[..] 46 | } 47 | 48 | /// Returns an exclusive slice to the bytes underlying to the byte buffer. 49 | pub fn data_mut(&mut self) -> &mut [u8] { 50 | &mut self.bytes[..] 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /legacy/src/memory/data.rs: -------------------------------------------------------------------------------- 1 | use crate::{arena::ArenaIndex, module, store::Stored, AsContextMut}; 2 | use alloc::sync::Arc; 3 | 4 | /// A raw index to a data segment entity. 5 | #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] 6 | pub struct DataSegmentIdx(u32); 7 | 8 | impl ArenaIndex for DataSegmentIdx { 9 | fn into_usize(self) -> usize { 10 | self.0 as usize 11 | } 12 | 13 | fn from_usize(value: usize) -> Self { 14 | let value = value.try_into().unwrap_or_else(|error| { 15 | panic!("index {value} is out of bounds as data segment index: {error}") 16 | }); 17 | Self(value) 18 | } 19 | } 20 | 21 | /// A Wasm data segment reference. 22 | #[derive(Debug, Copy, Clone)] 23 | #[repr(transparent)] 24 | pub struct DataSegment(Stored); 25 | 26 | impl DataSegment { 27 | /// Creates a new linear memory reference. 28 | pub fn from_inner(stored: Stored) -> Self { 29 | Self(stored) 30 | } 31 | 32 | /// Returns the underlying stored representation. 33 | pub fn as_inner(&self) -> &Stored { 34 | &self.0 35 | } 36 | 37 | /// Allocates a new [`DataSegment`] on the store. 38 | /// 39 | /// # Errors 40 | /// 41 | /// If more than [`u32::MAX`] much linear memory is allocated. 42 | pub fn new(mut ctx: impl AsContextMut, segment: &module::DataSegment) -> Self { 43 | let entity = DataSegmentEntity::from(segment); 44 | ctx.as_context_mut().store.inner.alloc_data_segment(entity) 45 | } 46 | } 47 | 48 | /// An instantiated [`DataSegmentEntity`]. 49 | /// 50 | /// # Note 51 | /// 52 | /// With the `bulk-memory` Wasm proposal it is possible to interact 53 | /// with data segments at runtime. Therefore Wasm instances now have 54 | /// a need to have an instantiated representation of data segments. 55 | #[derive(Debug)] 56 | pub struct DataSegmentEntity { 57 | /// The underlying bytes of the instance data segment. 58 | /// 59 | /// # Note 60 | /// 61 | /// These bytes are just readable after instantiation. 62 | /// Using Wasm `data.drop` simply replaces the instance 63 | /// with an empty one. 64 | bytes: Option>, 65 | } 66 | 67 | impl From<&'_ module::DataSegment> for DataSegmentEntity { 68 | fn from(segment: &'_ module::DataSegment) -> Self { 69 | match segment.kind() { 70 | module::DataSegmentKind::Passive => Self { 71 | bytes: Some(segment.clone_bytes()), 72 | }, 73 | module::DataSegmentKind::Active(_) => Self::empty(), 74 | } 75 | } 76 | } 77 | 78 | impl DataSegmentEntity { 79 | /// Create an empty [`DataSegmentEntity`] representing dropped data segments. 80 | fn empty() -> Self { 81 | Self { bytes: None } 82 | } 83 | 84 | /// Performs an emptiness check. 85 | /// This function returns `true` only if the segment contains no items and not just an empty 86 | /// array. 87 | /// This check is crucial to determine if a segment has been dropped. 88 | pub fn is_empty(&self) -> bool { 89 | self.bytes.is_none() 90 | } 91 | 92 | /// Returns the bytes of the [`DataSegmentEntity`]. 93 | pub fn bytes(&self) -> &[u8] { 94 | self.bytes 95 | .as_ref() 96 | .map(|bytes| &bytes[..]) 97 | .unwrap_or_else(|| &[]) 98 | } 99 | 100 | /// Drops the bytes of the [`DataSegmentEntity`]. 101 | pub fn drop_bytes(&mut self) { 102 | self.bytes = None; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /legacy/src/memory/error.rs: -------------------------------------------------------------------------------- 1 | use super::MemoryType; 2 | use core::{fmt, fmt::Display}; 3 | 4 | /// An error that may occur upon operating with virtual or linear memory. 5 | #[derive(Debug)] 6 | #[non_exhaustive] 7 | pub enum MemoryError { 8 | /// Tried to allocate more virtual memory than technically possible. 9 | OutOfBoundsAllocation, 10 | /// Tried to grow linear memory out of its set bounds. 11 | OutOfBoundsGrowth, 12 | /// Tried to access linear memory out of bounds. 13 | OutOfBoundsAccess, 14 | /// Tried to create an invalid linear memory type. 15 | InvalidMemoryType, 16 | /// Occurs when `ty` is not a subtype of `other`. 17 | InvalidSubtype { 18 | /// The [`MemoryType`] which is not a subtype of `other`. 19 | ty: MemoryType, 20 | /// The [`MemoryType`] which is supposed to be a supertype of `ty`. 21 | other: MemoryType, 22 | }, 23 | /// Tried to create too many memories 24 | TooManyMemories, 25 | } 26 | 27 | impl Display for MemoryError { 28 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 29 | match self { 30 | Self::OutOfBoundsAllocation => { 31 | write!(f, "out of bounds memory allocation") 32 | } 33 | Self::OutOfBoundsGrowth => { 34 | write!(f, "out of bounds memory growth") 35 | } 36 | Self::OutOfBoundsAccess => { 37 | write!(f, "out of bounds memory access") 38 | } 39 | Self::InvalidMemoryType => { 40 | write!(f, "tried to create an invalid virtual memory type") 41 | } 42 | Self::InvalidSubtype { ty, other } => { 43 | write!(f, "memory type {ty:?} is not a subtype of {other:?}",) 44 | } 45 | Self::TooManyMemories => { 46 | write!(f, "too many memories") 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /legacy/src/memory/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn memory_type(minimum: u32, maximum: impl Into>) -> MemoryType { 4 | MemoryType::new(minimum, maximum.into()).unwrap() 5 | } 6 | 7 | #[test] 8 | fn subtyping_works() { 9 | assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, 1))); 10 | assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, 2))); 11 | assert!(!memory_type(0, 2).is_subtype_of(&memory_type(0, 1))); 12 | assert!(memory_type(2, None).is_subtype_of(&memory_type(1, None))); 13 | assert!(memory_type(0, None).is_subtype_of(&memory_type(0, None))); 14 | assert!(memory_type(0, 1).is_subtype_of(&memory_type(0, None))); 15 | assert!(!memory_type(0, None).is_subtype_of(&memory_type(0, 1))); 16 | } 17 | -------------------------------------------------------------------------------- /legacy/src/module/data.rs: -------------------------------------------------------------------------------- 1 | use super::{ConstExpr, MemoryIdx}; 2 | use alloc::sync::Arc; 3 | 4 | /// A Wasm [`Module`] data segment. 5 | /// 6 | /// [`Module`]: [`super::Module`] 7 | #[derive(Debug)] 8 | pub struct DataSegment { 9 | /// The kind of the data segment. 10 | pub kind: DataSegmentKind, 11 | /// The bytes of the data segment. 12 | pub bytes: Arc<[u8]>, 13 | } 14 | 15 | /// The kind of a Wasm module [`DataSegment`]. 16 | #[derive(Debug)] 17 | pub enum DataSegmentKind { 18 | /// A passive data segment from the `bulk-memory` Wasm proposal. 19 | Passive, 20 | /// An active data segment that is initialized upon module instantiation. 21 | Active(ActiveDataSegment), 22 | } 23 | 24 | /// An active data segment. 25 | #[derive(Debug)] 26 | pub struct ActiveDataSegment { 27 | /// The linear memory that is to be initialized with this active segment. 28 | memory_index: MemoryIdx, 29 | /// The offset at which the data segment is initialized. 30 | offset: ConstExpr, 31 | } 32 | 33 | impl ActiveDataSegment { 34 | /// Returns the Wasm module memory index that is to be initialized. 35 | pub fn memory_index(&self) -> MemoryIdx { 36 | self.memory_index 37 | } 38 | 39 | /// Returns the offset expression of the [`ActiveDataSegment`]. 40 | pub fn offset(&self) -> &ConstExpr { 41 | &self.offset 42 | } 43 | } 44 | 45 | impl From> for DataSegmentKind { 46 | fn from(data_kind: wasmparser::DataKind<'_>) -> Self { 47 | match data_kind { 48 | wasmparser::DataKind::Active { 49 | memory_index, 50 | offset_expr, 51 | } => { 52 | let memory_index = MemoryIdx::from(memory_index); 53 | let offset = ConstExpr::new(offset_expr); 54 | Self::Active(ActiveDataSegment { 55 | memory_index, 56 | offset, 57 | }) 58 | } 59 | wasmparser::DataKind::Passive => Self::Passive, 60 | } 61 | } 62 | } 63 | 64 | impl From> for DataSegment { 65 | fn from(data: wasmparser::Data<'_>) -> Self { 66 | let kind = DataSegmentKind::from(data.kind); 67 | let bytes = data.data.into(); 68 | Self { kind, bytes } 69 | } 70 | } 71 | 72 | impl DataSegment { 73 | /// Returns the [`DataSegmentKind`] of the [`DataSegment`]. 74 | pub fn kind(&self) -> &DataSegmentKind { 75 | &self.kind 76 | } 77 | 78 | /// Returns the bytes of the [`DataSegment`]. 79 | pub fn bytes(&self) -> &[u8] { 80 | &self.bytes[..] 81 | } 82 | 83 | /// Clone the underlying bytes of the [`DataSegment`]. 84 | pub fn clone_bytes(&self) -> Arc<[u8]> { 85 | self.bytes.clone() 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /legacy/src/module/error.rs: -------------------------------------------------------------------------------- 1 | use super::ReadError; 2 | use crate::{engine::TranslationError, rwasm::RwasmBuilderError}; 3 | use core::{ 4 | fmt, 5 | fmt::{Debug, Display}, 6 | }; 7 | use wasmparser::BinaryReaderError as ParserError; 8 | 9 | /// Errors that may occur upon reading, parsing and translating Wasm modules. 10 | #[derive(Debug)] 11 | pub enum ModuleError { 12 | /// Encountered when there is a problem with the Wasm input stream. 13 | Read(ReadError), 14 | /// Encountered when there is a Wasm parsing error. 15 | Parser(ParserError), 16 | /// Encountered when there is a Wasm to `wasmi` translation error. 17 | Translation(TranslationError), 18 | /// Error that happens during rWASM build 19 | Rwasm(RwasmBuilderError), 20 | } 21 | 22 | impl Display for ModuleError { 23 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 24 | match self { 25 | ModuleError::Read(error) => Display::fmt(error, f), 26 | ModuleError::Parser(error) => Display::fmt(error, f), 27 | ModuleError::Translation(error) => Display::fmt(error, f), 28 | ModuleError::Rwasm(error) => Display::fmt(error, f), 29 | } 30 | } 31 | } 32 | 33 | impl From for ModuleError { 34 | fn from(error: ReadError) -> Self { 35 | Self::Read(error) 36 | } 37 | } 38 | 39 | impl From for ModuleError { 40 | fn from(error: ParserError) -> Self { 41 | Self::Parser(error) 42 | } 43 | } 44 | 45 | impl From for ModuleError { 46 | fn from(error: TranslationError) -> Self { 47 | Self::Translation(error) 48 | } 49 | } 50 | 51 | impl From for ModuleError { 52 | fn from(error: RwasmBuilderError) -> Self { 53 | Self::Rwasm(error) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /legacy/src/module/global.rs: -------------------------------------------------------------------------------- 1 | use super::ConstExpr; 2 | use crate::GlobalType; 3 | 4 | /// The index of a global variable within a [`Module`]. 5 | /// 6 | /// [`Module`]: [`super::Module`] 7 | #[derive(Debug, Copy, Clone)] 8 | pub struct GlobalIdx(u32); 9 | 10 | impl From for GlobalIdx { 11 | fn from(index: u32) -> Self { 12 | Self(index) 13 | } 14 | } 15 | 16 | impl GlobalIdx { 17 | /// Returns the [`GlobalIdx`] as `u32`. 18 | pub fn into_u32(self) -> u32 { 19 | self.0 20 | } 21 | } 22 | 23 | /// A global variable definition within a [`Module`]. 24 | /// 25 | /// [`Module`]: [`super::Module`] 26 | #[derive(Debug)] 27 | pub struct Global { 28 | /// The type of the global variable. 29 | global_type: GlobalType, 30 | /// The initial value of the global variable. 31 | /// 32 | /// # Note 33 | /// 34 | /// This is represented by a so called initializer expression 35 | /// that is run at module instantiation time. 36 | init_expr: ConstExpr, 37 | } 38 | 39 | impl From> for Global { 40 | fn from(global: wasmparser::Global<'_>) -> Self { 41 | let global_type = GlobalType::from_wasmparser(global.ty); 42 | let init_expr = ConstExpr::new(global.init_expr); 43 | Self { 44 | global_type, 45 | init_expr, 46 | } 47 | } 48 | } 49 | 50 | impl Global { 51 | /// Splits the [`Global`] into its global type and its global initializer. 52 | pub fn into_type_and_init(self) -> (GlobalType, ConstExpr) { 53 | (self.global_type, self.init_expr) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /legacy/src/module/instantiate/pre.rs: -------------------------------------------------------------------------------- 1 | use super::InstantiationError; 2 | use crate::{module::FuncIdx, AsContextMut, Error, Instance, InstanceEntityBuilder}; 3 | 4 | /// A partially instantiated [`Instance`] where the `start` function has not yet been executed. 5 | /// 6 | /// # Note 7 | /// 8 | /// Some users require Wasm modules to not have a `start` function that is required for 9 | /// conformant module instantiation. This API provides control over the precise instantiation 10 | /// process with regard to this need. 11 | #[derive(Debug)] 12 | pub struct InstancePre { 13 | handle: Instance, 14 | builder: InstanceEntityBuilder, 15 | } 16 | 17 | impl InstancePre { 18 | /// Creates a new [`InstancePre`]. 19 | pub(super) fn new(handle: Instance, builder: InstanceEntityBuilder) -> Self { 20 | Self { handle, builder } 21 | } 22 | 23 | /// Returns the index of the `start` function if any. 24 | /// 25 | /// Returns `None` if the Wasm module does not have a `start` function. 26 | fn start_fn(&self) -> Option { 27 | self.builder.get_start().map(FuncIdx::into_u32) 28 | } 29 | 30 | /// Runs the `start` function of the [`Instance`] and returns its handle. 31 | /// 32 | /// # Note 33 | /// 34 | /// This finishes the instantiation procedure. 35 | /// 36 | /// # Errors 37 | /// 38 | /// If executing the `start` function traps. 39 | /// 40 | /// # Panics 41 | /// 42 | /// If the `start` function is invalid albeit successful validation. 43 | pub fn start(self, mut context: impl AsContextMut) -> Result { 44 | let opt_start_index = self.start_fn(); 45 | context 46 | .as_context_mut() 47 | .store 48 | .inner 49 | .initialize_instance(self.handle, self.builder.finish()); 50 | if let Some(start_index) = opt_start_index { 51 | let start_func = self 52 | .handle 53 | .get_func_by_index(&mut context, start_index) 54 | .unwrap_or_else(|| { 55 | panic!("encountered invalid start function after validation: {start_index}") 56 | }); 57 | start_func.call(context.as_context_mut(), &[], &mut [])? 58 | } 59 | Ok(self.handle) 60 | } 61 | 62 | /// Finishes instantiation ensuring that no `start` function exists. 63 | /// 64 | /// # Errors 65 | /// 66 | /// If a `start` function exists that needs to be called for conformant module instantiation. 67 | pub fn ensure_no_start( 68 | self, 69 | mut context: impl AsContextMut, 70 | ) -> Result { 71 | if let Some(index) = self.start_fn() { 72 | return Err(InstantiationError::FoundStartFn { index }); 73 | } 74 | context 75 | .as_context_mut() 76 | .store 77 | .inner 78 | .initialize_instance(self.handle, self.builder.finish()); 79 | Ok(self.handle) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /legacy/src/module/read.rs: -------------------------------------------------------------------------------- 1 | use core::{fmt, fmt::Display}; 2 | #[cfg(feature = "std")] 3 | use std::io; 4 | 5 | /// Errors returned by [`Read::read`]. 6 | #[derive(Debug, PartialEq, Eq)] 7 | pub enum ReadError { 8 | /// The source has reached the end of the stream. 9 | EndOfStream, 10 | /// An unknown error occurred. 11 | UnknownError, 12 | } 13 | 14 | impl Display for ReadError { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | match self { 17 | ReadError::EndOfStream => write!(f, "encountered unexpected end of stream"), 18 | ReadError::UnknownError => write!(f, "encountered unknown error"), 19 | } 20 | } 21 | } 22 | 23 | /// Types implementing this trait act as byte streams. 24 | /// 25 | /// # Note 26 | /// 27 | /// Provides a subset of the interface provided by Rust's [`std::io::Read`][std_io_read] trait. 28 | /// 29 | /// [`Module::new`]: [`crate::Module::new`] 30 | /// [std_io_read]: https://doc.rust-lang.org/std/io/trait.Read.html 31 | pub trait Read { 32 | /// Pull some bytes from this source into the specified buffer, returning how many bytes were 33 | /// read. 34 | /// 35 | /// # Note 36 | /// 37 | /// Provides the same guarantees to the caller as [`std::io::Read::read`][io_read_read]. 38 | /// 39 | /// [io_read_read]: https://doc.rust-lang.org/std/io/trait.Read.html#tymethod.read 40 | /// 41 | /// # Errors 42 | /// 43 | /// - If `self` stream is already at its end. 44 | /// - For any unknown error returned by the generic [`Read`] implementer. 45 | fn read(&mut self, buf: &mut [u8]) -> Result; 46 | } 47 | 48 | #[cfg(feature = "std")] 49 | impl Read for T 50 | where 51 | T: io::Read, 52 | { 53 | fn read(&mut self, buffer: &mut [u8]) -> Result { 54 | ::read(self, buffer).map_err(|error| match error.kind() { 55 | io::ErrorKind::UnexpectedEof => ReadError::EndOfStream, 56 | _ => ReadError::UnknownError, 57 | }) 58 | } 59 | } 60 | 61 | #[cfg(not(feature = "std"))] 62 | impl<'a> Read for &'a [u8] { 63 | fn read(&mut self, buffer: &mut [u8]) -> Result { 64 | let len_copy = self.len().min(buffer.len()); 65 | buffer[..len_copy].copy_from_slice(&self[..len_copy]); 66 | *self = &self[len_copy..]; 67 | Ok(len_copy) 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /legacy/src/reftype.rs: -------------------------------------------------------------------------------- 1 | use crate::core::UntypedValue; 2 | 3 | /// Utility type used to convert between `reftype` and [`UntypedValue`]. 4 | /// 5 | /// # Note 6 | /// 7 | /// This is used for conversions of [`FuncRef`] and [`ExternRef`]. 8 | /// 9 | /// [`FuncRef`]: [`crate::FuncRef`] 10 | /// [`ExternRef`]: [`crate::ExternRef`] 11 | pub union Transposer { 12 | /// The `reftype` based representation. 13 | pub reftype: T, 14 | /// The integer based representation to model pointer types. 15 | pub value: u64, 16 | } 17 | 18 | impl Transposer { 19 | /// Creates a `null` [`Transposer`]. 20 | pub fn null() -> Self { 21 | Self { value: 0 } 22 | } 23 | } 24 | 25 | impl Transposer { 26 | /// Creates a new [`Transposer`] from the given `reftype`. 27 | pub fn new(reftype: T) -> Self { 28 | Transposer { reftype } 29 | } 30 | } 31 | 32 | impl From for Transposer { 33 | fn from(untyped: UntypedValue) -> Self { 34 | Transposer { 35 | value: u64::from(untyped), 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /legacy/src/rwasm/binary_format/drop_keep.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | engine::DropKeep, 3 | rwasm::{BinaryFormat, BinaryFormatError, BinaryFormatReader, BinaryFormatWriter}, 4 | }; 5 | 6 | impl<'a> BinaryFormat<'a> for DropKeep { 7 | type SelfType = DropKeep; 8 | 9 | fn encoded_length(&self) -> usize { 10 | 8 11 | } 12 | 13 | fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result { 14 | let mut n = 0; 15 | n += sink.write_u16_le(self.drop())?; 16 | n += sink.write_u16_le(self.keep())?; 17 | Ok(n) 18 | } 19 | 20 | fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result { 21 | let drop = sink.read_u16_le()?; 22 | let keep = sink.read_u16_le()?; 23 | Ok(DropKeep::new(drop as usize, keep as usize).unwrap()) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /legacy/src/rwasm/binary_format/instruction_set.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | engine::bytecode::Instruction, 3 | rwasm::{ 4 | binary_format::{ 5 | reader_writer::{BinaryFormatReader, BinaryFormatWriter}, 6 | BinaryFormat, 7 | BinaryFormatError, 8 | }, 9 | instruction_set::InstructionSet, 10 | }, 11 | }; 12 | 13 | impl<'a> BinaryFormat<'a> for InstructionSet { 14 | type SelfType = InstructionSet; 15 | 16 | fn encoded_length(&self) -> usize { 17 | let mut n = 0; 18 | for opcode in self.instrs().iter() { 19 | n += opcode.encoded_length(); 20 | } 21 | n 22 | } 23 | 24 | fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result { 25 | let mut n = 0; 26 | for opcode in self.instrs().iter() { 27 | n += opcode.write_binary(sink)?; 28 | } 29 | Ok(n) 30 | } 31 | 32 | fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result { 33 | let mut result = InstructionSet::new(); 34 | while !sink.is_empty() { 35 | result.push(Instruction::read_binary(sink)?); 36 | } 37 | Ok(result) 38 | } 39 | } 40 | 41 | #[cfg(test)] 42 | mod tests { 43 | use crate::rwasm::{BinaryFormat, InstructionSet}; 44 | use hex_literal::hex; 45 | 46 | #[test] 47 | fn decode_code_section() { 48 | let code_section = hex!("0a01000000000000000a00000000000000001101000000000000000b00000000000000000a01000000000000000a00000000000000001105000000000000000b00000000000000000a04000000000000001302000000000000003e00000000000000001000000000000000000900000000000000000a07000000000000001302000000000000003e00000400000000003e0c000000000000001001000000000000003e00000000000000001000000000000000000900000000000000000a01000000000000003f00000400000000001700000000000000003f0c000400000000001701000000000000003f10000400000000001702000000000000003e05000000000000003000000000000000001400000000000000003e00000400000000003f00000000000000003f0c000000000000003300000000000000003401000000000000001102000000000000000001000000000000003e01000000000000004300000000000000000404000000000000001400000000000000001002000000000000000b00000000000000000001000000000000003e00000000000000004300000000000000000404000000000000001400000000000000001003000000000000000b00000000000000001400000000000000000b0000000000000000"); 49 | let code_section = InstructionSet::read_from_slice(code_section.as_ref()).unwrap(); 50 | println!("{:?}", code_section.instr); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /legacy/src/rwasm/binary_format/mod.rs: -------------------------------------------------------------------------------- 1 | mod drop_keep; 2 | pub mod instruction; 3 | mod instruction_set; 4 | mod module; 5 | mod number; 6 | pub mod reader_writer; 7 | mod utils; 8 | 9 | pub use self::{ 10 | module::{RWASM_MAGIC_BYTE_0, RWASM_MAGIC_BYTE_1, RWASM_VERSION_V1}, 11 | reader_writer::{BinaryFormatReader, BinaryFormatWriter}, 12 | }; 13 | use alloc::vec::Vec; 14 | 15 | #[derive(Debug, Copy, Clone)] 16 | pub enum BinaryFormatError { 17 | ReachedUnreachable, 18 | NeedMore(usize), 19 | MalformedWasmModule, 20 | IllegalOpcode(u8), 21 | } 22 | 23 | pub trait BinaryFormat<'a> { 24 | type SelfType; 25 | 26 | fn encoded_length(&self) -> usize; 27 | 28 | fn write_binary_to_vec(&self, buffer: &'a mut Vec) -> Result { 29 | buffer.resize(self.encoded_length(), 0u8); 30 | let mut sink = BinaryFormatWriter::<'a>::new(buffer.as_mut_slice()); 31 | self.write_binary(&mut sink) 32 | } 33 | 34 | fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result; 35 | 36 | fn read_from_slice(sink: &'a [u8]) -> Result { 37 | let mut binary_format_reader = BinaryFormatReader::<'a>::new(sink); 38 | Self::read_binary(&mut binary_format_reader) 39 | } 40 | 41 | fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result; 42 | } 43 | -------------------------------------------------------------------------------- /legacy/src/rwasm/binary_format/number.rs: -------------------------------------------------------------------------------- 1 | use crate::rwasm::binary_format::{ 2 | reader_writer::{BinaryFormatReader, BinaryFormatWriter}, 3 | BinaryFormat, 4 | BinaryFormatError, 5 | }; 6 | 7 | macro_rules! impl_primitive_format { 8 | ($ty:ty, $write_method:ident, $read_method:ident) => { 9 | impl<'a> BinaryFormat<'a> for $ty { 10 | type SelfType = $ty; 11 | fn encoded_length(&self) -> usize { 12 | core::mem::size_of::<$ty>() 13 | } 14 | fn write_binary( 15 | &self, 16 | sink: &mut BinaryFormatWriter<'a>, 17 | ) -> Result { 18 | sink.$write_method(*self) 19 | } 20 | fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result<$ty, BinaryFormatError> { 21 | sink.$read_method() 22 | } 23 | } 24 | }; 25 | } 26 | 27 | impl_primitive_format!(u16, write_u16_le, read_u16_le); 28 | impl_primitive_format!(i16, write_i16_le, read_i16_le); 29 | impl_primitive_format!(u32, write_u32_le, read_u32_le); 30 | impl_primitive_format!(i32, write_i32_le, read_i32_le); 31 | impl_primitive_format!(u64, write_u64_le, read_u64_le); 32 | impl_primitive_format!(i64, write_i64_le, read_i64_le); 33 | -------------------------------------------------------------------------------- /legacy/src/rwasm/binary_format/utils.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | core::UntypedValue, 3 | engine::{ 4 | bytecode::{ 5 | AddressOffset, 6 | BlockFuel, 7 | BranchOffset, 8 | BranchTableTargets, 9 | DataSegmentIdx, 10 | ElementSegmentIdx, 11 | FuncIdx, 12 | GlobalIdx, 13 | LocalDepth, 14 | SignatureIdx, 15 | TableIdx, 16 | }, 17 | const_pool::ConstRef, 18 | CompiledFunc, 19 | }, 20 | rwasm::binary_format::{ 21 | reader_writer::{BinaryFormatReader, BinaryFormatWriter}, 22 | BinaryFormat, 23 | BinaryFormatError, 24 | }, 25 | }; 26 | 27 | impl<'a> BinaryFormat<'a> for UntypedValue { 28 | type SelfType = UntypedValue; 29 | 30 | fn encoded_length(&self) -> usize { 31 | 8 32 | } 33 | 34 | fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result { 35 | self.to_bits().write_binary(sink) 36 | } 37 | 38 | fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result { 39 | Ok(UntypedValue::from_bits(u64::read_binary(sink)?)) 40 | } 41 | } 42 | 43 | macro_rules! impl_default_idx { 44 | ($name:ident, $to_method:ident, $nested_type:ident) => { 45 | impl<'a> BinaryFormat<'a> for $name { 46 | type SelfType = $name; 47 | fn encoded_length(&self) -> usize { 48 | core::mem::size_of::<$nested_type>() 49 | } 50 | fn write_binary( 51 | &self, 52 | sink: &mut BinaryFormatWriter<'a>, 53 | ) -> Result { 54 | ((*self).$to_method() as $nested_type).write_binary(sink) 55 | } 56 | fn read_binary( 57 | sink: &mut BinaryFormatReader<'a>, 58 | ) -> Result { 59 | Ok($name::from($nested_type::read_binary(sink)?)) 60 | } 61 | } 62 | }; 63 | } 64 | 65 | impl_default_idx!(FuncIdx, to_u32, u32); 66 | impl_default_idx!(TableIdx, to_u32, u32); 67 | impl_default_idx!(SignatureIdx, to_u32, u32); 68 | impl_default_idx!(LocalDepth, to_usize, u32); 69 | impl_default_idx!(GlobalIdx, to_u32, u32); 70 | impl_default_idx!(DataSegmentIdx, to_u32, u32); 71 | impl_default_idx!(ElementSegmentIdx, to_u32, u32); 72 | impl_default_idx!(BranchTableTargets, to_usize, u32); 73 | impl_default_idx!(BlockFuel, to_u64, u32); 74 | impl_default_idx!(AddressOffset, into_inner, u32); 75 | impl_default_idx!(BranchOffset, to_i32, i32); 76 | impl_default_idx!(CompiledFunc, to_u32, u32); 77 | impl_default_idx!(ConstRef, to_usize, u32); 78 | -------------------------------------------------------------------------------- /legacy/src/rwasm/mod.rs: -------------------------------------------------------------------------------- 1 | mod types; 2 | pub use types::*; 3 | mod segment_builder; 4 | pub use segment_builder::*; 5 | mod translator; 6 | pub use translator::*; 7 | mod binary_format; 8 | pub use binary_format::*; 9 | mod instruction_set; 10 | pub use instruction_set::*; 11 | mod reduced_module; 12 | pub use reduced_module::*; 13 | mod drop_keep; 14 | pub use drop_keep::*; 15 | #[cfg(test)] 16 | mod tests; 17 | -------------------------------------------------------------------------------- /legacy/src/rwasm/types.rs: -------------------------------------------------------------------------------- 1 | use crate::module::ImportName; 2 | use core::{fmt, fmt::Display}; 3 | 4 | /// This constant is driven by WebAssembly standard, default 5 | /// memory page size is 64kB 6 | pub const N_BYTES_PER_MEMORY_PAGE: u32 = 65536; 7 | 8 | /// We have a hard limit for max possible memory used 9 | /// that is equal to ~64mB 10 | pub const N_MAX_MEMORY_PAGES: u32 = 1024; 11 | 12 | /// To optimize a proving process, we have to limit max 13 | /// number of pages, tables, etc. We found 1024 is enough. 14 | pub const N_MAX_TABLES: u32 = 1024; 15 | 16 | pub const N_MAX_STACK_HEIGHT: usize = 4096; 17 | pub const N_MAX_RECURSION_DEPTH: usize = 1024; 18 | 19 | #[derive(Debug)] 20 | pub enum RwasmBuilderError { 21 | MissingEntrypoint, 22 | MalformedFuncType, 23 | NotSupportedImport, 24 | UnknownImport(ImportName), 25 | ImportedGlobalsAreDisabled, 26 | NotSupportedGlobalExpr, 27 | OnlyFuncRefAllowed, 28 | ImportedMemoriesAreDisabled, 29 | ImportedTablesAreDisabled, 30 | } 31 | 32 | impl Display for RwasmBuilderError { 33 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 34 | match self { 35 | Self::MissingEntrypoint => write!(f, "MissingEntrypoint"), 36 | Self::MalformedFuncType => write!(f, "MalformedEntrypointFuncType"), 37 | Self::NotSupportedImport => write!(f, "NotSupportedImport"), 38 | Self::UnknownImport(_) => write!(f, "UnknownImport"), 39 | Self::ImportedGlobalsAreDisabled => write!(f, "ImportedGlobalsAreDisabled"), 40 | Self::NotSupportedGlobalExpr => write!(f, "NotSupportedGlobalExpr"), 41 | Self::OnlyFuncRefAllowed => write!(f, "OnlyFuncRefAllowed"), 42 | Self::ImportedMemoriesAreDisabled => write!(f, "ImportedMemoriesAreDisabled"), 43 | Self::ImportedTablesAreDisabled => write!(f, "ImportedTablesAreDisabled"), 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /legacy/src/table/error.rs: -------------------------------------------------------------------------------- 1 | use super::TableType; 2 | use crate::core::ValueType; 3 | use core::{fmt, fmt::Display}; 4 | 5 | /// Errors that may occur upon operating with table entities. 6 | #[derive(Debug)] 7 | #[non_exhaustive] 8 | pub enum TableError { 9 | /// Occurs when growing a table out of its set bounds. 10 | GrowOutOfBounds { 11 | /// The maximum allowed table size. 12 | maximum: u32, 13 | /// The current table size before the growth operation. 14 | current: u32, 15 | /// The amount of requested invalid growth. 16 | delta: u32, 17 | }, 18 | /// Occurs when operating with a [`Table`](crate::Table) and mismatching element types. 19 | ElementTypeMismatch { 20 | /// Expected element type for the [`Table`](crate::Table). 21 | expected: ValueType, 22 | /// Encountered element type. 23 | actual: ValueType, 24 | }, 25 | /// Occurs when accessing the table out of bounds. 26 | AccessOutOfBounds { 27 | /// The current size of the table. 28 | current: u32, 29 | /// The accessed index that is out of bounds. 30 | offset: u32, 31 | }, 32 | /// Occur when coping elements of tables out of bounds. 33 | CopyOutOfBounds, 34 | /// Occurs when `ty` is not a subtype of `other`. 35 | InvalidSubtype { 36 | /// The [`TableType`] which is not a subtype of `other`. 37 | ty: TableType, 38 | /// The [`TableType`] which is supposed to be a supertype of `ty`. 39 | other: TableType, 40 | }, 41 | TooManyTables, 42 | } 43 | 44 | impl Display for TableError { 45 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 46 | match self { 47 | Self::GrowOutOfBounds { 48 | maximum, 49 | current, 50 | delta, 51 | } => { 52 | write!( 53 | f, 54 | "tried to grow table with size of {current} and maximum of \ 55 | {maximum} by {delta} out of bounds", 56 | ) 57 | } 58 | Self::ElementTypeMismatch { expected, actual } => { 59 | write!(f, "encountered mismatching table element type, expected {expected:?} but found {actual:?}") 60 | } 61 | Self::AccessOutOfBounds { current, offset } => { 62 | write!( 63 | f, 64 | "out of bounds access of table element {offset} \ 65 | of table with size {current}", 66 | ) 67 | } 68 | Self::CopyOutOfBounds => { 69 | write!(f, "out of bounds access of table elements while copying") 70 | } 71 | Self::InvalidSubtype { ty, other } => { 72 | write!(f, "table type {ty:?} is not a subtype of {other:?}",) 73 | } 74 | Self::TooManyTables => { 75 | write!(f, "too many tables") 76 | } 77 | } 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /legacy/src/table/tests.rs: -------------------------------------------------------------------------------- 1 | use super::*; 2 | 3 | fn table_type(element: ValueType, minimum: u32, maximum: impl Into>) -> TableType { 4 | TableType::new(element, minimum, maximum.into()) 5 | } 6 | 7 | use ValueType::{F64, I32}; 8 | 9 | #[test] 10 | fn subtyping_works() { 11 | assert!(!table_type(I32, 0, 1).is_subtype_of(&table_type(F64, 0, 1))); 12 | assert!(table_type(I32, 0, 1).is_subtype_of(&table_type(I32, 0, 1))); 13 | assert!(table_type(I32, 0, 1).is_subtype_of(&table_type(I32, 0, 2))); 14 | assert!(!table_type(I32, 0, 2).is_subtype_of(&table_type(I32, 0, 1))); 15 | assert!(table_type(I32, 2, None).is_subtype_of(&table_type(I32, 1, None))); 16 | assert!(table_type(I32, 0, None).is_subtype_of(&table_type(I32, 0, None))); 17 | assert!(table_type(I32, 0, 1).is_subtype_of(&table_type(I32, 0, None))); 18 | assert!(!table_type(I32, 0, None).is_subtype_of(&table_type(I32, 0, 1))); 19 | } 20 | -------------------------------------------------------------------------------- /rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2025-01-27 -------------------------------------------------------------------------------- /snippets/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "snippets" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | path = "lib.rs" 9 | 10 | [dev-dependencies] 11 | rand = "0.9.1" 12 | 13 | [profile.release] 14 | panic = "abort" 15 | lto = true 16 | opt-level = 3 17 | strip = false 18 | debug = false 19 | debug-assertions = false 20 | rpath = false 21 | codegen-units = 1 22 | 23 | [dependencies] 24 | walrus = "0.23.3" 25 | rwasm = { path = ".." } 26 | -------------------------------------------------------------------------------- /snippets/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: lib.wasm 2 | lib.wasm: Cargo.toml lib.rs 3 | cargo b --release --target=wasm32-unknown-unknown --no-default-features 4 | cp ./target/wasm32-unknown-unknown/release/snippets.wasm lib.wasm 5 | wasm2wat lib.wasm > lib.wat -------------------------------------------------------------------------------- /snippets/extractor.rs: -------------------------------------------------------------------------------- 1 | use walrus::ir::{BinaryOp, Binop, Const, Instr, InstrSeq, UnaryOp, Unop, Value}; 2 | 3 | fn extract_wasm_snippet(wasm_binary: &[u8]) { 4 | let module = walrus::Module::from_buffer(wasm_binary).unwrap(); 5 | for func in module.funcs.iter() { 6 | let Some(func_name) = func.name.clone() else { 7 | continue; 8 | }; 9 | let kind = func.kind.unwrap_local(); 10 | assert_eq!(kind.args.len(), 4); 11 | let block = kind.block(kind.entry_block()); 12 | println!("func: {}", func_name); 13 | test_ending_opcodes(&block); 14 | println!() 15 | } 16 | } 17 | 18 | #[rustfmt::skip] 19 | fn test_ending_opcodes(seq: &InstrSeq) { 20 | let fake_block = seq 21 | .iter() 22 | .rev() 23 | .take(6) 24 | .map(|v| v.0.clone()) 25 | .rev() 26 | .collect::>(); 27 | for instr in &fake_block { 28 | println!(" - {:?}", instr); 29 | } 30 | assert_eq!(fake_block.len(), 6); 31 | // make sure the fake block we injected matches a>>32|b 32 | assert!(matches!(fake_block[0],Instr::Unop(Unop {op: UnaryOp::I64ExtendUI32}))); 33 | assert!(matches!(fake_block[1], Instr::Const(Const{value: Value::I64(32)}))); 34 | assert!(matches!(fake_block[2],Instr::Binop(Binop {op: BinaryOp::I64Shl}))); 35 | assert!(matches!(fake_block[3],Instr::LocalGet(..))); 36 | assert!(matches!(fake_block[4],Instr::Unop(Unop {op: UnaryOp::I64ExtendUI32}))); 37 | assert!(matches!(fake_block[5],Instr::Binop(Binop {op: BinaryOp::I64Or}))); 38 | } 39 | 40 | #[test] 41 | fn test_extract_i64_add() { 42 | let wasm_binary = include_bytes!("./lib.wasm"); 43 | extract_wasm_snippet(wasm_binary); 44 | } 45 | -------------------------------------------------------------------------------- /snippets/lib.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluentlabs-xyz/rwasm/4e3597f78bf038a39601f5e8e266715b5834123f/snippets/lib.wasm -------------------------------------------------------------------------------- /snippets/test.rs: -------------------------------------------------------------------------------- 1 | use crate::{add64_impl, div64s_impl, div64u_impl, karatsuba_mul64_impl, sub64_impl}; 2 | use rand::Rng; 3 | 4 | #[test] 5 | fn test_i64_mul_fuzz() { 6 | for _ in 0..1_000_000 { 7 | let a = rand::rng().random::(); 8 | let b = rand::rng().random::(); 9 | let c = a.wrapping_mul(b); 10 | let (r_lo, r_hi) = 11 | karatsuba_mul64_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); 12 | let r = (r_hi as u64) << 32 | r_lo as u64; 13 | assert_eq!(c, r); 14 | } 15 | } 16 | 17 | #[test] 18 | fn test_i64_add_fuzz() { 19 | for _ in 0..1_000_000 { 20 | let a = rand::rng().random::(); 21 | let b = rand::rng().random::(); 22 | let c = a.wrapping_add(b); 23 | let (r_lo, r_hi) = add64_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); 24 | let r = (r_hi as u64) << 32 | r_lo as u64; 25 | assert_eq!(c, r); 26 | } 27 | } 28 | 29 | #[test] 30 | fn test_i64_div_u_fuzz() { 31 | for _ in 0..1_000_000 { 32 | let a = rand::rng().random::(); 33 | let b = rand::rng().random::(); 34 | let c = a.wrapping_div(b); 35 | let (r_lo, r_hi) = div64u_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); 36 | let r = (r_hi as u64) << 32 | r_lo as u64; 37 | assert_eq!(c, r); 38 | } 39 | } 40 | 41 | #[test] 42 | fn test_i64_div_s_fuzz() { 43 | for _ in 0..1_000_000 { 44 | let a = rand::rng().random::(); 45 | let b = rand::rng().random::(); 46 | let c = a.wrapping_div(b); 47 | let (r_lo, r_hi) = div64s_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); 48 | let r = (r_hi as i64) << 32 | r_lo as i64; 49 | assert_eq!(c, r); 50 | } 51 | } 52 | 53 | #[test] 54 | fn test_i64_sub_fuzz() { 55 | for _ in 0..1_000_000 { 56 | let a = rand::rng().random::(); 57 | let b = rand::rng().random::(); 58 | let c = a.wrapping_sub(b); 59 | let (r_lo, r_hi) = sub64_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); 60 | let r = (r_hi as u64) << 32 | r_lo as u64; 61 | assert_eq!(c, r); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/compiler.rs: -------------------------------------------------------------------------------- 1 | use alloc::{vec, vec::Vec}; 2 | use rwasm_legacy::{ 3 | engine::RwasmConfig, 4 | rwasm::{BinaryFormat, BinaryFormatWriter, RwasmModule}, 5 | Error, 6 | }; 7 | 8 | pub struct RwasmCompilationResult { 9 | pub rwasm_bytecode: Vec, 10 | pub constructor_params: Vec, 11 | } 12 | 13 | pub fn compile_wasm_to_rwasm( 14 | wasm_binary: &[u8], 15 | rwasm_config: RwasmConfig, 16 | ) -> Result { 17 | let mut config = RwasmModule::default_config(None); 18 | config.rwasm_config(rwasm_config); 19 | let (rwasm_module, constructor_params) = 20 | RwasmModule::compile_and_retrieve_input(wasm_binary, &config)?; 21 | let length = rwasm_module.encoded_length(); 22 | let mut rwasm_bytecode = vec![0u8; length]; 23 | let mut binary_format_writer = BinaryFormatWriter::new(&mut rwasm_bytecode); 24 | rwasm_module 25 | .write_binary(&mut binary_format_writer) 26 | .expect("failed to encode rwasm bytecode"); 27 | Ok(RwasmCompilationResult { 28 | rwasm_bytecode, 29 | constructor_params, 30 | }) 31 | } 32 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | #![allow(dead_code)] 3 | #![warn(unused_crate_dependencies)] 4 | 5 | mod compiler; 6 | mod types; 7 | mod vm; 8 | 9 | extern crate alloc; 10 | extern crate core; 11 | 12 | pub use compiler::*; 13 | pub use types::*; 14 | pub use vm::*; 15 | 16 | pub mod legacy { 17 | pub use rwasm_legacy::*; 18 | } 19 | 20 | use libm as _; 21 | -------------------------------------------------------------------------------- /src/types/address_offset.rs: -------------------------------------------------------------------------------- 1 | use bincode::{Decode, Encode}; 2 | 3 | /// A linear memory access offset. 4 | /// 5 | /// # Note 6 | /// 7 | /// Used to calculate the effective address of linear memory access. 8 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] 9 | #[repr(transparent)] 10 | pub struct AddressOffset(u32); 11 | 12 | impl From for AddressOffset { 13 | fn from(index: u32) -> Self { 14 | Self(index) 15 | } 16 | } 17 | 18 | impl AddressOffset { 19 | /// Returns the inner `u32` index. 20 | pub fn into_inner(self) -> u32 { 21 | self.0 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/types/block_fuel.rs: -------------------------------------------------------------------------------- 1 | use crate::RwasmError; 2 | use bincode::{Decode, Encode}; 3 | 4 | /// The accumulated fuel to execute a block via [`Instruction::ConsumeFuel`]. 5 | /// 6 | /// [`Instruction::ConsumeFuel`]: [`super::Instruction::ConsumeFuel`] 7 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] 8 | #[repr(transparent)] 9 | pub struct BlockFuel(u32); 10 | 11 | impl TryFrom for BlockFuel { 12 | type Error = RwasmError; 13 | 14 | fn try_from(index: u64) -> Result { 15 | match u32::try_from(index) { 16 | Ok(index) => Ok(Self(index)), 17 | Err(_) => Err(RwasmError::BlockFuelOutOfBounds), 18 | } 19 | } 20 | } 21 | 22 | impl From for BlockFuel { 23 | fn from(value: u32) -> Self { 24 | BlockFuel(value) 25 | } 26 | } 27 | 28 | impl BlockFuel { 29 | /// Bump the fuel by `amount` if possible. 30 | /// 31 | /// # Errors 32 | /// 33 | /// If the new fuel amount after this operation is out of bounds. 34 | pub fn bump_by(&mut self, amount: u64) -> Result<(), RwasmError> { 35 | let new_amount = self 36 | .to_u64() 37 | .checked_add(amount) 38 | .ok_or(RwasmError::BlockFuelOutOfBounds)?; 39 | self.0 = u32::try_from(new_amount).map_err(|_| RwasmError::BlockFuelOutOfBounds)?; 40 | Ok(()) 41 | } 42 | 43 | /// Returns the index value as `u64`. 44 | pub fn to_u64(self) -> u64 { 45 | u64::from(self.0) 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/types/branch_offset.rs: -------------------------------------------------------------------------------- 1 | use crate::RwasmError; 2 | use bincode::{Decode, Encode}; 3 | 4 | /// A signed offset for branch instructions. 5 | /// 6 | /// This defines how much the instruction pointer is offset 7 | /// upon taking the respective branch. 8 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] 9 | pub struct BranchOffset(i32); 10 | 11 | impl From for BranchOffset { 12 | fn from(index: i32) -> Self { 13 | Self(index) 14 | } 15 | } 16 | 17 | impl BranchOffset { 18 | /// Creates an uninitalized [`BranchOffset`]. 19 | pub fn uninit() -> Self { 20 | Self(0) 21 | } 22 | 23 | /// Creates an initialized [`BranchOffset`] from `src` to `dst`. 24 | /// 25 | /// # Errors 26 | /// 27 | /// If the resulting [`BranchOffset`] is out of bounds. 28 | /// 29 | /// # Panics 30 | /// 31 | /// If the resulting [`BranchOffset`] is uninitialized, aka equal to 0. 32 | pub fn from_src_to_dst(src: u32, dst: u32) -> Result { 33 | fn make_err() -> RwasmError { 34 | RwasmError::BranchOffsetOutOfBounds 35 | } 36 | let src = i64::from(src); 37 | let dst = i64::from(dst); 38 | let offset = dst.checked_sub(src).ok_or_else(make_err)?; 39 | let offset = i32::try_from(offset).map_err(|_| make_err())?; 40 | Ok(Self(offset)) 41 | } 42 | 43 | /// Returns `true` if the [`BranchOffset`] has been initialized. 44 | pub fn is_init(self) -> bool { 45 | self.to_i32() != 0 46 | } 47 | 48 | /// Initializes the [`BranchOffset`] with a proper value. 49 | /// 50 | /// # Panics 51 | /// 52 | /// - If the [`BranchOffset`] have already been initialized. 53 | /// - If the given [`BranchOffset`] is not properly initialized. 54 | pub fn init(&mut self, valid_offset: BranchOffset) { 55 | assert!(valid_offset.is_init()); 56 | assert!(!self.is_init()); 57 | *self = valid_offset; 58 | } 59 | 60 | /// Returns the `i32` representation of the [`BranchOffset`]. 61 | pub fn to_i32(self) -> i32 { 62 | self.0 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/types/drop_keep.rs: -------------------------------------------------------------------------------- 1 | use bincode::{Decode, Encode}; 2 | use core::{fmt, fmt::Display}; 3 | 4 | /// Defines how many stack values are going to be dropped and kept after branching. 5 | #[derive(Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] 6 | pub struct DropKeep { 7 | pub drop: u16, 8 | pub keep: u16, 9 | } 10 | 11 | impl fmt::Debug for DropKeep { 12 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 13 | f.debug_struct("DropKeep") 14 | .field("drop", &self.drop()) 15 | .field("keep", &self.keep()) 16 | .finish() 17 | } 18 | } 19 | 20 | /// An error that may occur upon operating on [`DropKeep`]. 21 | #[derive(Debug, Copy, Clone)] 22 | pub enum DropKeepError { 23 | /// The number of kept elements exceeds the engine's limits. 24 | KeepOutOfBounds, 25 | /// The number of dropped elements exceeds the engine's limits. 26 | DropOutOfBounds, 27 | } 28 | 29 | impl Display for DropKeepError { 30 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 31 | match self { 32 | DropKeepError::KeepOutOfBounds => { 33 | write!(f, "number of kept elements exceeds engine limits") 34 | } 35 | DropKeepError::DropOutOfBounds => { 36 | write!(f, "number of dropped elements exceeds engine limits") 37 | } 38 | } 39 | } 40 | } 41 | 42 | impl DropKeep { 43 | pub fn none() -> Self { 44 | Self { drop: 0, keep: 0 } 45 | } 46 | 47 | /// Returns the number of stack values to keep. 48 | pub fn keep(self) -> u16 { 49 | self.keep 50 | } 51 | 52 | pub fn add_keep(&mut self, delta: u16) { 53 | self.keep += delta; 54 | } 55 | 56 | /// Returns the number of stack values to drop. 57 | pub fn drop(self) -> u16 { 58 | self.drop 59 | } 60 | 61 | /// Returns `true` if the [`DropKeep`] does nothing. 62 | pub fn is_noop(self) -> bool { 63 | self.drop == 0 64 | } 65 | 66 | /// Creates a new [`DropKeep`] with the given amounts to drop and keep. 67 | /// 68 | /// # Errors 69 | /// 70 | /// - If `keep` is larger than `drop`. 71 | /// - If `keep` is out of bounds. (max 4095) 72 | /// - If `drop` is out of bounds. (delta to keep max 4095) 73 | pub fn new(drop: usize, keep: usize) -> Result { 74 | let keep = u16::try_from(keep).map_err(|_| DropKeepError::KeepOutOfBounds)?; 75 | let drop = u16::try_from(drop).map_err(|_| DropKeepError::KeepOutOfBounds)?; 76 | // Now we can cast `drop` and `keep` to `u16` values safely. 77 | Ok(Self { drop, keep }) 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/types/error.rs: -------------------------------------------------------------------------------- 1 | use crate::types::HostError; 2 | use alloc::boxed::Box; 3 | use core::fmt::Formatter; 4 | 5 | #[derive(Debug)] 6 | pub enum RwasmError { 7 | MalformedBinary, 8 | UnknownExternalFunction(u32), 9 | ExecutionHalted(i32), 10 | HostInterruption(Box), 11 | FloatsAreDisabled, 12 | NotAllowedInFuelMode, 13 | UnreachableCodeReached, 14 | MemoryOutOfBounds, 15 | TableOutOfBounds, 16 | IndirectCallToNull, 17 | IntegerDivisionByZero, 18 | IntegerOverflow, 19 | BadConversionToInteger, 20 | StackOverflow, 21 | BadSignature, 22 | OutOfFuel, 23 | GrowthOperationLimited, 24 | UnresolvedFunction, 25 | BranchOffsetOutOfBounds, 26 | BlockFuelOutOfBounds, 27 | BranchTableTargetsOutOfBounds, 28 | } 29 | 30 | impl core::fmt::Display for RwasmError { 31 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 32 | match self { 33 | RwasmError::MalformedBinary => write!(f, "malformed binary"), 34 | RwasmError::UnknownExternalFunction(_) => write!(f, "unknown external function"), 35 | RwasmError::ExecutionHalted(_) => write!(f, "execution halted"), 36 | RwasmError::HostInterruption(_) => write!(f, "host interruption"), 37 | RwasmError::FloatsAreDisabled => write!(f, "floats are disabled"), 38 | RwasmError::NotAllowedInFuelMode => write!(f, "not allowed in fuel mode"), 39 | RwasmError::UnreachableCodeReached => write!(f, "unreachable code reached"), 40 | RwasmError::MemoryOutOfBounds => write!(f, "out of bounds memory access"), 41 | RwasmError::TableOutOfBounds => { 42 | write!(f, "undefined element: out of bounds table access") 43 | } 44 | RwasmError::IndirectCallToNull => write!(f, "uninitialized element 2"), 45 | RwasmError::IntegerDivisionByZero => write!(f, "integer divide by zero"), 46 | RwasmError::IntegerOverflow => write!(f, "integer overflow"), 47 | RwasmError::BadConversionToInteger => write!(f, "invalid conversion to integer"), 48 | RwasmError::StackOverflow => write!(f, "call stack exhausted"), 49 | RwasmError::BadSignature => write!(f, "indirect call type mismatch"), 50 | RwasmError::OutOfFuel => write!(f, "out of fuel"), 51 | RwasmError::GrowthOperationLimited => write!(f, "growth operation limited"), 52 | RwasmError::UnresolvedFunction => write!(f, "unresolved function"), 53 | RwasmError::BranchOffsetOutOfBounds => write!(f, "branch offset out of bounds"), 54 | RwasmError::BlockFuelOutOfBounds => write!(f, "block fuel out of bounds"), 55 | RwasmError::BranchTableTargetsOutOfBounds => { 56 | write!(f, "branch table targets are out of bounds") 57 | } 58 | } 59 | } 60 | } 61 | 62 | impl RwasmError { 63 | pub fn unwrap_exit_code(&self) -> i32 { 64 | match self { 65 | RwasmError::ExecutionHalted(exit_code) => *exit_code, 66 | _ => unreachable!("runtime: can't unwrap exit code from error"), 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/types/fuel_costs.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{DropKeep, UntypedValue}; 2 | use core::num::NonZeroU64; 3 | 4 | /// Type storing all kinds of fuel costs of instructions. 5 | #[derive(Debug, Copy, Clone)] 6 | pub struct FuelCosts { 7 | /// The base fuel costs for all instructions. 8 | pub base: u64, 9 | /// The fuel cost for instruction operating on Wasm entities. 10 | /// 11 | /// # Note 12 | /// 13 | /// A Wasm entity is one of `func`, `global`, `memory` or `table`. 14 | /// Those instructions are usually a bit more costly since they need 15 | /// multiple indirect accesses through the Wasm instance and store. 16 | pub entity: u64, 17 | /// The fuel cost offset for `memory.load` instructions. 18 | pub load: u64, 19 | /// The fuel cost offset for `memory.store` instructions. 20 | pub store: u64, 21 | /// The fuel cost offset for `call` and `call_indirect` instructions. 22 | pub call: u64, 23 | /// Determines how many moved stack values consume one fuel upon a branch or return 24 | /// instruction. 25 | /// 26 | /// # Note 27 | /// 28 | /// If this is zero, then processing [`DropKeep`] costs nothing. 29 | branch_kept_per_fuel: u64, 30 | /// Determines how many function locals consume one fuel per function call. 31 | /// 32 | /// # Note 33 | /// 34 | /// - This is also applied to all function parameters since they are translated to local 35 | /// variable slots. 36 | /// - If this is zero then processing function locals costs nothing. 37 | func_locals_per_fuel: u64, 38 | /// How many memory bytes can be processed per fuel in a `bulk-memory` instruction? 39 | /// 40 | /// # Note 41 | /// 42 | /// If this is zero, then processing memory bytes costs nothing. 43 | memory_bytes_per_fuel: u64, 44 | /// How many table elements can be processed per fuel in a `bulk-table` instruction? 45 | /// 46 | /// # Note 47 | /// 48 | /// If this is zero, then processing table elements costs nothing. 49 | table_elements_per_fuel: u64, 50 | } 51 | 52 | impl FuelCosts { 53 | /// Returns the fuel consumption of the number of items with costs per items. 54 | fn costs_per(len_items: u64, items_per_fuel: u64) -> u64 { 55 | NonZeroU64::new(items_per_fuel) 56 | .map(|items_per_fuel| len_items / items_per_fuel) 57 | .unwrap_or(0) 58 | } 59 | 60 | /// Returns the fuel consumption for branches and returns using the given [`DropKeep`]. 61 | pub fn fuel_for_drop_keep(&self, drop_keep: DropKeep) -> u64 { 62 | if drop_keep.drop == 0 { 63 | return 0; 64 | } 65 | Self::costs_per(u64::from(drop_keep.keep), self.branch_kept_per_fuel) 66 | } 67 | 68 | /// Returns the fuel consumption for calling a function with the amount of local variables. 69 | /// 70 | /// # Note 71 | /// 72 | /// Function parameters are also treated as local variables. 73 | pub fn fuel_for_locals(&self, locals: u64) -> u64 { 74 | Self::costs_per(locals, self.func_locals_per_fuel) 75 | } 76 | 77 | /// Returns the fuel consumption for processing the amount of memory bytes. 78 | pub fn fuel_for_bytes(&self, bytes: u64) -> u64 { 79 | Self::costs_per(bytes, self.memory_bytes_per_fuel) 80 | } 81 | 82 | /// Returns the fuel consumption for processing the amount of table elements. 83 | pub fn fuel_for_elements(&self, elements: u64) -> u64 { 84 | Self::costs_per(elements, self.table_elements_per_fuel) 85 | } 86 | } 87 | 88 | impl Default for FuelCosts { 89 | fn default() -> Self { 90 | let memory_bytes_per_fuel = 64; 91 | let bytes_per_register = size_of::() as u64; 92 | let registers_per_fuel = memory_bytes_per_fuel / bytes_per_register; 93 | Self { 94 | base: 1, 95 | entity: 1, 96 | load: 1, 97 | store: 1, 98 | call: 1, 99 | func_locals_per_fuel: registers_per_fuel, 100 | branch_kept_per_fuel: registers_per_fuel, 101 | memory_bytes_per_fuel, 102 | table_elements_per_fuel: registers_per_fuel, 103 | } 104 | } 105 | } 106 | -------------------------------------------------------------------------------- /src/types/func_ref.rs: -------------------------------------------------------------------------------- 1 | use crate::{UntypedValue, FUNC_REF_NULL, FUNC_REF_OFFSET}; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct FuncRef(u32); 5 | 6 | impl FuncRef { 7 | pub fn new(func_idx: u32) -> Self { 8 | Self(func_idx + FUNC_REF_OFFSET) 9 | } 10 | 11 | pub fn null() -> Self { 12 | Self(FUNC_REF_NULL) 13 | } 14 | 15 | pub fn resolve_index(&self) -> u32 { 16 | assert!(!self.is_null(), "rwasm: resolve of null func ref"); 17 | self.0 - FUNC_REF_OFFSET 18 | } 19 | 20 | pub fn is_null(&self) -> bool { 21 | self.0 == FUNC_REF_NULL 22 | } 23 | } 24 | 25 | impl From for FuncRef { 26 | fn from(value: UntypedValue) -> Self { 27 | let value = value.as_u32(); 28 | if value == 0 { 29 | Self::null() 30 | } else { 31 | Self(value) 32 | } 33 | } 34 | } 35 | impl Into for FuncRef { 36 | fn into(self) -> UntypedValue { 37 | UntypedValue::from(self.0) 38 | } 39 | } 40 | 41 | pub type ExternRef = FuncRef; 42 | -------------------------------------------------------------------------------- /src/types/host_error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::{Debug, Display}; 2 | use downcast_rs::{impl_downcast, DowncastSync}; 3 | 4 | /// Trait that allows the host to return a custom error. 5 | /// 6 | /// It should be useful for representing custom traps, 7 | /// troubles at instantiation time or other host-specific conditions. 8 | /// 9 | /// Types that implement this trait can automatically be converted to `wasmi::Error` and 10 | /// `wasmi::Trap` and will be represented as a boxed `HostError`. You can then use the various 11 | /// methods on `wasmi::Error` to get your custom error type back 12 | pub trait HostError: 'static + Display + Debug + DowncastSync {} 13 | impl_downcast!(HostError); 14 | -------------------------------------------------------------------------------- /src/types/import_linker.rs: -------------------------------------------------------------------------------- 1 | use crate::{ImportName, ValueType}; 2 | use core::ops::{Deref, DerefMut}; 3 | use hashbrown::HashMap; 4 | 5 | #[derive(Debug, Default, Clone)] 6 | pub struct ImportLinker { 7 | func_by_name: HashMap, 8 | } 9 | 10 | impl Deref for ImportLinker { 11 | type Target = HashMap; 12 | 13 | fn deref(&self) -> &Self::Target { 14 | &self.func_by_name 15 | } 16 | } 17 | impl DerefMut for ImportLinker { 18 | fn deref_mut(&mut self) -> &mut Self::Target { 19 | &mut self.func_by_name 20 | } 21 | } 22 | 23 | #[derive(Debug, Clone)] 24 | pub struct ImportLinkerEntity { 25 | pub func_idx: u32, 26 | pub block_fuel: u32, 27 | pub params: &'static [ValueType], 28 | pub result: &'static [ValueType], 29 | } 30 | 31 | impl From<[(&'static str, &'static str, ImportLinkerEntity); N]> for ImportLinker { 32 | fn from(arr: [(&'static str, &'static str, ImportLinkerEntity); N]) -> Self { 33 | Self { 34 | func_by_name: HashMap::from_iter(arr.into_iter().map( 35 | |(module_name, fn_name, entity)| (ImportName::new(module_name, fn_name), entity), 36 | )), 37 | } 38 | } 39 | } 40 | 41 | impl From<[(ImportName, ImportLinkerEntity); N]> for ImportLinker { 42 | fn from(arr: [(ImportName, ImportLinkerEntity); N]) -> Self { 43 | Self { 44 | func_by_name: HashMap::from(arr), 45 | } 46 | } 47 | } 48 | 49 | impl ImportLinker { 50 | pub fn insert_function( 51 | &mut self, 52 | import_name: ImportName, 53 | func_idx: u32, 54 | block_fuel: u32, 55 | params: &'static [ValueType], 56 | result: &'static [ValueType], 57 | ) { 58 | let last_value = self.func_by_name.insert( 59 | import_name, 60 | ImportLinkerEntity { 61 | func_idx, 62 | block_fuel, 63 | params, 64 | result, 65 | }, 66 | ); 67 | assert!(last_value.is_none(), "rwasm: import linker name collision"); 68 | } 69 | 70 | pub fn resolve_by_import_name(&self, import_name: &ImportName) -> Option<&ImportLinkerEntity> { 71 | self.func_by_name.get(import_name) 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/types/import_name.rs: -------------------------------------------------------------------------------- 1 | use alloc::boxed::Box; 2 | 3 | /// The name or namespace of an imported item. 4 | #[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)] 5 | pub struct ImportName { 6 | /// The name of the [`Module`] that defines the imported item. 7 | /// 8 | /// [`Module`]: [`super::Module`] 9 | pub(crate) module: Box, 10 | /// The name of the imported item within the [`Module`] namespace. 11 | /// 12 | /// [`Module`]: [`super::Module`] 13 | pub(crate) field: Box, 14 | } 15 | 16 | impl core::fmt::Display for ImportName { 17 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { 18 | let module_name = &*self.module; 19 | let field_name = &*self.field; 20 | write!(f, "{module_name}::{field_name}") 21 | } 22 | } 23 | 24 | impl ImportName { 25 | /// Creates a new [`Import`] item. 26 | pub fn new(module: &str, field: &str) -> Self { 27 | Self { 28 | module: module.into(), 29 | field: field.into(), 30 | } 31 | } 32 | 33 | /// Returns the name of the [`Module`] that defines the imported item. 34 | /// 35 | /// [`Module`]: [`super::Module`] 36 | pub fn module(&self) -> &str { 37 | &self.module 38 | } 39 | 40 | /// Returns the name of the imported item within the [`Module`] namespace. 41 | /// 42 | /// [`Module`]: [`super::Module`] 43 | pub fn name(&self) -> &str { 44 | &self.field 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/types/mod.rs: -------------------------------------------------------------------------------- 1 | mod address_offset; 2 | mod block_fuel; 3 | mod branch_offset; 4 | mod drop_keep; 5 | mod error; 6 | mod fuel_costs; 7 | mod func_ref; 8 | mod host_error; 9 | mod import_linker; 10 | mod import_name; 11 | mod instruction; 12 | mod instruction_set; 13 | mod module; 14 | mod nan_preserving_float; 15 | mod units; 16 | mod untyped_value; 17 | mod utils; 18 | mod value; 19 | 20 | pub const N_DEFAULT_STACK_SIZE: usize = 4096; 21 | pub const N_MAX_STACK_SIZE: usize = 4096; 22 | pub const N_MAX_TABLE_SIZE: usize = 1024; 23 | pub const N_MAX_RECURSION_DEPTH: usize = 1024; 24 | 25 | pub const N_MAX_DATA_SEGMENTS: usize = 1024; 26 | 27 | pub const DEFAULT_TABLE_LIMIT: usize = 1024; 28 | pub const DEFAULT_MEMORY_LIMIT: usize = 10000; 29 | 30 | pub const DEFAULT_MIN_VALUE_STACK_HEIGHT: usize = 1024; 31 | pub const DEFAULT_MAX_VALUE_STACK_HEIGHT: usize = 1024; 32 | 33 | pub const FUNC_REF_NULL: u32 = 0; 34 | pub const FUNC_REF_OFFSET: u32 = 1000; 35 | 36 | /// This constant is driven by WebAssembly standard, default 37 | /// memory page size is 64kB 38 | pub const N_BYTES_PER_MEMORY_PAGE: u32 = 65536; 39 | 40 | /// We have a hard limit for max possible memory used 41 | /// that is equal to ~64mB 42 | #[cfg(not(feature = "more-max-pages"))] 43 | pub const N_MAX_MEMORY_PAGES: u32 = 1024; 44 | /// Increased value needed for SVM for now 45 | #[cfg(feature = "more-max-pages")] 46 | pub const N_MAX_MEMORY_PAGES: u32 = 1024 * 10; 47 | 48 | /// To optimize a proving process, we have to limit the max 49 | /// number of pages, tables, etc. We found 1024 is enough. 50 | pub const N_MAX_TABLES: u32 = 1024; 51 | 52 | pub const N_MAX_STACK_HEIGHT: usize = 4096; 53 | 54 | pub const DEFAULT_MEMORY_INDEX: u32 = 0; 55 | 56 | pub type CompiledFunc = u32; 57 | 58 | pub use address_offset::*; 59 | pub use block_fuel::*; 60 | pub use branch_offset::*; 61 | pub use drop_keep::*; 62 | pub use error::*; 63 | pub use fuel_costs::*; 64 | pub use func_ref::*; 65 | pub use host_error::*; 66 | pub use import_linker::*; 67 | pub use import_name::*; 68 | pub use instruction::*; 69 | pub use instruction_set::*; 70 | pub use module::*; 71 | pub use nan_preserving_float::*; 72 | pub use units::*; 73 | pub use untyped_value::*; 74 | pub use utils::*; 75 | pub use value::*; 76 | -------------------------------------------------------------------------------- /src/vm/config.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct ExecutorConfig { 3 | pub fuel_enabled: bool, 4 | pub fuel_limit: Option, 5 | pub floats_enabled: bool, 6 | pub trace_enabled: bool, 7 | } 8 | 9 | impl ExecutorConfig { 10 | pub fn new() -> Self { 11 | Self { 12 | fuel_enabled: true, 13 | fuel_limit: None, 14 | floats_enabled: false, 15 | trace_enabled: false, 16 | } 17 | } 18 | 19 | pub fn fuel_enabled(mut self, fuel_enabled: bool) -> Self { 20 | self.fuel_enabled = fuel_enabled; 21 | self 22 | } 23 | 24 | pub fn fuel_limit(mut self, fuel_limit: u64) -> Self { 25 | self.fuel_limit = Some(fuel_limit); 26 | self 27 | } 28 | 29 | pub fn floats_enabled(mut self, floats_enabled: bool) -> Self { 30 | self.floats_enabled = floats_enabled; 31 | self 32 | } 33 | 34 | pub fn trace_enabled(mut self, trace_enabled: bool) -> Self { 35 | self.trace_enabled = trace_enabled; 36 | self 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/vm/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | types::{RwasmError, UntypedValue}, 3 | vm::executor::RwasmExecutor, 4 | }; 5 | use alloc::{vec, vec::Vec}; 6 | 7 | pub struct Caller<'a, T> { 8 | vm: &'a mut RwasmExecutor, 9 | } 10 | 11 | impl<'a, T> Caller<'a, T> { 12 | pub fn new(store: &'a mut RwasmExecutor) -> Self { 13 | Self { vm: store } 14 | } 15 | 16 | pub fn stack_push>(&mut self, value: I) { 17 | self.vm.sp.push_as(value); 18 | } 19 | 20 | pub fn stack_pop(&mut self) -> UntypedValue { 21 | self.vm.sp.pop() 22 | } 23 | 24 | pub fn stack_pop_as>(&mut self) -> I { 25 | I::from(self.vm.sp.pop()) 26 | } 27 | 28 | pub fn stack_pop2(&mut self) -> (UntypedValue, UntypedValue) { 29 | let rhs = self.vm.sp.pop(); 30 | let lhs = self.vm.sp.pop(); 31 | (lhs, rhs) 32 | } 33 | 34 | pub fn stack_pop2_as>(&mut self) -> (I, I) { 35 | let (lhs, rhs) = self.stack_pop2(); 36 | (I::from(lhs), I::from(rhs)) 37 | } 38 | 39 | pub fn stack_pop_n(&mut self) -> [UntypedValue; N] { 40 | let mut result: [UntypedValue; N] = [UntypedValue::default(); N]; 41 | for i in 0..N { 42 | result[N - i - 1] = self.vm.sp.pop(); 43 | } 44 | result 45 | } 46 | 47 | pub fn memory_read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), RwasmError> { 48 | self.vm.global_memory.read(offset, buffer)?; 49 | Ok(()) 50 | } 51 | 52 | pub fn memory_read_fixed(&self, offset: usize) -> Result<[u8; N], RwasmError> { 53 | let mut buffer = [0u8; N]; 54 | self.vm.global_memory.read(offset, &mut buffer)?; 55 | Ok(buffer) 56 | } 57 | 58 | pub fn memory_read_vec(&self, offset: usize, length: usize) -> Result, RwasmError> { 59 | let mut buffer = vec![0u8; length]; 60 | self.vm.global_memory.read(offset, &mut buffer)?; 61 | Ok(buffer) 62 | } 63 | 64 | pub fn memory_write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), RwasmError> { 65 | self.vm.global_memory.write(offset, buffer)?; 66 | if let Some(tracer) = self.vm.tracer.as_mut() { 67 | tracer.memory_change(offset as u32, buffer.len() as u32, buffer); 68 | } 69 | Ok(()) 70 | } 71 | 72 | pub fn vm_mut(&mut self) -> &mut RwasmExecutor { 73 | &mut self.vm 74 | } 75 | 76 | pub fn vm(&self) -> &RwasmExecutor { 77 | &self.vm 78 | } 79 | 80 | pub fn context_mut(&mut self) -> &mut T { 81 | self.vm.context_mut() 82 | } 83 | 84 | pub fn context(&self) -> &T { 85 | self.vm.context() 86 | } 87 | 88 | pub fn dump_stack(&mut self) -> Vec { 89 | self.vm.value_stack.dump_stack(self.vm.sp) 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/vm/data_entity.rs: -------------------------------------------------------------------------------- 1 | use alloc::sync::Arc; 2 | 3 | /// An instantiated [`DataSegmentEntity`]. 4 | /// 5 | /// # Note 6 | /// 7 | /// With the `bulk-memory` Wasm proposal it is possible to interact 8 | /// with data segments at runtime. Therefore Wasm instances now have 9 | /// a need to have an instantiated representation of data segments. 10 | #[derive(Debug)] 11 | pub struct DataSegmentEntity { 12 | /// The underlying bytes of the instance data segment. 13 | /// 14 | /// # Note 15 | /// 16 | /// These bytes are just readable after instantiation. 17 | /// Using Wasm `data.drop` simply replaces the instance 18 | /// with an empty one. 19 | bytes: Option>, 20 | } 21 | 22 | impl DataSegmentEntity { 23 | /// Create an empty [`DataSegmentEntity`] representing dropped data segments. 24 | pub fn empty() -> Self { 25 | Self { bytes: None } 26 | } 27 | 28 | pub fn new(bytes: Arc<[u8]>) -> Self { 29 | Self { bytes: Some(bytes) } 30 | } 31 | 32 | /// Performs an emptiness check. 33 | /// This function returns `true` only if the segment contains no items and not just an empty 34 | /// array. 35 | /// This check is crucial to determine if a segment has been dropped. 36 | pub fn is_empty(&self) -> bool { 37 | self.bytes.is_none() 38 | } 39 | 40 | /// Returns the bytes of the [`DataSegmentEntity`]. 41 | pub fn bytes(&self) -> &[u8] { 42 | self.bytes 43 | .as_ref() 44 | .map(|bytes| &bytes[..]) 45 | .unwrap_or_else(|| &[]) 46 | } 47 | 48 | /// Drops the bytes of the [`DataSegmentEntity`]. 49 | pub fn drop_bytes(&mut self) { 50 | self.bytes = None; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/vm/handler.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | types::{RwasmError, UntypedValue}, 3 | vm::context::Caller, 4 | }; 5 | use alloc::{vec, vec::Vec}; 6 | 7 | pub type SyscallHandler = fn(Caller, u32) -> Result<(), RwasmError>; 8 | 9 | pub fn always_failing_syscall_handler( 10 | _caller: Caller, 11 | func_idx: u32, 12 | ) -> Result<(), RwasmError> { 13 | Err(RwasmError::UnknownExternalFunction(func_idx)) 14 | } 15 | 16 | #[derive(Default)] 17 | pub struct SimpleCallContext { 18 | pub input: Vec, 19 | pub state: u32, 20 | pub output: Vec, 21 | } 22 | 23 | #[derive(Default)] 24 | struct SimpleCallHandler; 25 | 26 | impl SimpleCallHandler { 27 | fn fn_proc_exit(mut caller: Caller) -> Result<(), RwasmError> { 28 | let exit_code = caller.stack_pop(); 29 | Err(RwasmError::ExecutionHalted(i32::from(exit_code))) 30 | } 31 | 32 | fn fn_get_state(mut caller: Caller) -> Result<(), RwasmError> { 33 | caller.stack_push(UntypedValue::from(caller.context().state)); 34 | Ok(()) 35 | } 36 | 37 | fn fn_read_input(mut caller: Caller) -> Result<(), RwasmError> { 38 | let [target, offset, length] = caller.stack_pop_n(); 39 | let input = caller 40 | .context() 41 | .input 42 | .get(offset.as_usize()..(offset.as_usize() + length.as_usize())) 43 | .ok_or(RwasmError::ExecutionHalted(-2020))? 44 | .to_vec(); 45 | caller.memory_write(target.as_usize(), &input)?; 46 | Ok(()) 47 | } 48 | 49 | fn fn_input_size(mut caller: Caller) -> Result<(), RwasmError> { 50 | caller.stack_push(UntypedValue::from(caller.context().input.len() as i32)); 51 | Ok(()) 52 | } 53 | 54 | fn fn_write_output(mut caller: Caller) -> Result<(), RwasmError> { 55 | let [offset, length] = caller.stack_pop_n(); 56 | let mut buffer = vec![0u8; length.as_usize()]; 57 | caller.memory_read(offset.as_usize(), &mut buffer)?; 58 | caller.context_mut().output.extend_from_slice(&buffer); 59 | Ok(()) 60 | } 61 | 62 | fn fn_keccak256(mut caller: Caller) -> Result<(), RwasmError> { 63 | use tiny_keccak::Hasher; 64 | let [data_offset, data_len, output32_offset] = caller.stack_pop_n(); 65 | let mut buffer = vec![0u8; data_len.as_usize()]; 66 | caller.memory_read(data_offset.as_usize(), &mut buffer)?; 67 | let mut hash = tiny_keccak::Keccak::v256(); 68 | hash.update(&buffer); 69 | let mut output = [0u8; 32]; 70 | hash.finalize(&mut output); 71 | caller.memory_write(output32_offset.as_usize(), &output)?; 72 | Ok(()) 73 | } 74 | } 75 | 76 | pub(crate) fn simple_call_handler_syscall_handler( 77 | caller: Caller, 78 | func_idx: u32, 79 | ) -> Result<(), RwasmError> { 80 | match func_idx { 81 | 0x0001 => SimpleCallHandler::fn_proc_exit(caller), 82 | 0x0002 => SimpleCallHandler::fn_get_state(caller), 83 | 0x0003 => SimpleCallHandler::fn_read_input(caller), 84 | 0x0004 => SimpleCallHandler::fn_input_size(caller), 85 | 0x0005 => SimpleCallHandler::fn_write_output(caller), 86 | 0x0101 => SimpleCallHandler::fn_keccak256(caller), 87 | _ => unreachable!("rwasm: unknown function ({})", func_idx), 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/vm/instr_ptr.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{Opcode, OpcodeData}; 2 | 3 | /// The instruction pointer to the instruction of a function on the call stack. 4 | #[derive(Debug, Copy, Clone, PartialEq)] 5 | pub struct InstructionPtr { 6 | /// The pointer to the instruction. 7 | pub(crate) ptr: *const (Opcode, OpcodeData), 8 | pub(crate) src: *const (Opcode, OpcodeData), 9 | } 10 | 11 | /// It is safe to send an [`rwasm::engine::code_map::InstructionPtr`] to another thread. 12 | /// 13 | /// The access to the pointed-to [`Opcode`] is read-only and 14 | /// [`Opcode`] itself is [`Send`]. 15 | /// 16 | /// However, it is not safe to share an [`rwasm::engine::code_map::InstructionPtr`] between threads 17 | /// due to their [`rwasm::engine::code_map::InstructionPtr::offset`] method which relinks the 18 | /// internal pointer and is not synchronized. 19 | unsafe impl Send for InstructionPtr {} 20 | 21 | impl InstructionPtr { 22 | /// Creates a new [`rwasm::engine::code_map::InstructionPtr`] for `instr`. 23 | #[inline] 24 | pub fn new(ptr: *const (Opcode, OpcodeData)) -> Self { 25 | Self { ptr, src: ptr } 26 | } 27 | 28 | #[inline(always)] 29 | pub fn pc(&self) -> u32 { 30 | let size = size_of::<(Opcode, OpcodeData)>() as u32; 31 | let diff = self.ptr as u32 - self.src as u32; 32 | diff / size 33 | } 34 | 35 | /// Offset the [`rwasm::engine::code_map::InstructionPtr`] by the given value. 36 | /// 37 | /// # Safety 38 | /// 39 | /// The caller is responsible for calling this method only with valid 40 | /// offset values so that the [`rwasm::engine::code_map::InstructionPtr`] never points out of 41 | /// valid bounds of the instructions of the same compiled Wasm function. 42 | #[inline(always)] 43 | pub fn offset(&mut self, by: isize) { 44 | // SAFETY: Within Wasm bytecode execution we are guaranteed by 45 | // Wasm validation and `wasmi` codegen to never run out 46 | // of valid bounds using this method. 47 | self.ptr = unsafe { self.ptr.offset(by) }; 48 | } 49 | 50 | #[inline(always)] 51 | pub fn add(&mut self, delta: usize) { 52 | // SAFETY: Within Wasm bytecode execution we are guaranteed by 53 | // Wasm validation and `wasmi` codegen to never run out 54 | // of valid bounds using this method. 55 | self.ptr = unsafe { self.ptr.add(delta) }; 56 | } 57 | 58 | /// Returns a shared reference to the currently pointed at [`Opcode`]. 59 | /// 60 | /// # Safety 61 | /// 62 | /// The caller is responsible for calling this method only when it is 63 | /// guaranteed that the [`rwasm::engine::code_map::InstructionPtr`] is validly pointing inside 64 | /// the boundaries of its associated compiled Wasm function. 65 | #[inline(always)] 66 | pub fn get(&self) -> Opcode { 67 | // SAFETY: Within Wasm bytecode execution we are guaranteed by 68 | // Wasm validation and `wasmi` codegen to never run out 69 | // of valid bounds using this method. 70 | unsafe { &*self.ptr }.0 71 | } 72 | 73 | #[inline(always)] 74 | pub fn data(&self) -> &OpcodeData { 75 | // SAFETY: Within Wasm bytecode execution we are guaranteed by 76 | // Wasm validation and `wasmi` codegen to never run out 77 | // of valid bounds using this method. 78 | &unsafe { &*self.ptr }.1 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/vm/memory.rs: -------------------------------------------------------------------------------- 1 | use crate::types::{Pages, RwasmError, N_MAX_MEMORY_PAGES}; 2 | use bytes::BytesMut; 3 | 4 | pub struct GlobalMemory { 5 | pub shared_memory: BytesMut, 6 | pub current_pages: Pages, 7 | } 8 | 9 | const MEMORY_MAX_PAGES: Pages = Pages::new_unchecked(N_MAX_MEMORY_PAGES * 2); 10 | 11 | impl GlobalMemory { 12 | pub fn new(initial_pages: Pages) -> Self { 13 | let initial_len = initial_pages 14 | .to_bytes() 15 | .expect("rwasm: not supported target pointer width"); 16 | let maximum_len = MEMORY_MAX_PAGES 17 | .to_bytes() 18 | .expect("rwasm: not supported target pointer width"); 19 | if initial_len > maximum_len { 20 | unreachable!("rwasm: initial memory size is greater than the maximum"); 21 | } 22 | let mut shared_memory = BytesMut::with_capacity(maximum_len); 23 | shared_memory.resize(initial_len, 0); 24 | Self { 25 | shared_memory, 26 | current_pages: initial_pages, 27 | } 28 | } 29 | 30 | /// Returns the number of pages in use by the linear memory. 31 | pub fn current_pages(&self) -> Pages { 32 | self.current_pages 33 | } 34 | 35 | /// Grows the linear memory by the given number of new pages. 36 | /// 37 | /// Returns the number of pages before the operation upon success. 38 | /// 39 | /// # Errors 40 | /// 41 | /// If the linear memory grows beyond its maximum limit after 42 | /// the growth operation. 43 | pub fn grow(&mut self, additional: Pages) -> Result { 44 | let current_pages = self.current_pages(); 45 | if additional == Pages::from(0) { 46 | return Ok(current_pages); 47 | } 48 | let desired_pages = current_pages 49 | .checked_add(additional) 50 | .ok_or(RwasmError::GrowthOperationLimited)?; 51 | if desired_pages > MEMORY_MAX_PAGES { 52 | return Err(RwasmError::GrowthOperationLimited); 53 | } 54 | // At this point, it is okay to grow the underlying virtual memory 55 | // by the given number of additional pages. 56 | let new_size = desired_pages 57 | .to_bytes() 58 | .expect("rwasm: not supported target pointer width"); 59 | assert!(new_size >= self.shared_memory.len()); 60 | self.shared_memory.resize(new_size, 0); 61 | self.current_pages = desired_pages; 62 | Ok(current_pages) 63 | } 64 | 65 | /// Returns a shared slice to the bytes underlying to the byte buffer. 66 | pub fn data(&self) -> &[u8] { 67 | self.shared_memory.as_ref() 68 | } 69 | 70 | /// Returns an exclusive slice to the bytes underlying to the byte buffer. 71 | pub fn data_mut(&mut self) -> &mut [u8] { 72 | self.shared_memory.as_mut() 73 | } 74 | 75 | /// Reads `n` bytes from `memory[offset..offset+n]` into `buffer` 76 | /// where `n` is the length of `buffer`. 77 | /// 78 | /// # Errors 79 | /// 80 | /// If this operation accesses out of bounds linear memory. 81 | pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), RwasmError> { 82 | let len_buffer = buffer.len(); 83 | let slice = self 84 | .data() 85 | .get(offset..(offset + len_buffer)) 86 | .ok_or(RwasmError::MemoryOutOfBounds)?; 87 | buffer.copy_from_slice(slice); 88 | Ok(()) 89 | } 90 | 91 | /// Writes `n` bytes to `memory[offset..offset+n]` from `buffer` 92 | /// where `n` if the length of `buffer`. 93 | /// 94 | /// # Errors 95 | /// 96 | /// If this operation accesses out of bounds linear memory. 97 | pub fn write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), RwasmError> { 98 | let len_buffer = buffer.len(); 99 | let slice = self 100 | .data_mut() 101 | .get_mut(offset..(offset + len_buffer)) 102 | .ok_or(RwasmError::MemoryOutOfBounds)?; 103 | slice.copy_from_slice(buffer); 104 | Ok(()) 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/vm/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod context; 3 | mod data_entity; 4 | mod executor; 5 | mod handler; 6 | mod instr_ptr; 7 | mod instruction_table; 8 | mod memory; 9 | mod opcodes; 10 | mod table_entity; 11 | mod tracer; 12 | mod value_stack; 13 | 14 | pub use config::*; 15 | pub use context::*; 16 | pub use data_entity::*; 17 | pub use executor::*; 18 | pub use handler::*; 19 | pub use instr_ptr::*; 20 | pub use instruction_table::*; 21 | pub use memory::*; 22 | pub use table_entity::*; 23 | pub use tracer::*; 24 | pub use value_stack::*; 25 | --------------------------------------------------------------------------------