├── .editorconfig ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── coverage.yml ├── .gitignore ├── .gitmodules ├── .rustfmt.toml ├── Cargo.lock ├── Cargo.toml ├── Makefile ├── README.md ├── benchmarks ├── .gitignore ├── Cargo.toml ├── Makefile ├── README.md ├── bench.rs ├── lib.rs └── rust-toolchain ├── codecov.yml ├── 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 ├── .gitignore ├── Cargo.toml ├── Makefile ├── extractor.rs ├── fuzz.rs ├── i64_add.rs ├── i64_div_s.rs ├── i64_div_u.rs ├── i64_mul.rs ├── i64_ne.rs ├── i64_rem_s.rs ├── i64_rem_u.rs ├── i64_rotl.rs ├── i64_rotr.rs ├── i64_shl.rs ├── i64_shr_s.rs ├── i64_shr_u.rs ├── i64_sub.rs └── lib.rs ├── src ├── compiler │ ├── config.rs │ ├── control_flow.rs │ ├── drop_keep.rs │ ├── error.rs │ ├── fuel_costs.rs │ ├── func_builder.rs │ ├── labels.rs │ ├── locals_registry.rs │ ├── mod.rs │ ├── parser.rs │ ├── segment_builder.rs │ ├── translator.rs │ ├── utils.rs │ └── value_stack.rs ├── lib.rs ├── strategy.rs ├── types │ ├── branch_offset.rs │ ├── compiled_expr.rs │ ├── constructor_params.rs │ ├── error.rs │ ├── func_ref.rs │ ├── global_variable.rs │ ├── host_error.rs │ ├── import_linker.rs │ ├── import_name.rs │ ├── instruction_set.rs │ ├── instruction_set │ │ ├── add_sub.rs │ │ ├── bitwise.rs │ │ ├── compare.rs │ │ ├── conv.rs │ │ ├── div_s.rs │ │ ├── div_u.rs │ │ ├── memory.rs │ │ ├── mul.rs │ │ ├── rem_s.rs │ │ ├── rem_u.rs │ │ └── table.rs │ ├── mod.rs │ ├── module.rs │ ├── nan_preserving_float.rs │ ├── opcode.rs │ ├── trap_code.rs │ ├── units.rs │ ├── untyped_value.rs │ └── value.rs ├── vm │ ├── call_stack.rs │ ├── config.rs │ ├── context.rs │ ├── engine.rs │ ├── executor.rs │ ├── executor │ │ ├── alu.rs │ │ ├── control_flow.rs │ │ ├── fpu.rs │ │ ├── memory.rs │ │ ├── stack.rs │ │ ├── system.rs │ │ └── table.rs │ ├── handler.rs │ ├── instr_ptr.rs │ ├── memory.rs │ ├── mod.rs │ ├── store.rs │ ├── table_entity.rs │ ├── tracer │ │ ├── event.rs │ │ ├── mem.rs │ │ ├── mem_index.rs │ │ ├── mod.rs │ │ ├── rw_op.rs │ │ └── state.rs │ └── value_stack.rs └── wasmtime │ └── mod.rs ├── tests ├── interruption.rs ├── lib.rs ├── nitro-verifier │ ├── Cargo.toml │ ├── Makefile │ ├── attestation.bin │ ├── build.rs │ ├── lib.rs │ └── nitro.pem ├── nitro.rs ├── snippets.rs └── wasmtime_fpu.rs └── wasm ├── .gitignore ├── Cargo.toml ├── Makefile └── lib.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: 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-amd64-8core 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 | rustup target add wasm32-unknown-unknown 42 | make test-specific-cases 43 | 44 | lint: 45 | name: Lint 46 | runs-on: ubuntu-amd64-8core 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 -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: Coverage 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 | coverage: 22 | name: Coverage 23 | runs-on: ubuntu-latest 24 | steps: 25 | - uses: actions/checkout@v4 26 | with: 27 | submodules: "recursive" 28 | - name: Install Rust toolchain 29 | uses: dtolnay/rust-toolchain@master 30 | with: 31 | toolchain: stable 32 | targets: wasm32-unknown-unknown 33 | - name: Setup cache 34 | uses: Swatinem/rust-cache@v2 35 | - name: Install wasm32 target 36 | run: rustup target add wasm32-unknown-unknown 37 | - name: Run cargo check 38 | run: cargo check 39 | - name: Generate code coverage 40 | run: | 41 | cargo install cargo-llvm-cov grcov 42 | make coverage 43 | - name: Upload to codecov.io 44 | uses: codecov/codecov-action@v5.4.3 45 | with: 46 | files: ./lcov.info 47 | token: ${{ secrets.CODECOV_TOKEN }} 48 | fail_ci_if_error: true 49 | - name: Archive code coverage results 50 | uses: actions/upload-artifact@v4 51 | with: 52 | name: code-coverage-report 53 | path: cobertura.xml -------------------------------------------------------------------------------- /.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 19 | 20 | # coverage 21 | lcov*.info 22 | 23 | # exclude all wasm & wat files 24 | lib.wasm 25 | lib.wat -------------------------------------------------------------------------------- /.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 | bytes = { version = "1.10.1", default-features = false } 19 | downcast-rs = { version = "2.0.1", default-features = false, features = ["sync"] } 20 | bincode = { version = "2.0.1", default-features = false, features = ["alloc", "derive"] } 21 | num-traits = { version = "0.2", default-features = false } 22 | bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } 23 | libm = "0.2.1" 24 | wasmtime = { git = "https://github.com/fluentlabs-xyz/wasmtime", branch = "devel", optional = true, features = ["disable-fpu"] } 25 | #wasmtime = { path = "../wasmtime/crates/wasmtime", optional = true, features = ["disable-fpu"] } 26 | serde = { version = "1.0.219", features = ["derive"], optional = true } 27 | smallvec = "1.15.0" 28 | anyhow = { version = "1.0.98", default-features = false, optional = true } 29 | directories = { version = "6.0.0", optional = true } 30 | 31 | [dev-dependencies] 32 | rand = "0.9.1" 33 | wat = "1.230.0" 34 | 35 | [features] 36 | default = ["std", "wasmtime"] 37 | std = [ 38 | "bytes/std", 39 | "bincode/std", 40 | "num-traits/std", 41 | "bitvec/std", 42 | "wasmtime?/std", 43 | ] 44 | more-max-pages = [] 45 | tracing = [ 46 | "dep:serde", "serde/derive" 47 | ] 48 | debug-print = [] 49 | fpu = [] 50 | wasmtime = ["dep:wasmtime", "dep:anyhow", "dep:directories"] 51 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: test-specific-cases 2 | test-specific-cases: 3 | # build all binaries 4 | cd benchmarks && make 5 | cd wasm && make 6 | cd tests/nitro-verifier && make 7 | cd snippets && make 8 | # run tests 9 | cargo test --color=always --no-fail-fast --manifest-path Cargo.toml 10 | cargo test --color=always --no-fail-fast --manifest-path e2e/Cargo.toml 11 | cargo test --color=always --no-fail-fast --manifest-path snippets/Cargo.toml 12 | # run nitro test (with release flag) 13 | cargo test --release --package rwasm --test nitro test_nitro_verifier -- --ignored 14 | 15 | .PHONY: coverage 16 | coverage: 17 | # build all binaries 18 | cd benchmarks && make 19 | cd wasm && make 20 | cd tests/nitro-verifier && make 21 | cd snippets && make 22 | # run tests 23 | cargo llvm-cov --lcov --manifest-path=./snippets/Cargo.toml > lcov1.info 24 | cargo llvm-cov --lcov --manifest-path=./Cargo.toml > lcov2.info 25 | cargo llvm-cov --lcov --manifest-path=./e2e/Cargo.toml > lcov3.info 26 | # merge all lcov files together 27 | grcov --llvm ./lcov1.info ./lcov2.info ./lcov3.info > lcov.info 28 | 29 | .PHONY: clean 30 | clean: 31 | # Delete all target folders 32 | find . -type d -name "target" -exec rm -rf {} + 33 | # Delete all Cargo.lock files except the root 34 | find . -name Cargo.lock ! -path './Cargo.lock' -type f -exec rm -f {} + 35 | 36 | all: test-specific-cases -------------------------------------------------------------------------------- /benchmarks/.gitignore: -------------------------------------------------------------------------------- 1 | target 2 | Cargo.lock 3 | lib.wat 4 | lib.wasm -------------------------------------------------------------------------------- /benchmarks/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "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 = "..", features = ["std", "wasmtime"] } 24 | wasmi = { version = "=0.31.2" } -------------------------------------------------------------------------------- /benchmarks/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | RUSTFLAGS="-C link-arg=-zstack-size=0" cargo b --target=wasm32-unknown-unknown --release --no-default-features 4 | cp ./target/wasm32-unknown-unknown/release/fib.wasm ./lib.wasm 5 | wasm2wat ./lib.wasm > ./lib.wat || true -------------------------------------------------------------------------------- /benchmarks/README.md: -------------------------------------------------------------------------------- 1 | In these benchmarks, we emulate the Ethereum execution environment, where a smart contract must be loaded from its 2 | binary representation before execution. 3 | 4 | The binary can be loaded either from a cache or directly from the state trie. 5 | If we load the binary from the state, we need to parse, verify, and execute it (optionally caching it afterward). 6 | For cached binaries, only execution is required. 7 | 8 | **Current results:** 9 | 10 | | Test | Native | rWasm | rWasm (no-cache) | Wasmi | Wasmi (no-cache) | 11 | |---------|--------|-------|------------------|-------|------------------| 12 | | fib(47) | 12ns | 484ns | 879ns | 341ns | 5379ns | 13 | 14 | Tested on Apple M3 MAX. 15 | 16 | **PS:** We also benchmarked SP1's RISC-V implementation but removed it from the table since it was too slow (\~7 ms for 17 | fib). 18 | 19 | --- 20 | 21 | rWasm delivers performance very close to Wasmi (the minor difference is due to the entrypoint logic that rWasm 22 | requires). 23 | This happens because rWasm has a dedicated function to initialize all necessary data, and this data is reset before each 24 | benchmark iteration. 25 | Wasmi caches initialization as well, which is why it sometimes appears faster. 26 | 27 | In practice, the most common scenario is execution without cache ("no-cache"). 28 | In this case, rWasm shows significant performance improvements over Wasmi (up to 10x faster). 29 | There are techniques available to further reduce binary decoding costs, but they haven’t been applied yet. 30 | With optimized binary decoding, execution time could be as low as \~400–500 ns. -------------------------------------------------------------------------------- /benchmarks/lib.rs: -------------------------------------------------------------------------------- 1 | #![feature(test)] 2 | 3 | #[cfg(test)] 4 | mod bench; 5 | 6 | #[cfg(target_arch = "wasm32")] 7 | #[no_mangle] 8 | pub fn main(n: i32) -> i32 { 9 | let (mut a, mut b) = (0, 1); 10 | for _ in 0..n { 11 | let temp = a; 12 | a = b; 13 | b = temp + b; 14 | } 15 | a 16 | } 17 | -------------------------------------------------------------------------------- /benchmarks/rust-toolchain: -------------------------------------------------------------------------------- 1 | nightly-2025-01-27 -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | ignore: 2 | - "benchmarks" 3 | - "e2e" 4 | - "legacy" # legacy will be removed 5 | - "snippets" 6 | - "wasm" 7 | -------------------------------------------------------------------------------- /docs/SUMMARY.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluentlabs-xyz/rwasm/74f26a351667b91d7299dc50cefb9ab1b241957a/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/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 = "..", features = ["fpu"] } 8 | anyhow = "1.0.71" 9 | wast = "=64.0" 10 | 11 | [features] 12 | default = [] 13 | debug-print = ["rwasm/debug-print"] -------------------------------------------------------------------------------- /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 std::{error::Error, fmt, fmt::Display}; 3 | 4 | /// Errors that may occur upon Wasm spec test suite execution. 5 | #[derive(Debug)] 6 | pub enum TestError { 7 | Rwasm(RwasmError), 8 | InstanceNotRegistered { name: String }, 9 | NoModuleInstancesFound, 10 | } 11 | 12 | impl Error for TestError {} 13 | 14 | impl Display for TestError { 15 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 16 | match self { 17 | Self::InstanceNotRegistered { name } => { 18 | write!(f, "missing module instance with name: {name}") 19 | } 20 | Self::NoModuleInstancesFound => { 21 | write!(f, "found no module instances registered so far") 22 | } 23 | Self::Rwasm(rwasm_error) => Display::fmt(rwasm_error, f), 24 | } 25 | } 26 | } 27 | 28 | impl From for TestError 29 | where 30 | E: Into, 31 | { 32 | fn from(error: E) -> Self { 33 | Self::Rwasm(error.into()) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /e2e/src/handler.rs: -------------------------------------------------------------------------------- 1 | use rwasm::{Caller, Store, TrapCode, TypedCaller, Value}; 2 | 3 | pub const FUNC_ENTRYPOINT: u32 = u32::MAX; 4 | pub const FUNC_PRINT: u32 = 100; 5 | pub const FUNC_PRINT_I32: u32 = 101; 6 | pub const FUNC_PRINT_I64: u32 = 102; 7 | pub const FUNC_PRINT_F32: u32 = 103; 8 | pub const FUNC_PRINT_F64: u32 = 104; 9 | pub const FUNC_PRINT_I32_F32: u32 = 105; 10 | pub const FUNC_PRINT_I64_F64: u32 = 106; 11 | 12 | #[derive(Default)] 13 | pub struct TestingContext { 14 | pub program_counter: u32, 15 | pub state: u32, 16 | } 17 | 18 | pub(crate) fn testing_context_syscall_handler( 19 | caller: &mut TypedCaller, 20 | func_idx: u32, 21 | params: &[Value], 22 | _result: &mut [Value], 23 | ) -> Result<(), TrapCode> { 24 | match func_idx { 25 | FUNC_PRINT => { 26 | println!("print"); 27 | Ok(()) 28 | } 29 | FUNC_PRINT_I32 => { 30 | let value = params[0].i32().unwrap(); 31 | println!("print: {value}"); 32 | Ok(()) 33 | } 34 | FUNC_PRINT_I64 => { 35 | let value = params[0].i64().unwrap(); 36 | println!("print: {value}"); 37 | Ok(()) 38 | } 39 | FUNC_PRINT_F32 => { 40 | let value = params[0].f32().unwrap(); 41 | println!("print: {value}"); 42 | Ok(()) 43 | } 44 | FUNC_PRINT_F64 => { 45 | let value = params[0].f64().unwrap(); 46 | println!("print: {value}"); 47 | Ok(()) 48 | } 49 | FUNC_PRINT_I32_F32 => { 50 | let v0 = params[0].i32().unwrap(); 51 | let v1 = params[1].f32().unwrap(); 52 | println!("print: {:?} {:?}", i32::from(v0), f32::from(v1)); 53 | Ok(()) 54 | } 55 | FUNC_PRINT_I64_F64 => { 56 | let v0 = params[0].i64().unwrap(); 57 | let v1 = params[1].f64().unwrap(); 58 | println!("print: {:?} {:?}", i64::from(v0), f64::from(v1)); 59 | Ok(()) 60 | } 61 | FUNC_ENTRYPOINT => { 62 | // yeah, dirty, but this is how we remember the program counter to reset, 63 | // since we're 100% sure the function is called using `Call` 64 | // that we can safely deduct 1 from PC (for `ReturnCall` we need to deduct 2) 65 | let pc = caller.program_counter(); 66 | caller.context_mut(|ctx| ctx.program_counter = pc - 1); 67 | // push state value into the stack 68 | let state = caller.context(|ctx| ctx.state); 69 | caller.stack_push(state.into()); 70 | Ok(()) 71 | } 72 | _ => todo!("not implemented syscall handler"), 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /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/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/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/.gitignore: -------------------------------------------------------------------------------- 1 | lib.wasm 2 | lib.wat -------------------------------------------------------------------------------- /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 = "..", default-features = false, features = [] } 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 || true -------------------------------------------------------------------------------- /snippets/extractor.rs: -------------------------------------------------------------------------------- 1 | use rwasm::{CompilationConfig, RwasmModule}; 2 | use walrus::ir::InstrSeq; 3 | 4 | fn extract_wasm_snippet(wasm_binary: &[u8]) { 5 | let module = walrus::Module::from_buffer(wasm_binary).unwrap(); 6 | for func in module.funcs.iter() { 7 | let Some(func_name) = func.name.clone() else { 8 | continue; 9 | }; 10 | let kind = func.kind.unwrap_local(); 11 | assert_eq!(kind.args.len(), 4); 12 | let block = kind.block(kind.entry_block()); 13 | println!("func: {}", func_name); 14 | test_ending_opcodes(&block); 15 | println!() 16 | } 17 | } 18 | 19 | #[rustfmt::skip] 20 | fn test_ending_opcodes(seq: &InstrSeq) { 21 | let fake_block = seq 22 | .iter() 23 | .rev() 24 | .take(6) 25 | .map(|v| v.0.clone()) 26 | .rev() 27 | .collect::>(); 28 | for instr in &fake_block { 29 | println!(" - {:?}", instr); 30 | } 31 | assert_eq!(fake_block.len(), 6); 32 | // make sure the fake block we injected matches a>>32|b 33 | // assert!(matches!(fake_block[0],Instr::Unop(Unop {op: UnaryOp::I64ExtendUI32}))); 34 | // assert!(matches!(fake_block[1], Instr::Const(Const{value: Value::I64(32)}))); 35 | // assert!(matches!(fake_block[2],Instr::Binop(Binop {op: BinaryOp::I64Shl}))); 36 | // assert!(matches!(fake_block[3],Instr::LocalGet(..))); 37 | // assert!(matches!(fake_block[4],Instr::Unop(Unop {op: UnaryOp::I64ExtendUI32}))); 38 | // assert!(matches!(fake_block[5],Instr::Binop(Binop {op: BinaryOp::I64Or}))); 39 | } 40 | 41 | #[test] 42 | fn test_extract_rwasm_bytecode() { 43 | let wasm_binary = include_bytes!("./lib.wasm"); 44 | let (module, _) = RwasmModule::compile( 45 | CompilationConfig::default() 46 | .with_entrypoint_name("i64_rotr".into()) 47 | .with_consume_fuel(false), 48 | wasm_binary, 49 | ) 50 | .unwrap(); 51 | println!("{}", module); 52 | } 53 | -------------------------------------------------------------------------------- /snippets/i64_add.rs: -------------------------------------------------------------------------------- 1 | #[inline(always)] 2 | pub(crate) fn i64_add_impl(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> (u32, u32) { 3 | // low part 4 | let sum_lo = a_lo.wrapping_add(b_lo); 5 | // compute carry without branches 6 | let carry = (sum_lo < a_lo) as u32; 7 | // high part + carry 8 | let sum_hi = a_hi.wrapping_add(b_hi).wrapping_add(carry); 9 | // push result 10 | (sum_lo, sum_hi) 11 | } 12 | 13 | #[no_mangle] 14 | pub fn i64_add(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { 15 | let (res_lo, res_hi) = i64_add_impl(a_lo, a_hi, b_lo, b_hi); 16 | (res_hi as u64) << 32 | res_lo as u64 17 | } 18 | -------------------------------------------------------------------------------- /snippets/i64_ne.rs: -------------------------------------------------------------------------------- 1 | #[no_mangle] 2 | pub fn i64_ne(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u32 { 3 | ((a_lo != b_lo) || (a_hi != b_hi)) as u32 4 | } 5 | -------------------------------------------------------------------------------- /snippets/i64_rotl.rs: -------------------------------------------------------------------------------- 1 | #[inline(always)] 2 | pub(crate) fn i64_rotl_impl(a_lo: u32, a_hi: u32, b_lo: u32, _b_hi: u32) -> (u32, u32) { 3 | let k = b_lo & 0x3F; 4 | if k == 0 { 5 | (a_lo, a_hi) 6 | } else if k < 32 { 7 | let lo = (a_lo << k) | (a_hi >> (32 - k)); 8 | let hi = (a_hi << k) | (a_lo >> (32 - k)); 9 | (lo, hi) 10 | } else if k == 32 { 11 | (a_hi, a_lo) 12 | } else { 13 | // k in 33..=63 14 | let m = k - 32; 15 | let lo = (a_hi << m) | (a_lo >> (32 - m)); 16 | let hi = (a_lo << m) | (a_hi >> (32 - m)); 17 | (lo, hi) 18 | } 19 | } 20 | 21 | #[no_mangle] 22 | pub fn i64_rotl(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { 23 | let (res_lo, res_hi) = i64_rotl_impl(a_lo, a_hi, b_lo, b_hi); 24 | (res_hi as u64) << 32 | res_lo as u64 25 | } 26 | 27 | #[test] 28 | fn test_rotl() { 29 | fn rotl_ref(a: u64, k: u32) -> u64 { 30 | let k = k & 63; 31 | if k == 0 { 32 | a 33 | } else { 34 | (a << k) | (a >> (64 - k)) 35 | } 36 | } 37 | let a: u64 = 0x123456789ABCDEF0; 38 | for k in 0..=64 { 39 | let b_lo = k; 40 | let (a_lo, a_hi) = (a as u32, (a >> 32) as u32); 41 | let (r_lo, r_hi) = i64_rotl_impl(a_lo, a_hi, b_lo, 0); 42 | let expect = rotl_ref(a, k); 43 | assert_eq!((r_lo as u64) | ((r_hi as u64) << 32), expect); 44 | } 45 | } 46 | 47 | #[test] 48 | fn test_i64_rotl_highest_bit() { 49 | let a: u64 = 0x8000_0000_0000_0000; 50 | let b: u32 = 1; 51 | let a_lo = a as u32; 52 | let a_hi = (a >> 32) as u32; 53 | let (res_lo, res_hi) = i64_rotl_impl(a_lo, a_hi, b, 0); 54 | 55 | let result = (res_hi as u64) << 32 | (res_lo as u64); 56 | assert_eq!(result, 0x0000_0000_0000_0001); 57 | } 58 | -------------------------------------------------------------------------------- /snippets/i64_rotr.rs: -------------------------------------------------------------------------------- 1 | #[inline(always)] 2 | pub(crate) fn i64_rotr_impl(a_lo: u32, a_hi: u32, b_lo: u32, _b_hi: u32) -> (u32, u32) { 3 | let k = b_lo & 0x3F; 4 | match k { 5 | 0 => (a_lo, a_hi), 6 | 32 => (a_hi, a_lo), 7 | n @ 1..=31 => { 8 | let lo = (a_lo >> n) | (a_hi << (32 - n)); 9 | let hi = (a_hi >> n) | (a_lo << (32 - n)); 10 | (lo, hi) 11 | } 12 | n @ 33..=63 => { 13 | let m = n - 32; 14 | let lo = (a_hi >> m) | (a_lo << (32 - m)); 15 | let hi = (a_lo >> m) | (a_hi << (32 - m)); 16 | (lo, hi) 17 | } 18 | _ => unsafe { 19 | core::intrinsics::unreachable(); 20 | }, 21 | } 22 | } 23 | 24 | #[no_mangle] 25 | pub fn i64_rotr(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { 26 | let (res_lo, res_hi) = i64_rotr_impl(a_lo, a_hi, b_lo, b_hi); 27 | (res_hi as u64) << 32 | res_lo as u64 28 | } 29 | -------------------------------------------------------------------------------- /snippets/i64_shl.rs: -------------------------------------------------------------------------------- 1 | #[inline(always)] 2 | pub(crate) fn i64_shl_impl(a_lo: u32, a_hi: u32, b_lo: u32, _b_hi: u32) -> (u32, u32) { 3 | // WASM uses only the low 6 bits of the shift count 4 | let shamt = b_lo & 0x3F; 5 | 6 | match shamt { 7 | 0 => (a_lo, a_hi), 8 | n @ 1..=31 => { 9 | // Cross bits from lo to hi 10 | let res_lo = a_lo << n; 11 | let res_hi = (a_hi << n) | (a_lo >> (32 - n)); 12 | (res_lo, res_hi) 13 | } 14 | 32 => (0, a_lo), 15 | n @ 33..=63 => { 16 | // Only low 32 bits (a_lo) matter, shifted up to hi 17 | let res_hi = a_lo << (n - 32); 18 | (0, res_hi) 19 | } 20 | _ => (0, 0), // For completeness (shouldn't hit, due to masking) 21 | } 22 | } 23 | 24 | #[no_mangle] 25 | pub fn i64_shl(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { 26 | let (res_lo, res_hi) = i64_shl_impl(a_lo, a_hi, b_lo, b_hi); 27 | (res_hi as u64) << 32 | res_lo as u64 28 | } 29 | -------------------------------------------------------------------------------- /snippets/i64_shr_s.rs: -------------------------------------------------------------------------------- 1 | #[inline(always)] 2 | pub(crate) fn i64_shr_s_impl(a_lo: u32, a_hi: u32, b_lo: u32, _b_hi: u32) -> (u32, u32) { 3 | // WASM uses only the low 6 bits of the shift count 4 | let shamt = (b_lo & 0x3F) as u32; 5 | 6 | match shamt { 7 | 0 => (a_lo, a_hi), 8 | n @ 1..=31 => { 9 | // Arithmetic right shift for hi: sign is preserved 10 | let hi = a_hi as i32; 11 | let res_hi = (hi >> n) as u32; 12 | let res_lo = (a_lo >> n) | (a_hi << (32 - n)); 13 | (res_lo, res_hi) 14 | } 15 | 32 => { 16 | let hi = a_hi as i32; 17 | let res_lo = hi as u32; 18 | let res_hi = if hi < 0 { u32::MAX } else { 0 }; 19 | (res_lo, res_hi) 20 | } 21 | n @ 33..=63 => { 22 | let hi = a_hi as i32; 23 | let res_hi = if hi < 0 { u32::MAX } else { 0 }; 24 | let res_lo = (hi >> (n - 32)) as u32; 25 | (res_lo, res_hi) 26 | } 27 | _ => (0, 0), // unreachable due to masking, but keeps exhaustiveness 28 | } 29 | } 30 | 31 | #[no_mangle] 32 | pub fn i64_shr_s(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { 33 | let (res_lo, res_hi) = i64_shr_s_impl(a_lo, a_hi, b_lo, b_hi); 34 | (res_hi as u64) << 32 | res_lo as u64 35 | } 36 | -------------------------------------------------------------------------------- /snippets/i64_shr_u.rs: -------------------------------------------------------------------------------- 1 | #[inline(always)] 2 | pub(crate) fn i64_shr_u_impl(a_lo: u32, a_hi: u32, b_lo: u32, _b_hi: u32) -> (u32, u32) { 3 | // Only the lower 6 bits of the shift amount are used in wasm 4 | let shamt = b_lo & 0x3F; 5 | 6 | let (res_lo, res_hi) = match shamt { 7 | 0 => (a_lo, a_hi), // No shift 8 | n @ 1..=31 => { 9 | // Shift both halves right, with cross-over 10 | let new_lo = (a_lo >> n) | (a_hi << (32 - n)); 11 | let new_hi = a_hi >> n; 12 | (new_lo, new_hi) 13 | } 14 | 32 => (a_hi, 0), 15 | n @ 33..=63 => { 16 | let new_lo = a_hi >> (n - 32); 17 | (new_lo, 0) 18 | } 19 | _ => (0, 0), // For completeness; shamt only goes to 63 20 | }; 21 | 22 | (res_lo, res_hi) 23 | } 24 | 25 | #[no_mangle] 26 | pub fn i64_shr_u(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { 27 | let (res_lo, res_hi) = i64_shr_u_impl(a_lo, a_hi, b_lo, b_hi); 28 | (res_hi as u64) << 32 | res_lo as u64 29 | } 30 | -------------------------------------------------------------------------------- /snippets/i64_sub.rs: -------------------------------------------------------------------------------- 1 | /// ------------------------------------------------------------------------- 2 | /// 64-bit subtraction in pure 32-bit arithmetic 3 | /// (two’s-complement works for both signed and unsigned values) 4 | /// ------------------------------------------------------------------------- 5 | 6 | #[inline(always)] 7 | pub(crate) fn i64_sub_impl( 8 | a_lo: u32, 9 | a_hi: u32, 10 | b_lo: u32, 11 | b_hi: u32, 12 | ) -> (u32 /* res_lo */, u32 /* res_hi */) { 13 | // low 32-bit difference 14 | let diff_lo = a_lo.wrapping_sub(b_lo); 15 | 16 | // detect borrow without branches 17 | let borrow = (a_lo < b_lo) as u32; 18 | 19 | // high 32-bit difference minus borrow 20 | let diff_hi = a_hi.wrapping_sub(b_hi).wrapping_sub(borrow); 21 | 22 | (diff_lo, diff_hi) 23 | } 24 | 25 | #[no_mangle] 26 | pub fn i64_sub(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { 27 | let (res_lo, res_hi) = i64_sub_impl(a_lo, a_hi, b_lo, b_hi); 28 | // pack the two limbs back into a single i64 29 | ((res_hi as u64) << 32) | res_lo as u64 30 | } 31 | -------------------------------------------------------------------------------- /snippets/lib.rs: -------------------------------------------------------------------------------- 1 | #![allow( 2 | clippy::needless_range_loop, 3 | internal_features, 4 | unused_unsafe, 5 | dead_code 6 | )] 7 | #![feature(core_intrinsics)] 8 | 9 | #[cfg(test)] 10 | mod extractor; 11 | #[cfg(test)] 12 | mod fuzz; 13 | mod i64_add; 14 | mod i64_div_s; 15 | mod i64_div_u; 16 | mod i64_mul; 17 | mod i64_ne; 18 | mod i64_rem_s; 19 | mod i64_rem_u; 20 | mod i64_rotl; 21 | mod i64_rotr; 22 | mod i64_shl; 23 | mod i64_shr_s; 24 | mod i64_shr_u; 25 | mod i64_sub; 26 | -------------------------------------------------------------------------------- /src/compiler/error.rs: -------------------------------------------------------------------------------- 1 | use core::fmt::Formatter; 2 | use wasmparser::BinaryReaderError; 3 | 4 | #[derive(Debug)] 5 | pub enum CompilationError { 6 | BranchOffsetOutOfBounds, 7 | BlockFuelOutOfBounds, 8 | NotSupportedExtension, 9 | DropKeepOutOfBounds, 10 | BranchTableTargetsOutOfBounds, 11 | MalformedWasmBinary(BinaryReaderError), 12 | NotSupportedImportType, 13 | NotSupportedFuncType, 14 | UnresolvedImportFunction, 15 | MalformedImportFunctionType, 16 | NonDefaultMemoryIndex, 17 | ConstEvaluationFailed, 18 | NotSupportedLocalType, 19 | NotSupportedGlobalType, 20 | MaxReadonlyDataReached, 21 | MissingEntrypoint, 22 | MalformedFuncType, 23 | MemoryOutOfBounds, 24 | TableOutOfBounds, 25 | } 26 | 27 | impl From for CompilationError { 28 | fn from(err: BinaryReaderError) -> Self { 29 | CompilationError::MalformedWasmBinary(err) 30 | } 31 | } 32 | 33 | impl core::fmt::Display for CompilationError { 34 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 35 | match self { 36 | CompilationError::BranchOffsetOutOfBounds => write!(f, "branch offset out of bounds"), 37 | CompilationError::BlockFuelOutOfBounds => write!(f, "block fuel out of bounds"), 38 | CompilationError::NotSupportedExtension => write!(f, "not supported extension"), 39 | CompilationError::DropKeepOutOfBounds => write!(f, "drop keep out of bounds"), 40 | CompilationError::BranchTableTargetsOutOfBounds => { 41 | write!(f, "branch table targets are out of bounds") 42 | } 43 | CompilationError::MalformedWasmBinary(err) => { 44 | write!(f, "malformed wasm binary ({})", err) 45 | } 46 | CompilationError::NotSupportedImportType => write!(f, "not supported an import type"), 47 | CompilationError::NotSupportedFuncType => write!(f, "not supported func type"), 48 | CompilationError::UnresolvedImportFunction => write!(f, "unresolved import function"), 49 | CompilationError::MalformedImportFunctionType => { 50 | write!(f, "MalformedImportFunctionType") 51 | } 52 | CompilationError::NonDefaultMemoryIndex => write!(f, "non default memory index"), 53 | CompilationError::ConstEvaluationFailed => write!(f, "const evaluation failed"), 54 | CompilationError::NotSupportedLocalType => write!(f, "not supported local type"), 55 | CompilationError::NotSupportedGlobalType => write!(f, "not supported global type"), 56 | CompilationError::MaxReadonlyDataReached => write!(f, "memory segments overflow"), 57 | CompilationError::MissingEntrypoint => write!(f, "missing entrypoint"), 58 | CompilationError::MalformedFuncType => write!(f, "malformed func type"), 59 | CompilationError::MemoryOutOfBounds => write!(f, "out of bounds memory access"), 60 | CompilationError::TableOutOfBounds => write!(f, "out of bounds table access"), 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/compiler/fuel_costs.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | compiler::drop_keep::DropKeep, 3 | BASE_FUEL_COST, 4 | CALL_FUEL_COST, 5 | DROP_KEEP_PER_FUEL, 6 | ENTITY_FUEL_COST, 7 | LOAD_FUEL_COST, 8 | LOCALS_PER_FUEL, 9 | MEMORY_BYTES_PER_FUEL, 10 | STORE_FUEL_COST, 11 | TABLE_ELEMS_PER_FUEL, 12 | }; 13 | use core::num::NonZeroU32; 14 | 15 | /// Type storing all kinds of fuel costs of instructions. 16 | #[derive(Default, Debug, Copy, Clone)] 17 | pub struct FuelCosts; 18 | 19 | impl FuelCosts { 20 | pub const BASE: u32 = BASE_FUEL_COST; 21 | pub const ENTITY: u32 = ENTITY_FUEL_COST; 22 | pub const LOAD: u32 = LOAD_FUEL_COST; 23 | pub const STORE: u32 = STORE_FUEL_COST; 24 | pub const CALL: u32 = CALL_FUEL_COST; 25 | 26 | /// Returns the fuel consumption of the number of items with costs per items. 27 | pub fn costs_per(len_items: u32, items_per_fuel: u32) -> u32 { 28 | NonZeroU32::new(items_per_fuel) 29 | .map(|items_per_fuel| len_items / items_per_fuel) 30 | .unwrap_or(0) 31 | } 32 | 33 | /// Returns the fuel consumption for branches and returns using the given [`DropKeep`]. 34 | pub fn fuel_for_drop_keep(drop_keep: DropKeep) -> u32 { 35 | if drop_keep.drop == 0 { 36 | return 0; 37 | } 38 | Self::costs_per(u32::from(drop_keep.keep), DROP_KEEP_PER_FUEL) 39 | } 40 | 41 | /// Returns the fuel consumption for calling a function with the amount of local variables. 42 | /// 43 | /// # Note 44 | /// 45 | /// Function parameters are also treated as local variables. 46 | pub fn fuel_for_locals(locals: u32) -> u32 { 47 | Self::costs_per(locals, LOCALS_PER_FUEL) 48 | } 49 | 50 | /// Returns the fuel consumption for processing the amount of memory bytes. 51 | pub fn fuel_for_bytes(bytes: u32) -> u32 { 52 | Self::costs_per(bytes, MEMORY_BYTES_PER_FUEL) 53 | } 54 | 55 | /// Returns the fuel consumption for processing the amount of table elements. 56 | pub fn fuel_for_elements(elements: u32) -> u32 { 57 | Self::costs_per(elements, TABLE_ELEMS_PER_FUEL) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/compiler/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 local registry efficiently registers and resolves local variables. 8 | /// The problem is that the Wasm specification allows encoding 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 | /// to protect the `rwasm` interpreter against exploitation. 12 | /// 13 | /// This implementation allows accessing local variables in this worst-case 14 | /// scenario with the 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 number of registered local variables. 23 | len_registered: u32, 24 | } 25 | 26 | impl LocalsRegistry { 27 | /// Returns the number of registered local variables. 28 | /// 29 | /// # Note 30 | /// 31 | /// Since in WebAssembly function parameters are also local variables, 32 | /// this function actually returns the number of function parameters 33 | /// and explicitly defined local variables. 34 | pub(crate) fn len_registered(&self) -> u32 { 35 | self.len_registered 36 | } 37 | 38 | /// Registers an `amount` of local variables. 39 | /// 40 | /// # Panics 41 | /// 42 | /// If too many local variables have been registered. 43 | pub fn register_locals(&mut self, amount: u32) { 44 | if amount == 0 { 45 | return; 46 | } 47 | self.len_registered = self.len_registered.checked_add(amount).unwrap_or_else(|| { 48 | panic!( 49 | "tried to register too many local variables for the function: got {}, additional {amount}", 50 | self.len_registered 51 | ) 52 | }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/compiler/mod.rs: -------------------------------------------------------------------------------- 1 | mod config; 2 | mod control_flow; 3 | mod drop_keep; 4 | mod error; 5 | mod fuel_costs; 6 | mod func_builder; 7 | mod labels; 8 | mod locals_registry; 9 | mod parser; 10 | mod segment_builder; 11 | mod translator; 12 | mod utils; 13 | mod value_stack; 14 | 15 | pub use self::{ 16 | config::{CompilationConfig, StateRouterConfig}, 17 | error::CompilationError, 18 | fuel_costs::FuelCosts, 19 | parser::ModuleParser, 20 | }; 21 | use crate::RwasmModule; 22 | use alloc::vec::Vec; 23 | 24 | pub struct RwasmCompilationResult { 25 | pub rwasm_bytecode: Vec, 26 | pub constructor_params: Vec, 27 | } 28 | 29 | pub fn compile_wasm_to_rwasm( 30 | wasm_binary: &[u8], 31 | compilation_config: CompilationConfig, 32 | ) -> Result { 33 | let (module, params) = RwasmModule::compile(compilation_config, wasm_binary)?; 34 | Ok(RwasmCompilationResult { 35 | rwasm_bytecode: module.serialize(), 36 | constructor_params: params.into_vec(), 37 | }) 38 | } 39 | -------------------------------------------------------------------------------- /src/compiler/utils.rs: -------------------------------------------------------------------------------- 1 | /// The relative depth of a Wasm branching target. 2 | #[derive(Debug, Copy, Clone)] 3 | pub struct RelativeDepth(u32); 4 | 5 | impl RelativeDepth { 6 | /// Returns the relative depth as `u32`. 7 | pub fn into_u32(self) -> u32 { 8 | self.0 9 | } 10 | 11 | /// Creates a relative depth from the given `u32` value. 12 | pub fn from_u32(relative_depth: u32) -> Self { 13 | Self(relative_depth) 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | #![cfg_attr(not(feature = "std"), no_std)] 2 | #![warn(unused_crate_dependencies)] 3 | #![allow(unused_variables)] 4 | #![recursion_limit = "750"] 5 | 6 | mod compiler; 7 | mod strategy; 8 | mod types; 9 | mod vm; 10 | #[cfg(feature = "wasmtime")] 11 | mod wasmtime; 12 | 13 | extern crate alloc; 14 | extern crate core; 15 | 16 | pub use compiler::*; 17 | use libm as _; 18 | pub use strategy::*; 19 | pub use types::*; 20 | pub use vm::*; 21 | pub use wasmparser::{FuncType, ValType}; 22 | #[cfg(feature = "wasmtime")] 23 | pub use wasmtime::*; 24 | -------------------------------------------------------------------------------- /src/types/branch_offset.rs: -------------------------------------------------------------------------------- 1 | use bincode::{Decode, Encode}; 2 | #[cfg(feature = "tracing")] 3 | use serde::{Deserialize, Serialize}; 4 | 5 | /// A signed offset for branch instructions. 6 | /// 7 | /// This defines how much the instruction pointer is offset 8 | /// upon taking the respective branch. 9 | #[cfg_attr(feature = "tracing", derive(Serialize, Deserialize))] 10 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] 11 | pub struct BranchOffset(i32); 12 | 13 | impl From for BranchOffset { 14 | fn from(index: i32) -> Self { 15 | Self(index) 16 | } 17 | } 18 | 19 | impl BranchOffset { 20 | /// Creates an uninitialized [`BranchOffset`]. 21 | pub fn uninit() -> Self { 22 | Self(0) 23 | } 24 | 25 | /// Creates an initialized [`BranchOffset`] from `src` to `dst`. 26 | /// 27 | /// # Errors 28 | /// 29 | /// If the resulting [`BranchOffset`] is out of bounds. 30 | /// 31 | /// # Panics 32 | /// 33 | /// If the resulting [`BranchOffset`] is uninitialized, aka equal to 0. 34 | pub fn from_src_to_dst(src: u32, dst: u32) -> Option { 35 | let src = i64::from(src); 36 | let dst = i64::from(dst); 37 | let offset = dst.checked_sub(src)?; 38 | let offset = i32::try_from(offset).ok()?; 39 | Some(Self(offset)) 40 | } 41 | 42 | /// Returns the `i32` representation of the [`BranchOffset`]. 43 | pub fn to_i32(self) -> i32 { 44 | self.0 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/types/constructor_params.rs: -------------------------------------------------------------------------------- 1 | use alloc::vec::Vec; 2 | use wasmparser::CustomSectionReader; 3 | 4 | /// A constant string that represents the name of a custom section. 5 | /// 6 | /// This constant is used to denote the name of a custom section named "input". 7 | /// It is defined as a static string slice with a fixed value. 8 | const CONSTRUCTOR_CUSTOM_SECTION_NAME: &'static str = "input"; 9 | 10 | #[derive(Default, Debug)] 11 | #[repr(transparent)] 12 | pub struct ConstructorParams(Option>); 13 | 14 | impl core::ops::Deref for ConstructorParams { 15 | type Target = Option>; 16 | 17 | fn deref(&self) -> &Self::Target { 18 | &self.0 19 | } 20 | } 21 | 22 | impl Into> for ConstructorParams { 23 | fn into(self) -> Vec { 24 | self.0.unwrap_or_default() 25 | } 26 | } 27 | 28 | impl ConstructorParams { 29 | pub fn try_parse(&mut self, reader: CustomSectionReader) { 30 | if reader.name() == CONSTRUCTOR_CUSTOM_SECTION_NAME { 31 | self.0 = Some(reader.data().to_vec()); 32 | } 33 | } 34 | 35 | pub fn into_vec(self) -> Vec { 36 | self.0.unwrap_or_default() 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/types/error.rs: -------------------------------------------------------------------------------- 1 | use crate::{types::HostError, CompilationError, TrapCode}; 2 | use alloc::boxed::Box; 3 | use core::fmt::Formatter; 4 | 5 | #[derive(Debug)] 6 | pub enum RwasmError { 7 | CompilationError(CompilationError), 8 | TrapCode(TrapCode), 9 | HostInterruption(Box), 10 | } 11 | 12 | impl From for RwasmError { 13 | fn from(err: CompilationError) -> Self { 14 | RwasmError::CompilationError(err) 15 | } 16 | } 17 | impl From for RwasmError { 18 | fn from(err: TrapCode) -> Self { 19 | RwasmError::TrapCode(err) 20 | } 21 | } 22 | 23 | impl core::fmt::Display for RwasmError { 24 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 25 | match self { 26 | RwasmError::CompilationError(err) => write!(f, "{}", err), 27 | RwasmError::TrapCode(err) => write!(f, "{}", err), 28 | RwasmError::HostInterruption(_) => write!(f, "host interruption"), 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/types/func_ref.rs: -------------------------------------------------------------------------------- 1 | use crate::{UntypedValue, NULL_FUNC_IDX}; 2 | 3 | #[derive(Clone, Debug)] 4 | pub struct FuncRef(pub u32); 5 | 6 | impl FuncRef { 7 | pub fn new(func_idx: u32) -> Self { 8 | Self(func_idx) 9 | } 10 | 11 | pub fn null() -> Self { 12 | Self(NULL_FUNC_IDX) 13 | } 14 | 15 | pub fn resolve_index(&self) -> u32 { 16 | self.0 17 | } 18 | 19 | pub fn is_null(&self) -> bool { 20 | self.0 == NULL_FUNC_IDX 21 | } 22 | } 23 | 24 | impl From for FuncRef { 25 | fn from(value: UntypedValue) -> Self { 26 | let value = value.as_u32(); 27 | if value == 0 { 28 | Self::null() 29 | } else { 30 | Self(value) 31 | } 32 | } 33 | } 34 | impl Into for FuncRef { 35 | fn into(self) -> UntypedValue { 36 | UntypedValue::from(self.0) 37 | } 38 | } 39 | 40 | pub type ExternRef = FuncRef; 41 | -------------------------------------------------------------------------------- /src/types/global_variable.rs: -------------------------------------------------------------------------------- 1 | use crate::{ExternRef, FuncRef, Value, F32, F64}; 2 | use wasmparser::{GlobalType, ValType}; 3 | 4 | #[derive(Debug)] 5 | pub struct GlobalVariable { 6 | pub global_type: GlobalType, 7 | pub default_value: i64, 8 | } 9 | 10 | impl GlobalVariable { 11 | pub fn new(global_type: GlobalType, default_value: i64) -> Self { 12 | Self { 13 | global_type, 14 | default_value, 15 | } 16 | } 17 | 18 | pub fn value(&self) -> Option { 19 | match self.global_type.content_type { 20 | ValType::I32 => Some(Value::I32(self.default_value as i32)), 21 | ValType::I64 => Some(Value::I64(self.default_value)), 22 | ValType::F32 => Some(Value::F32(F32::from_bits(self.default_value as i32 as u32))), 23 | ValType::F64 => Some(Value::F64(F64::from_bits(self.default_value as u64))), 24 | ValType::V128 => None, 25 | ValType::FuncRef => Some(Value::FuncRef(FuncRef::new( 26 | self.default_value.try_into().ok()?, 27 | ))), 28 | ValType::ExternRef => Some(Value::ExternRef(ExternRef::new( 29 | self.default_value.try_into().ok()?, 30 | ))), 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /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 `rwasm::Error` and 10 | /// `rwasm::Trap` and will be represented as a boxed `HostError`. You can then use the various 11 | /// methods on `rwasm::Error` to get your custom error type back 12 | pub trait HostError: 'static + Display + Debug + DowncastSync {} 13 | impl_downcast!(HostError); 14 | -------------------------------------------------------------------------------- /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/instruction_set/add_sub.rs: -------------------------------------------------------------------------------- 1 | use crate::InstructionSet; 2 | 3 | impl InstructionSet { 4 | /// Max stack height: 8 5 | pub fn op_i64_add(&mut self) { 6 | self.op_local_get(4); 7 | self.op_local_get(3); 8 | self.op_i32_or(); 9 | self.op_i32_const(-1); 10 | self.op_i32_xor(); 11 | self.op_i32_clz(); 12 | self.op_local_get(5); 13 | self.op_local_get(4); 14 | self.op_i32_add(); 15 | self.op_local_get(1); 16 | self.op_local_set(6); 17 | self.op_i32_const(-1); 18 | self.op_i32_xor(); 19 | self.op_i32_clz(); 20 | self.op_local_get(5); 21 | self.op_local_get(4); 22 | self.op_i32_add(); 23 | self.op_local_get(3); 24 | self.op_local_get(3); 25 | self.op_i32_gt_u(); 26 | self.op_br_if_eqz(3); 27 | self.op_i32_const(1); 28 | self.op_i32_add(); 29 | self.op_local_set(5); 30 | self.op_drop(); 31 | self.op_drop(); 32 | self.op_drop(); 33 | self.op_drop(); 34 | } 35 | 36 | /// Max stack height: 8 37 | pub fn op_i64_sub(&mut self) { 38 | // TODO(dmitry123): "looks optimizable" 39 | self.op_local_get(4); 40 | self.op_local_get(3); 41 | self.op_i32_sub(); 42 | self.op_local_get(5); 43 | self.op_local_get(4); 44 | self.op_i32_lt_u(); 45 | self.op_local_get(5); 46 | self.op_local_get(4); 47 | self.op_i32_sub(); 48 | self.op_local_get(2); 49 | self.op_i32_sub(); 50 | self.op_local_set(5); 51 | self.op_drop(); 52 | self.op_local_set(4); 53 | self.op_drop(); 54 | self.op_drop(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/types/instruction_set/conv.rs: -------------------------------------------------------------------------------- 1 | use crate::InstructionSet; 2 | 3 | impl InstructionSet { 4 | /// Max stack height: 0 5 | pub fn op_i32_wrap_i64(&mut self) { 6 | self.op_drop(); // drop high 7 | } 8 | 9 | /// Max stack height: 1 10 | pub fn op_i64_extend_i32_s(&mut self) { 11 | self.op_local_get(1); // duplicate for both low and high 12 | self.op_i32_const(31); 13 | self.op_i32_shr_s(); // arithmetic shift right → high 14 | } 15 | 16 | /// Max stack height: 1 17 | pub fn op_i64_extend_i32_u(&mut self) { 18 | self.op_i32_const(0); // high = 0 19 | } 20 | 21 | /// Max stack height: 2 22 | pub fn op_i64_extend8_s(&mut self) { 23 | self.op_drop(); // drop old high word 24 | self.op_i32_extend8_s(); // apply sign-extension to 8-bit low 25 | self.op_dup(); // copy low → uses to derive high 26 | self.op_i32_const(31); 27 | self.op_i32_shr_s(); // high = low >> 31 28 | } 29 | 30 | /// Max stack height: 2 31 | pub fn op_i64_extend16_s(&mut self) { 32 | self.op_drop(); // drop old high word 33 | self.op_i32_extend16_s(); // apply sign-extension to 8-bit low 34 | self.op_dup(); // copy low → uses to derive high 35 | self.op_i32_const(31); 36 | self.op_i32_shr_s(); // high = low >> 31 37 | } 38 | 39 | /// Max stack height: 2 40 | pub fn op_i64_extend32_s(&mut self) { 41 | self.op_drop(); // drop old high word 42 | self.op_dup(); // copy low → uses to derive high 43 | self.op_i32_const(31); 44 | self.op_i32_shr_s(); // high = low >> 31 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/types/instruction_set/mul.rs: -------------------------------------------------------------------------------- 1 | use crate::InstructionSet; 2 | 3 | impl InstructionSet { 4 | /// Max stack height: 8 5 | pub fn op_i64_mul(&mut self) { 6 | self.op_i32_const(0); 7 | self.op_i32_const(0); 8 | self.op_i32_const(0); 9 | self.op_local_get(4); 10 | self.op_local_get(6); 11 | self.op_i32_add(); 12 | self.op_local_get(7); 13 | self.op_local_get(9); 14 | self.op_i32_add(); 15 | self.op_i32_mul(); 16 | self.op_local_get(5); 17 | self.op_local_get(8); 18 | self.op_i32_mul(); 19 | self.op_local_get(7); 20 | self.op_i32_const(65535); 21 | self.op_i32_and(); 22 | self.op_local_tee(9); 23 | self.op_local_get(10); 24 | self.op_i32_const(65535); 25 | self.op_i32_and(); 26 | self.op_local_tee(8); 27 | self.op_i32_mul(); 28 | self.op_local_tee(6); 29 | self.op_local_get(8); 30 | self.op_i32_const(16); 31 | self.op_i32_shr_u(); 32 | self.op_local_tee(6); 33 | self.op_local_get(8); 34 | self.op_i32_mul(); 35 | self.op_local_tee(8); 36 | self.op_local_get(10); 37 | self.op_local_get(12); 38 | self.op_i32_const(16); 39 | self.op_i32_shr_u(); 40 | self.op_local_tee(7); 41 | self.op_i32_mul(); 42 | self.op_i32_add(); 43 | self.op_local_tee(11); 44 | self.op_i32_const(16); 45 | self.op_i32_shl(); 46 | self.op_i32_add(); 47 | self.op_local_tee(8); 48 | self.op_i32_add(); 49 | self.op_i32_sub(); 50 | self.op_local_get(6); 51 | self.op_local_get(5); 52 | self.op_i32_lt_u(); 53 | self.op_i32_add(); 54 | self.op_local_get(3); 55 | self.op_local_get(3); 56 | self.op_i32_mul(); 57 | self.op_local_tee(8); 58 | self.op_local_get(9); 59 | self.op_i32_const(16); 60 | self.op_i32_shr_u(); 61 | self.op_local_get(10); 62 | self.op_local_get(8); 63 | self.op_i32_lt_u(); 64 | self.op_i32_const(16); 65 | self.op_i32_shl(); 66 | self.op_i32_or(); 67 | self.op_i32_add(); 68 | self.op_local_tee(9); 69 | self.op_i32_add(); 70 | self.op_local_get(8); 71 | self.op_local_get(8); 72 | self.op_i32_lt_u(); 73 | self.op_i32_add(); 74 | self.op_local_get(6); 75 | // TODO(dmitry123): "how efficiently make drop=7 keep=2?" 76 | self.op_local_set(8); 77 | self.op_local_set(6); 78 | self.op_drop(); 79 | self.op_drop(); 80 | self.op_drop(); 81 | self.op_drop(); 82 | self.op_drop(); 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/types/trap_code.rs: -------------------------------------------------------------------------------- 1 | use bincode::{Decode, Encode}; 2 | use core::fmt::Formatter; 3 | 4 | #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode)] 5 | #[cfg_attr(feature = "tracing", derive(serde::Serialize, serde::Deserialize))] 6 | #[repr(u8)] 7 | pub enum TrapCode { 8 | UnreachableCodeReached = 0x00, 9 | MemoryOutOfBounds = 0x01, 10 | TableOutOfBounds = 0x02, 11 | IndirectCallToNull = 0x03, 12 | IntegerDivisionByZero = 0x04, 13 | IntegerOverflow = 0x05, 14 | BadConversionToInteger = 0x06, 15 | StackOverflow = 0x07, 16 | BadSignature = 0x08, 17 | OutOfFuel = 0x09, 18 | UnknownExternalFunction = 0x0a, 19 | IllegalOpcode = 0x0b, 20 | // a special trap code for interrupting an execution, 21 | // it saves the latest registers for IP and SP in the call stack 22 | InterruptionCalled = 0x0c, 23 | // this trap code is only used for external calls to terminate the execution, 24 | // but this error can't be returned from an execution cycle 25 | ExecutionHalted = 0xff, 26 | } 27 | 28 | impl core::fmt::Display for TrapCode { 29 | fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { 30 | match self { 31 | TrapCode::UnreachableCodeReached => write!(f, "unreachable code reached"), 32 | TrapCode::MemoryOutOfBounds => write!(f, "out of bounds memory access"), 33 | TrapCode::TableOutOfBounds => { 34 | write!(f, "undefined element: out of bounds table access") 35 | } 36 | TrapCode::IndirectCallToNull => write!(f, "uninitialized element 2"), 37 | TrapCode::IntegerDivisionByZero => write!(f, "integer divide by zero"), 38 | TrapCode::IntegerOverflow => write!(f, "integer overflow"), 39 | TrapCode::BadConversionToInteger => write!(f, "invalid conversion to integer"), 40 | TrapCode::StackOverflow => write!(f, "call stack exhausted"), 41 | TrapCode::BadSignature => write!(f, "indirect call type mismatch"), 42 | TrapCode::OutOfFuel => write!(f, "out of fuel"), 43 | TrapCode::UnknownExternalFunction => write!(f, "unknown external function"), 44 | TrapCode::IllegalOpcode => write!(f, "illegal opcode"), 45 | TrapCode::InterruptionCalled => write!(f, "interruption called"), 46 | TrapCode::ExecutionHalted => write!(f, "execution halted"), 47 | } 48 | } 49 | } 50 | 51 | impl core::error::Error for TrapCode {} 52 | -------------------------------------------------------------------------------- /src/vm/call_stack.rs: -------------------------------------------------------------------------------- 1 | use crate::{InstructionPtr, ValueStackPtr}; 2 | use smallvec::SmallVec; 3 | 4 | #[derive(Default, Clone)] 5 | pub struct CallStack { 6 | buf: SmallVec<[(InstructionPtr, ValueStackPtr); 128]>, 7 | offsets: SmallVec<[usize; 128]>, 8 | offset: usize, 9 | } 10 | 11 | impl CallStack { 12 | pub fn push(&mut self, ip: InstructionPtr, vs: ValueStackPtr) { 13 | self.buf.push((ip, vs)); 14 | } 15 | 16 | pub fn pop(&mut self) -> Option<(InstructionPtr, ValueStackPtr)> { 17 | if self.buf.len() > self.offset { 18 | self.buf.pop() 19 | } else { 20 | None 21 | } 22 | } 23 | 24 | pub fn is_empty(&self) -> bool { 25 | self.buf.len() == self.offset 26 | } 27 | 28 | pub fn len(&self) -> usize { 29 | // underflow should never happen here 30 | self.buf.len() - self.offset 31 | } 32 | 33 | pub fn commit_offset(&mut self) { 34 | self.offsets.push(self.offset); 35 | self.offset = self.buf.len(); 36 | } 37 | 38 | pub fn reset(&mut self) { 39 | unsafe { 40 | self.buf.set_len(self.offset); 41 | } 42 | // TODO(dmitry123): "replace with unwrap() once e2e is refactored" 43 | self.offset = self.offsets.pop().unwrap_or(0); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/vm/config.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default)] 2 | pub struct ExecutorConfig { 3 | pub fuel_enabled: bool, 4 | pub fuel_limit: Option, 5 | #[cfg(feature = "tracing")] 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 | #[cfg(feature = "tracing")] 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 | #[cfg(feature = "tracing")] 30 | pub fn trace_enabled(mut self, trace_enabled: bool) -> Self { 31 | self.trace_enabled = trace_enabled; 32 | self 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/vm/context.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | types::{TrapCode, UntypedValue}, 3 | Caller, 4 | RwasmStore, 5 | Store, 6 | ValueStackPtr, 7 | }; 8 | 9 | pub struct RwasmCaller<'a, T: Send + Sync + 'static> { 10 | store: &'a mut RwasmStore, 11 | program_counter: u32, 12 | sp: ValueStackPtr, 13 | } 14 | 15 | impl<'a, T: Send + Sync> RwasmCaller<'a, T> { 16 | pub fn new(store: &'a mut RwasmStore, program_counter: u32, sp: ValueStackPtr) -> Self { 17 | Self { 18 | store, 19 | program_counter, 20 | sp, 21 | } 22 | } 23 | 24 | pub fn sp(&self) -> ValueStackPtr { 25 | self.sp 26 | } 27 | } 28 | 29 | impl<'a, T: Send + Sync> Store for RwasmCaller<'a, T> { 30 | fn memory_read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), TrapCode> { 31 | self.store.global_memory.read(offset, buffer)?; 32 | Ok(()) 33 | } 34 | 35 | fn memory_write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), TrapCode> { 36 | self.store.global_memory.write(offset, buffer)?; 37 | #[cfg(feature = "tracing")] 38 | self.vm 39 | .store 40 | .tracer 41 | .memory_change(offset as u32, buffer.len() as u32, buffer); 42 | Ok(()) 43 | } 44 | 45 | fn context_mut R>(&mut self, mut func: F) -> R { 46 | func(&mut self.store.context.borrow_mut()) 47 | } 48 | 49 | fn context R>(&self, func: F) -> R { 50 | func(&self.store.context.borrow()) 51 | } 52 | 53 | fn try_consume_fuel(&mut self, delta: u64) -> Result<(), TrapCode> { 54 | self.store.try_consume_fuel(delta) 55 | } 56 | 57 | fn remaining_fuel(&mut self) -> Option { 58 | self.store.remaining_fuel() 59 | } 60 | } 61 | 62 | impl<'a, T: Send + Sync> Caller for RwasmCaller<'a, T> { 63 | fn program_counter(&self) -> u32 { 64 | self.program_counter 65 | } 66 | 67 | fn stack_push(&mut self, value: UntypedValue) { 68 | self.sp.push(value); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/vm/executor/alu.rs: -------------------------------------------------------------------------------- 1 | use crate::{types::UntypedValue, vm::executor::RwasmExecutor, TrapCode}; 2 | 3 | macro_rules! impl_visit_unary { 4 | ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { 5 | $( 6 | #[inline(always)] 7 | pub(crate) fn $visit_ident(&mut self) { 8 | self.sp.eval_top(UntypedValue::$untyped_ident); 9 | self.ip.add(1); 10 | } 11 | )* 12 | } 13 | } 14 | 15 | impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { 16 | impl_visit_unary! { 17 | fn visit_i32_eqz(i32_eqz); 18 | 19 | fn visit_i32_clz(i32_clz); 20 | fn visit_i32_ctz(i32_ctz); 21 | fn visit_i32_popcnt(i32_popcnt); 22 | 23 | fn visit_i32_wrap_i64(i32_wrap_i64); 24 | 25 | fn visit_i32_extend8_s(i32_extend8_s); 26 | fn visit_i32_extend16_s(i32_extend16_s); 27 | } 28 | } 29 | 30 | macro_rules! impl_visit_binary { 31 | ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { 32 | $( 33 | #[inline(always)] 34 | pub(crate) fn $visit_ident(&mut self) { 35 | self.sp.eval_top2(UntypedValue::$untyped_ident); 36 | self.ip.add(1); 37 | } 38 | )* 39 | } 40 | } 41 | 42 | impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { 43 | impl_visit_binary! { 44 | fn visit_i32_eq(i32_eq); 45 | fn visit_i32_ne(i32_ne); 46 | fn visit_i32_lt_s(i32_lt_s); 47 | fn visit_i32_lt_u(i32_lt_u); 48 | fn visit_i32_gt_s(i32_gt_s); 49 | fn visit_i32_gt_u(i32_gt_u); 50 | fn visit_i32_le_s(i32_le_s); 51 | fn visit_i32_le_u(i32_le_u); 52 | fn visit_i32_ge_s(i32_ge_s); 53 | fn visit_i32_ge_u(i32_ge_u); 54 | 55 | fn visit_i32_add(i32_add); 56 | fn visit_i32_sub(i32_sub); 57 | fn visit_i32_mul(i32_mul); 58 | fn visit_i32_and(i32_and); 59 | fn visit_i32_or(i32_or); 60 | fn visit_i32_xor(i32_xor); 61 | fn visit_i32_shl(i32_shl); 62 | fn visit_i32_shr_s(i32_shr_s); 63 | fn visit_i32_shr_u(i32_shr_u); 64 | fn visit_i32_rotl(i32_rotl); 65 | fn visit_i32_rotr(i32_rotr); 66 | } 67 | } 68 | 69 | macro_rules! impl_visit_fallible_binary { 70 | ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { 71 | $( 72 | #[inline(always)] 73 | pub(crate) fn $visit_ident(&mut self) -> Result<(), TrapCode> { 74 | self.sp.try_eval_top2(UntypedValue::$untyped_ident)?; 75 | self.ip.add(1); 76 | Ok(()) 77 | } 78 | )* 79 | } 80 | } 81 | 82 | impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { 83 | impl_visit_fallible_binary! { 84 | fn visit_i32_div_s(i32_div_s); 85 | fn visit_i32_div_u(i32_div_u); 86 | fn visit_i32_rem_s(i32_rem_s); 87 | fn visit_i32_rem_u(i32_rem_u); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /src/vm/executor/stack.rs: -------------------------------------------------------------------------------- 1 | use crate::{CompiledFunc, LocalDepth, RwasmExecutor, UntypedValue}; 2 | 3 | impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { 4 | #[inline(always)] 5 | pub(crate) fn visit_local_get(&mut self, local_depth: LocalDepth) { 6 | let value = self.sp.nth_back(local_depth as usize); 7 | self.sp.push(value); 8 | self.ip.add(1); 9 | } 10 | 11 | #[inline(always)] 12 | pub(crate) fn visit_local_set(&mut self, local_depth: LocalDepth) { 13 | let new_value = self.sp.pop(); 14 | self.sp.set_nth_back(local_depth as usize, new_value); 15 | self.ip.add(1); 16 | } 17 | 18 | #[inline(always)] 19 | pub(crate) fn visit_local_tee(&mut self, local_depth: LocalDepth) { 20 | let new_value = self.sp.last(); 21 | self.sp.set_nth_back(local_depth as usize, new_value); 22 | self.ip.add(1); 23 | } 24 | 25 | #[inline(always)] 26 | pub(crate) fn visit_drop(&mut self) { 27 | self.sp.drop(); 28 | self.ip.add(1); 29 | } 30 | 31 | #[inline(always)] 32 | pub(crate) fn visit_select(&mut self) { 33 | self.sp.eval_top3(|e1, e2, e3| { 34 | let condition = >::from(e3); 35 | if condition { 36 | e1 37 | } else { 38 | e2 39 | } 40 | }); 41 | self.ip.add(1); 42 | } 43 | 44 | #[inline(always)] 45 | pub(crate) fn visit_ref_func(&mut self, compiled_func: CompiledFunc) { 46 | self.sp.push_as(compiled_func); 47 | self.ip.add(1); 48 | } 49 | 50 | #[inline(always)] 51 | pub(crate) fn visit_i32_const(&mut self, untyped_value: UntypedValue) { 52 | self.sp.push(untyped_value); 53 | self.ip.add(1); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/vm/executor/system.rs: -------------------------------------------------------------------------------- 1 | use crate::{BlockFuel, GlobalIdx, MaxStackHeight, RwasmExecutor, SignatureIdx, Store, TrapCode}; 2 | 3 | impl<'a, T: Send + Sync> RwasmExecutor<'a, T> { 4 | #[inline(always)] 5 | pub(crate) fn visit_consume_fuel(&mut self, block_fuel: BlockFuel) -> Result<(), TrapCode> { 6 | if self.store.config.fuel_enabled { 7 | self.store.try_consume_fuel(block_fuel as u64)?; 8 | } 9 | self.ip.add(1); 10 | Ok(()) 11 | } 12 | 13 | #[inline(always)] 14 | pub(crate) fn visit_consume_fuel_stack(&mut self) -> Result<(), TrapCode> { 15 | let block_fuel: u32 = self.sp.pop_as(); 16 | if self.store.config.fuel_enabled { 17 | self.store.try_consume_fuel(block_fuel as u64)?; 18 | } 19 | self.ip.add(1); 20 | Ok(()) 21 | } 22 | 23 | #[inline(always)] 24 | pub(crate) fn visit_signature_check( 25 | &mut self, 26 | signature_idx: SignatureIdx, 27 | ) -> Result<(), TrapCode> { 28 | if let Some(actual_signature) = self.store.last_signature.take() { 29 | if actual_signature != signature_idx { 30 | return Err(TrapCode::BadSignature); 31 | } 32 | } 33 | self.ip.add(1); 34 | Ok(()) 35 | } 36 | 37 | #[inline(always)] 38 | pub(crate) fn visit_stack_check( 39 | &mut self, 40 | max_stack_height: MaxStackHeight, 41 | ) -> Result<(), TrapCode> { 42 | self.value_stack.reserve(max_stack_height as usize)?; 43 | // we should rewrite SP after reserve because of potential reallocation 44 | self.sp = self.value_stack.stack_ptr(); 45 | self.ip.add(1); 46 | Ok(()) 47 | } 48 | 49 | #[inline(always)] 50 | pub(crate) fn visit_global_get(&mut self, global_idx: GlobalIdx) { 51 | let global_value = self 52 | .store 53 | .global_variables 54 | .get(&global_idx) 55 | .copied() 56 | .unwrap_or_default(); 57 | self.sp.push(global_value); 58 | self.ip.add(1); 59 | } 60 | 61 | #[inline(always)] 62 | pub(crate) fn visit_global_set(&mut self, global_idx: GlobalIdx) { 63 | let new_value = self.sp.pop(); 64 | self.store.global_variables.insert(global_idx, new_value); 65 | self.ip.add(1); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/vm/instr_ptr.rs: -------------------------------------------------------------------------------- 1 | use crate::types::Opcode; 2 | 3 | /// The instruction pointer to the instruction of a function on the call stack. 4 | #[derive(Debug, Copy, Clone, PartialEq)] 5 | #[repr(transparent)] 6 | pub struct InstructionPtr { 7 | /// The pointer to the instruction. 8 | pub(crate) ptr: *const Opcode, 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) -> Self { 25 | Self { ptr } 26 | } 27 | 28 | /// Offset the [`rwasm::engine::code_map::InstructionPtr`] by the given value. 29 | /// 30 | /// # Safety 31 | /// 32 | /// The caller is responsible for calling this method only with valid 33 | /// offset values so that the [`rwasm::engine::code_map::InstructionPtr`] never points out of 34 | /// valid bounds of the instructions of the same compiled Wasm function. 35 | #[inline(always)] 36 | pub fn offset(&mut self, by: isize) { 37 | // SAFETY: Within Wasm bytecode execution we are guaranteed by 38 | // Wasm validation and `rwasm` codegen to never run out 39 | // of valid bounds using this method. 40 | self.ptr = unsafe { self.ptr.offset(by) }; 41 | } 42 | 43 | #[inline(always)] 44 | pub fn add(&mut self, delta: usize) { 45 | // SAFETY: Within Wasm bytecode execution we are guaranteed by 46 | // Wasm validation and `rwasm` codegen to never run out 47 | // of valid bounds using this method. 48 | self.ptr = unsafe { self.ptr.add(delta) }; 49 | } 50 | 51 | /// Returns a shared reference to the currently pointed at [`Opcode`]. 52 | /// 53 | /// # Safety 54 | /// 55 | /// The caller is responsible for calling this method only when it is 56 | /// guaranteed that the [`rwasm::engine::code_map::InstructionPtr`] is validly pointing inside 57 | /// the boundaries of its associated compiled Wasm function. 58 | #[inline(always)] 59 | pub fn get(&self) -> Opcode { 60 | // SAFETY: Within Wasm bytecode execution we are guaranteed by 61 | // Wasm validation and `rwasm` codegen to never run out 62 | // of valid bounds using this method. 63 | unsafe { *self.ptr } 64 | } 65 | 66 | #[cfg(feature = "tracing")] 67 | pub fn is_valid(self, max: u64) -> bool { 68 | self.ptr as u64 <= max 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/vm/mod.rs: -------------------------------------------------------------------------------- 1 | mod call_stack; 2 | mod config; 3 | mod context; 4 | mod engine; 5 | mod executor; 6 | mod handler; 7 | mod instr_ptr; 8 | mod memory; 9 | mod store; 10 | mod table_entity; 11 | #[cfg(feature = "tracing")] 12 | mod tracer; 13 | mod value_stack; 14 | 15 | pub use call_stack::*; 16 | pub use config::*; 17 | pub use context::*; 18 | pub use engine::*; 19 | pub use executor::*; 20 | pub use handler::*; 21 | pub use instr_ptr::*; 22 | pub use memory::*; 23 | pub use store::*; 24 | pub use table_entity::*; 25 | #[cfg(feature = "tracing")] 26 | pub use tracer::*; 27 | pub use value_stack::*; 28 | -------------------------------------------------------------------------------- /src/vm/tracer/event.rs: -------------------------------------------------------------------------------- 1 | use crate::Opcode; 2 | 3 | pub fn opcode_stack_read(ins: Opcode) -> u32 { 4 | if ins.is_binary_instruction() { 5 | return 2; 6 | } else if ins.is_unary_instruction() { 7 | return 1; 8 | } else if ins.is_nullary() { 9 | return 0; 10 | } else if ins.is_memory_load_instruction() { 11 | return 1; 12 | } else if ins.is_memory_store_instruction() { 13 | return 2; 14 | } else if ins.is_branch_instruction() { 15 | } 16 | 0 17 | } 18 | 19 | pub fn opcode_stack_write(op: Opcode) -> bool { 20 | if op.is_binary_instruction() || op.is_unary_instruction() | op.is_const_instruction() { 21 | return true; 22 | } 23 | if op.is_binary_instruction() { 24 | return false; 25 | } 26 | if op.is_memory_instruction() { 27 | if op.is_memory_load_instruction() { 28 | true 29 | } else { 30 | false 31 | } 32 | } else { 33 | false 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/vm/tracer/mem_index.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | N_DEFAULT_STACK_SIZE, 3 | N_MAX_DATA_SEGMENTS_BITS, 4 | N_MAX_RECURSION_DEPTH, 5 | N_MAX_TABLES, 6 | N_MAX_TABLE_SIZE, 7 | }; 8 | 9 | /// We map every type of data of rwasm engine including stack, tables and call frames and memory 10 | /// into one type of virtual indexing. This indexing is only used to prove memory consistency and 11 | /// never actually implemented. We provide helper functions to map recorded memory changes, table 12 | /// changes and calls into virtual memory changes. The unit data type for virtual memory indexing is 13 | /// u8 i.e., a byte. The basic data type of rwasm is u32, and it is represented by 4 bytes in the 14 | /// virtual memory indexing. (We're always using the max capacity since the zkVM does not care about 15 | /// dynamic capacity). Virtual indexing starts with the stack, then function call frames, then 16 | /// tables, with memory comes last. The stack has 4096 elements 17 | pub const UNIT: u32 = 4; // size_of() / size_of() 18 | 19 | /// The stack starts with and invalid position, and every element in the stack has an index less 20 | /// than SP_START. 21 | pub const SP_START: u32 = N_DEFAULT_STACK_SIZE as u32 * UNIT + UNIT; 22 | 23 | /// This is the index when the stack reaches the max length. So every valid index for the stack is 24 | /// >0. Making the index of a stack element strictly larger than 0 makes circuit checking this bound 25 | /// simpler. 26 | pub const SP_END: u32 = UNIT; 27 | 28 | pub const FUNC_FRAME_SIZE: u32 = UNIT; // TODO (dmitry123): "it looks like the call stack only save the returning pc right?" 29 | pub const FUNC_FRAME_START: u32 = SP_START + UNIT; 30 | pub const FUNC_FRAME_END: u32 = FUNC_FRAME_START + FUNC_FRAME_SIZE * N_MAX_RECURSION_DEPTH as u32; 31 | pub const TABLE_ELEM_SIZE: u32 = UNIT; 32 | pub const TABLE_SEG_START: u32 = FUNC_FRAME_END + UNIT; 33 | pub const TABLE_SEG_END: u32 = TABLE_SEG_START + N_MAX_TABLES * N_MAX_TABLE_SIZE * TABLE_ELEM_SIZE; 34 | pub const DATA_SEG_ELEM_SIZE: u32 = UNIT; 35 | pub const DATA_SEG_START: u32 = TABLE_SEG_END + UNIT; 36 | pub const DATA_SEG_END: u32 = DATA_SEG_START + N_MAX_DATA_SEGMENTS_BITS as u32 * DATA_SEG_ELEM_SIZE; 37 | pub const GLOBAL_MEM_START: u32 = DATA_SEG_END + UNIT; 38 | pub const GLOBAL_MEM_END: u32 = GLOBAL_MEM_START + (1 << 8) << 20; 39 | -------------------------------------------------------------------------------- /src/vm/tracer/state.rs: -------------------------------------------------------------------------------- 1 | #[derive(Default, Clone)] 2 | pub struct VMState { 3 | pub clk: u32, 4 | pub shard: u32, 5 | pub sp: u32, 6 | } 7 | 8 | pub const MAX_CYCLE_FOR_OP: u32 = 8; // TODO determine this for all instructions 9 | pub const MAX_CYCLE: u32 = 1 << 20; 10 | 11 | impl VMState { 12 | pub fn next_cycle(&mut self) { 13 | if self.clk + MAX_CYCLE_FOR_OP > MAX_CYCLE { 14 | self.clk = 0; 15 | self.next_shard(); 16 | } else { 17 | self.clk += 4; 18 | } 19 | } 20 | 21 | pub fn next_shard(&mut self) { 22 | self.shard += 1; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/nitro-verifier/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "nitro-verifier" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | path = "lib.rs" 9 | 10 | [dependencies] 11 | fluentbase-sdk = { git = "https://github.com/fluentlabs-xyz/fluentbase", branch = "devel", default-features = false, features = ["debug-print", "fast-panic"] } 12 | coset = { version = "0.3.8", default-features = false } 13 | ciborium = { version = "^0.2.1", default-features = false } 14 | x509-cert = { version = "0.2.5", default-features = false, features = ["pem"] } 15 | der = { version = "0.7.9", default-features = false, features = ["alloc"] } 16 | ecdsa = { version = "0.16.9", default-features = false, features = [ 17 | "digest", 18 | "alloc", 19 | "der", 20 | ] } 21 | p384 = { version = "0.13.0", default-features = false, features = [ 22 | "ecdsa-core", 23 | "ecdsa", 24 | ] } 25 | 26 | [build-dependencies] 27 | fluentbase-build = { git = "https://github.com/fluentlabs-xyz/fluentbase", branch = "devel", default-features = false } 28 | 29 | [features] 30 | default = ["std"] 31 | std = [] 32 | 33 | [profile.release] 34 | panic = "abort" 35 | lto = true 36 | opt-level = 3 37 | strip = true 38 | debug = false 39 | debug-assertions = false 40 | rpath = false 41 | codegen-units = 1 -------------------------------------------------------------------------------- /tests/nitro-verifier/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all 2 | all: 3 | cargo b -------------------------------------------------------------------------------- /tests/nitro-verifier/attestation.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/fluentlabs-xyz/rwasm/74f26a351667b91d7299dc50cefb9ab1b241957a/tests/nitro-verifier/attestation.bin -------------------------------------------------------------------------------- /tests/nitro-verifier/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | fluentbase_build::build_default_example_contract() 3 | } 4 | -------------------------------------------------------------------------------- /tests/nitro-verifier/nitro.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIICETCCAZagAwIBAgIRAPkxdWgbkK/hHUbMtOTn+FYwCgYIKoZIzj0EAwMwSTEL 3 | MAkGA1UEBhMCVVMxDzANBgNVBAoMBkFtYXpvbjEMMAoGA1UECwwDQVdTMRswGQYD 4 | VQQDDBJhd3Mubml0cm8tZW5jbGF2ZXMwHhcNMTkxMDI4MTMyODA1WhcNNDkxMDI4 5 | MTQyODA1WjBJMQswCQYDVQQGEwJVUzEPMA0GA1UECgwGQW1hem9uMQwwCgYDVQQL 6 | DANBV1MxGzAZBgNVBAMMEmF3cy5uaXRyby1lbmNsYXZlczB2MBAGByqGSM49AgEG 7 | BSuBBAAiA2IABPwCVOumCMHzaHDimtqQvkY4MpJzbolL//Zy2YlES1BR5TSksfbb 8 | 48C8WBoyt7F2Bw7eEtaaP+ohG2bnUs990d0JX28TcPQXCEPZ3BABIeTPYwEoCWZE 9 | h8l5YoQwTcU/9KNCMEAwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUkCW1DdkF 10 | R+eWw5b6cp3PmanfS5YwDgYDVR0PAQH/BAQDAgGGMAoGCCqGSM49BAMDA2kAMGYC 11 | MQCjfy+Rocm9Xue4YnwWmNJVA44fA0P5W2OpYow9OYCVRaEevL8uO1XYru5xtMPW 12 | rfMCMQCi85sWBbJwKKXdS6BptQFuZbT73o/gBh1qUxl/nNr12UO8Yfwr6wPLb+6N 13 | IwLz3/Y= 14 | -----END CERTIFICATE----- -------------------------------------------------------------------------------- /tests/wasmtime_fpu.rs: -------------------------------------------------------------------------------- 1 | use wasmtime::{Engine, Instance, Module, Store, TypedFunc}; 2 | 3 | pub fn run_main(wat: &str) -> anyhow::Result 4 | where 5 | Results: wasmtime::WasmResults, 6 | { 7 | let engine = Engine::default(); 8 | let module = Module::new(&engine, wat)?; 9 | let mut store = Store::new(&engine, ()); 10 | let instance = Instance::new(&mut store, &module, &[])?; 11 | let run: TypedFunc<(), Results> = instance.get_typed_func(&mut store, "main")?; 12 | run.call(&mut store, ()) 13 | } 14 | 15 | #[test] 16 | fn test_wasmtime_disabled_f32_sqrt() -> anyhow::Result<()> { 17 | let wat = r#" 18 | (module 19 | (func (export "main") (result f32) 20 | f32.const 9.0 21 | f32.sqrt 22 | ) 23 | ) 24 | "#; 25 | let result = run_main::(wat); 26 | let trap = result 27 | .err() 28 | .expect("execution should fail") 29 | .downcast_ref::() 30 | .expect("execution should fail with a trap") 31 | .clone(); 32 | matches!(trap, wasmtime::Trap::DisabledOpcode); 33 | Ok(()) 34 | } 35 | 36 | #[test] 37 | fn test_wasmtime_disabled_f64_div() -> anyhow::Result<()> { 38 | let wat = r#" 39 | (module 40 | (func (export "main") (result f64) 41 | f64.const 9.0 42 | f64.const 3.0 43 | f64.div 44 | f64.const 10.0 45 | f64.add 46 | ) 47 | ) 48 | "#; 49 | let result = run_main::(wat); 50 | let trap = result 51 | .err() 52 | .expect("execution should fail") 53 | .downcast_ref::() 54 | .expect("execution should fail with a trap") 55 | .clone(); 56 | matches!(trap, wasmtime::Trap::DisabledOpcode); 57 | Ok(()) 58 | } 59 | 60 | #[test] 61 | fn test_wasmtime_f32_const() -> anyhow::Result<()> { 62 | let wat = r#" 63 | (module 64 | (func (export "main") (result f32) 65 | f32.const 9.0 66 | ) 67 | ) 68 | "#; 69 | let result = run_main::(wat); 70 | matches!(result, Ok(9.0)); 71 | Ok(()) 72 | } 73 | 74 | #[test] 75 | fn test_wasmtime_f64_const() -> anyhow::Result<()> { 76 | let wat = r#" 77 | (module 78 | (func (export "main") (result f64) 79 | f64.const 9.0 80 | ) 81 | ) 82 | "#; 83 | let result = run_main::(wat); 84 | matches!(result, Ok(9.0)); 85 | Ok(()) 86 | } 87 | -------------------------------------------------------------------------------- /wasm/.gitignore: -------------------------------------------------------------------------------- 1 | lib.wasm 2 | lib.wat -------------------------------------------------------------------------------- /wasm/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "wasm" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [lib] 7 | crate-type = ["cdylib"] 8 | path = "lib.rs" 9 | 10 | [dependencies] 11 | rwasm = { path = "..", default-features = false } 12 | wat = "1.230.0" -------------------------------------------------------------------------------- /wasm/Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: build 2 | build: 3 | cargo b --release --target=wasm32-unknown-unknown --no-default-features 4 | cp ./target/wasm32-unknown-unknown/release/wasm.wasm ./lib.wasm 5 | wasm2wat ./lib.wasm > ./lib.wat || true -------------------------------------------------------------------------------- /wasm/lib.rs: -------------------------------------------------------------------------------- 1 | use rwasm::{CompilationConfig, RwasmModule}; 2 | 3 | #[no_mangle] 4 | pub fn main() { 5 | let wasm_binary = wat::parse_str( 6 | r#" 7 | (module 8 | (func $const-i32 (result i32) (i32.const 0x132)) 9 | (func (export "as-select-first") (result i32) 10 | (select (call $const-i32) (i32.const 2) (i32.const 3)) 11 | ) 12 | )"#, 13 | ) 14 | .unwrap(); 15 | let (result, _) = RwasmModule::compile(CompilationConfig::default(), &wasm_binary).unwrap(); 16 | core::hint::black_box(result); 17 | } 18 | --------------------------------------------------------------------------------