From 6f86ac7b272f6d55ac8e2d9c5204afc1281cd094 Mon Sep 17 00:00:00 2001 From: Dmitry Savonin <3367605+dmitry123@users.noreply.github.com> Date: Thu, 8 May 2025 14:36:49 +0400 Subject: [PATCH 001/141] refactor!: A major refactoring of rwasm compiler and executor (#9) * refactor!: a major refactoring of rwasm compiler and executor * fix: parsing i32 params * chore: code cleanup and tiny fixes * ci: enable running e2e tests after unit tests * ci: fix missing symbol in ci config * refactor: simplifying the runtime - Remove `ElementSegmentEntity` and associated logic. - Refactor `RwasmExecutor` to use bit vectors for tracking empty segments. - Consolidate module organization, migrating `executor` to `vm`. - Replace legacy data structures with more efficient implementations. - Update Cargo dependencies, adding `bitvec` and removing unused packages. --- .github/workflows/ci.yml | 4 +- .gitignore | 9 +- Cargo.lock | 676 ++----- Cargo.toml | 45 +- e2e/Cargo.toml | 9 +- e2e/src/context.rs | 232 +-- e2e/src/error.rs | 6 +- e2e/src/handler.rs | 4 +- e2e/src/lib.rs | 57 +- e2e/src/run.rs | 45 +- executor/Cargo.toml | 28 - executor/src/binary_format/drop_keep.rs | 28 - executor/src/binary_format/instruction.rs | 73 - executor/src/binary_format/module.rs | 118 -- executor/src/binary_format/number.rs | 32 - executor/src/binary_format/reader_writer.rs | 269 --- executor/src/binary_format/utils.rs | 77 - executor/src/lib.rs | 39 - executor/src/memory.rs | 161 -- executor/src/types.rs | 49 - executor/src/utils.rs | 83 - {rwasm => legacy}/Cargo.toml | 9 +- {rwasm => legacy}/README.md | 0 {rwasm => legacy}/src/arena/component_vec.rs | 0 {rwasm => legacy}/src/arena/dedup.rs | 0 {rwasm => legacy}/src/arena/guarded.rs | 0 {rwasm => legacy}/src/arena/mod.rs | 0 {rwasm => legacy}/src/arena/tests.rs | 0 {rwasm => legacy}/src/core/host_error.rs | 0 {rwasm => legacy}/src/core/import_linker.rs | 0 {rwasm => legacy}/src/core/mod.rs | 0 .../src/core/nan_preserving_float.rs | 0 {rwasm => legacy}/src/core/rwasm.rs | 0 {rwasm => legacy}/src/core/trap.rs | 0 {rwasm => legacy}/src/core/units.rs | 0 {rwasm => legacy}/src/core/untyped.rs | 2 + {rwasm => legacy}/src/core/value.rs | 0 .../src/engine/bytecode/instr_meta.rs | 0 {rwasm => legacy}/src/engine/bytecode/mod.rs | 0 .../src/engine/bytecode/stack_height.rs | 0 .../src/engine/bytecode/tests.rs | 0 .../src/engine/bytecode/utils.rs | 0 {rwasm => legacy}/src/engine/cache.rs | 0 {rwasm => legacy}/src/engine/code_map.rs | 0 {rwasm => legacy}/src/engine/config.rs | 8 +- {rwasm => legacy}/src/engine/const_pool.rs | 0 {rwasm => legacy}/src/engine/executor.rs | 0 {rwasm => legacy}/src/engine/func_args.rs | 0 .../src/engine/func_builder/control_frame.rs | 0 .../src/engine/func_builder/control_stack.rs | 0 .../src/engine/func_builder/error.rs | 0 .../src/engine/func_builder/inst_builder.rs | 0 .../src/engine/func_builder/labels.rs | 0 .../engine/func_builder/locals_registry.rs | 0 .../src/engine/func_builder/mod.rs | 0 .../src/engine/func_builder/translator.rs | 0 .../src/engine/func_builder/translator_i32.rs | 0 .../src/engine/func_builder/value_stack.rs | 0 {rwasm => legacy}/src/engine/func_types.rs | 0 {rwasm => legacy}/src/engine/mod.rs | 0 {rwasm => legacy}/src/engine/resumable.rs | 0 {rwasm => legacy}/src/engine/stack/frames.rs | 0 {rwasm => legacy}/src/engine/stack/mod.rs | 0 .../src/engine/stack/values/mod.rs | 0 .../src/engine/stack/values/sp.rs | 0 .../src/engine/stack/values/tests.rs | 0 {rwasm => legacy}/src/engine/tests.rs | 0 {rwasm => legacy}/src/engine/tracer.rs | 0 {rwasm => legacy}/src/engine/traits.rs | 0 {rwasm => legacy}/src/error.rs | 0 {rwasm => legacy}/src/externref.rs | 0 {rwasm => legacy}/src/foreach_tuple.rs | 0 {rwasm => legacy}/src/func/caller.rs | 0 {rwasm => legacy}/src/func/error.rs | 0 {rwasm => legacy}/src/func/func_type.rs | 0 {rwasm => legacy}/src/func/funcref.rs | 0 {rwasm => legacy}/src/func/into_func.rs | 0 {rwasm => legacy}/src/func/mod.rs | 0 {rwasm => legacy}/src/func/typed_func.rs | 0 {rwasm => legacy}/src/global.rs | 0 {rwasm => legacy}/src/instance/builder.rs | 0 {rwasm => legacy}/src/instance/exports.rs | 0 {rwasm => legacy}/src/instance/mod.rs | 0 {rwasm => legacy}/src/lib.rs | 0 {rwasm => legacy}/src/limits.rs | 0 {rwasm => legacy}/src/linker.rs | 0 {rwasm => legacy}/src/memory/buffer.rs | 0 {rwasm => legacy}/src/memory/data.rs | 0 {rwasm => legacy}/src/memory/error.rs | 0 {rwasm => legacy}/src/memory/mod.rs | 0 {rwasm => legacy}/src/memory/tests.rs | 0 {rwasm => legacy}/src/module/builder.rs | 0 .../src/module/compile/block_type.rs | 0 {rwasm => legacy}/src/module/compile/mod.rs | 0 .../src/module/custom_section.rs | 0 {rwasm => legacy}/src/module/data.rs | 0 {rwasm => legacy}/src/module/element.rs | 0 {rwasm => legacy}/src/module/error.rs | 0 {rwasm => legacy}/src/module/export.rs | 0 {rwasm => legacy}/src/module/global.rs | 0 {rwasm => legacy}/src/module/import.rs | 0 {rwasm => legacy}/src/module/init_expr.rs | 0 .../src/module/instantiate/error.rs | 0 .../src/module/instantiate/mod.rs | 0 .../src/module/instantiate/pre.rs | 0 .../src/module/instantiate/tests.rs | 0 {rwasm => legacy}/src/module/mod.rs | 0 {rwasm => legacy}/src/module/parser.rs | 0 {rwasm => legacy}/src/module/read.rs | 0 {rwasm => legacy}/src/module/utils.rs | 0 {rwasm => legacy}/src/reftype.rs | 0 .../src/rwasm/binary_format/drop_keep.rs | 8 +- .../src/rwasm/binary_format/instruction.rs | 66 - .../rwasm/binary_format/instruction_set.rs | 0 .../src/rwasm}/binary_format/mod.rs | 9 +- legacy/src/rwasm/binary_format/module.rs | 136 ++ .../src/rwasm/binary_format/number.rs | 0 .../src/rwasm/binary_format/reader_writer.rs | 0 .../src/rwasm/binary_format/utils.rs | 0 {rwasm => legacy}/src/rwasm/drop_keep.rs | 0 .../src/rwasm/instruction_set.rs | 0 {rwasm => legacy}/src/rwasm/mod.rs | 0 {rwasm => legacy}/src/rwasm/reduced_module.rs | 57 +- .../src/rwasm/segment_builder.rs | 0 {rwasm => legacy}/src/rwasm/tests.rs | 0 {rwasm => legacy}/src/rwasm/translator.rs | 0 {rwasm => legacy}/src/rwasm/types.rs | 0 {rwasm => legacy}/src/store.rs | 0 {rwasm => legacy}/src/table/element.rs | 0 {rwasm => legacy}/src/table/error.rs | 0 {rwasm => legacy}/src/table/mod.rs | 0 {rwasm => legacy}/src/table/tests.rs | 0 {rwasm => legacy}/src/value.rs | 0 rwasm/src/rwasm/binary_format/mod.rs | 51 - rwasm/src/rwasm/binary_format/module.rs | 178 -- src/lib.rs | 12 + src/types/address_offset.rs | 23 + src/types/block_fuel.rs | 47 + src/types/branch_offset.rs | 64 + src/types/drop_keep.rs | 79 + src/types/error.rs | 68 + src/types/fuel_costs.rs | 105 ++ src/types/func_ref.rs | 41 + src/types/host_error.rs | 13 + src/types/import_linker.rs | 74 + src/types/import_name.rs | 44 + .../src/module.rs => src/types/instruction.rs | 119 +- src/types/instruction_set.rs | 446 +++++ src/types/mod.rs | 71 + src/types/module.rs | 128 ++ src/types/nan_preserving_float.rs | 270 +++ src/types/units.rs | 324 ++++ src/types/untyped_value.rs | 1661 +++++++++++++++++ src/types/utils.rs | 216 +++ src/types/value.rs | 1085 +++++++++++ {executor/src => src/vm}/config.rs | 0 {executor/src => src/vm}/context.rs | 6 +- src/vm/data_entity.rs | 52 + {executor/src => src/vm}/executor.rs | 298 ++- {executor/src => src/vm}/handler.rs | 10 +- {executor/src => src/vm}/instr_ptr.rs | 34 +- {executor/src => src/vm}/instruction_table.rs | 4 +- src/vm/memory.rs | 106 ++ src/vm/mod.rs | 24 + {executor/src => src/vm}/opcodes.rs | 337 ++-- src/vm/rw_op.rs | 360 ++++ src/vm/table_entity.rs | 238 +++ src/vm/tracer.rs | 186 ++ src/vm/value_stack.rs | 652 +++++++ 169 files changed, 7223 insertions(+), 2542 deletions(-) delete mode 100644 executor/Cargo.toml delete mode 100644 executor/src/binary_format/drop_keep.rs delete mode 100644 executor/src/binary_format/instruction.rs delete mode 100644 executor/src/binary_format/module.rs delete mode 100644 executor/src/binary_format/number.rs delete mode 100644 executor/src/binary_format/reader_writer.rs delete mode 100644 executor/src/binary_format/utils.rs delete mode 100644 executor/src/lib.rs delete mode 100644 executor/src/memory.rs delete mode 100644 executor/src/types.rs delete mode 100644 executor/src/utils.rs rename {rwasm => legacy}/Cargo.toml (84%) rename {rwasm => legacy}/README.md (100%) rename {rwasm => legacy}/src/arena/component_vec.rs (100%) rename {rwasm => legacy}/src/arena/dedup.rs (100%) rename {rwasm => legacy}/src/arena/guarded.rs (100%) rename {rwasm => legacy}/src/arena/mod.rs (100%) rename {rwasm => legacy}/src/arena/tests.rs (100%) rename {rwasm => legacy}/src/core/host_error.rs (100%) rename {rwasm => legacy}/src/core/import_linker.rs (100%) rename {rwasm => legacy}/src/core/mod.rs (100%) rename {rwasm => legacy}/src/core/nan_preserving_float.rs (100%) rename {rwasm => legacy}/src/core/rwasm.rs (100%) rename {rwasm => legacy}/src/core/trap.rs (100%) rename {rwasm => legacy}/src/core/units.rs (100%) rename {rwasm => legacy}/src/core/untyped.rs (99%) rename {rwasm => legacy}/src/core/value.rs (100%) rename {rwasm => legacy}/src/engine/bytecode/instr_meta.rs (100%) rename {rwasm => legacy}/src/engine/bytecode/mod.rs (100%) rename {rwasm => legacy}/src/engine/bytecode/stack_height.rs (100%) rename {rwasm => legacy}/src/engine/bytecode/tests.rs (100%) rename {rwasm => legacy}/src/engine/bytecode/utils.rs (100%) rename {rwasm => legacy}/src/engine/cache.rs (100%) rename {rwasm => legacy}/src/engine/code_map.rs (100%) rename {rwasm => legacy}/src/engine/config.rs (99%) rename {rwasm => legacy}/src/engine/const_pool.rs (100%) rename {rwasm => legacy}/src/engine/executor.rs (100%) rename {rwasm => legacy}/src/engine/func_args.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/control_frame.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/control_stack.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/error.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/inst_builder.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/labels.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/locals_registry.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/mod.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/translator.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/translator_i32.rs (100%) rename {rwasm => legacy}/src/engine/func_builder/value_stack.rs (100%) rename {rwasm => legacy}/src/engine/func_types.rs (100%) rename {rwasm => legacy}/src/engine/mod.rs (100%) rename {rwasm => legacy}/src/engine/resumable.rs (100%) rename {rwasm => legacy}/src/engine/stack/frames.rs (100%) rename {rwasm => legacy}/src/engine/stack/mod.rs (100%) rename {rwasm => legacy}/src/engine/stack/values/mod.rs (100%) rename {rwasm => legacy}/src/engine/stack/values/sp.rs (100%) rename {rwasm => legacy}/src/engine/stack/values/tests.rs (100%) rename {rwasm => legacy}/src/engine/tests.rs (100%) rename {rwasm => legacy}/src/engine/tracer.rs (100%) rename {rwasm => legacy}/src/engine/traits.rs (100%) rename {rwasm => legacy}/src/error.rs (100%) rename {rwasm => legacy}/src/externref.rs (100%) rename {rwasm => legacy}/src/foreach_tuple.rs (100%) rename {rwasm => legacy}/src/func/caller.rs (100%) rename {rwasm => legacy}/src/func/error.rs (100%) rename {rwasm => legacy}/src/func/func_type.rs (100%) rename {rwasm => legacy}/src/func/funcref.rs (100%) rename {rwasm => legacy}/src/func/into_func.rs (100%) rename {rwasm => legacy}/src/func/mod.rs (100%) rename {rwasm => legacy}/src/func/typed_func.rs (100%) rename {rwasm => legacy}/src/global.rs (100%) rename {rwasm => legacy}/src/instance/builder.rs (100%) rename {rwasm => legacy}/src/instance/exports.rs (100%) rename {rwasm => legacy}/src/instance/mod.rs (100%) rename {rwasm => legacy}/src/lib.rs (100%) rename {rwasm => legacy}/src/limits.rs (100%) rename {rwasm => legacy}/src/linker.rs (100%) rename {rwasm => legacy}/src/memory/buffer.rs (100%) rename {rwasm => legacy}/src/memory/data.rs (100%) rename {rwasm => legacy}/src/memory/error.rs (100%) rename {rwasm => legacy}/src/memory/mod.rs (100%) rename {rwasm => legacy}/src/memory/tests.rs (100%) rename {rwasm => legacy}/src/module/builder.rs (100%) rename {rwasm => legacy}/src/module/compile/block_type.rs (100%) rename {rwasm => legacy}/src/module/compile/mod.rs (100%) rename {rwasm => legacy}/src/module/custom_section.rs (100%) rename {rwasm => legacy}/src/module/data.rs (100%) rename {rwasm => legacy}/src/module/element.rs (100%) rename {rwasm => legacy}/src/module/error.rs (100%) rename {rwasm => legacy}/src/module/export.rs (100%) rename {rwasm => legacy}/src/module/global.rs (100%) rename {rwasm => legacy}/src/module/import.rs (100%) rename {rwasm => legacy}/src/module/init_expr.rs (100%) rename {rwasm => legacy}/src/module/instantiate/error.rs (100%) rename {rwasm => legacy}/src/module/instantiate/mod.rs (100%) rename {rwasm => legacy}/src/module/instantiate/pre.rs (100%) rename {rwasm => legacy}/src/module/instantiate/tests.rs (100%) rename {rwasm => legacy}/src/module/mod.rs (100%) rename {rwasm => legacy}/src/module/parser.rs (100%) rename {rwasm => legacy}/src/module/read.rs (100%) rename {rwasm => legacy}/src/module/utils.rs (100%) rename {rwasm => legacy}/src/reftype.rs (100%) rename {rwasm => legacy}/src/rwasm/binary_format/drop_keep.rs (75%) rename {rwasm => legacy}/src/rwasm/binary_format/instruction.rs (90%) rename {rwasm => legacy}/src/rwasm/binary_format/instruction_set.rs (100%) rename {executor/src => legacy/src/rwasm}/binary_format/mod.rs (82%) create mode 100644 legacy/src/rwasm/binary_format/module.rs rename {rwasm => legacy}/src/rwasm/binary_format/number.rs (100%) rename {rwasm => legacy}/src/rwasm/binary_format/reader_writer.rs (100%) rename {rwasm => legacy}/src/rwasm/binary_format/utils.rs (100%) rename {rwasm => legacy}/src/rwasm/drop_keep.rs (100%) rename {rwasm => legacy}/src/rwasm/instruction_set.rs (100%) rename {rwasm => legacy}/src/rwasm/mod.rs (100%) rename {rwasm => legacy}/src/rwasm/reduced_module.rs (83%) rename {rwasm => legacy}/src/rwasm/segment_builder.rs (100%) rename {rwasm => legacy}/src/rwasm/tests.rs (100%) rename {rwasm => legacy}/src/rwasm/translator.rs (100%) rename {rwasm => legacy}/src/rwasm/types.rs (100%) rename {rwasm => legacy}/src/store.rs (100%) rename {rwasm => legacy}/src/table/element.rs (100%) rename {rwasm => legacy}/src/table/error.rs (100%) rename {rwasm => legacy}/src/table/mod.rs (100%) rename {rwasm => legacy}/src/table/tests.rs (100%) rename {rwasm => legacy}/src/value.rs (100%) delete mode 100644 rwasm/src/rwasm/binary_format/mod.rs delete mode 100644 rwasm/src/rwasm/binary_format/module.rs create mode 100644 src/lib.rs create mode 100644 src/types/address_offset.rs create mode 100644 src/types/block_fuel.rs create mode 100644 src/types/branch_offset.rs create mode 100644 src/types/drop_keep.rs create mode 100644 src/types/error.rs create mode 100644 src/types/fuel_costs.rs create mode 100644 src/types/func_ref.rs create mode 100644 src/types/host_error.rs create mode 100644 src/types/import_linker.rs create mode 100644 src/types/import_name.rs rename executor/src/module.rs => src/types/instruction.rs (70%) create mode 100644 src/types/instruction_set.rs create mode 100644 src/types/mod.rs create mode 100644 src/types/module.rs create mode 100644 src/types/nan_preserving_float.rs create mode 100644 src/types/units.rs create mode 100644 src/types/untyped_value.rs create mode 100644 src/types/utils.rs create mode 100644 src/types/value.rs rename {executor/src => src/vm}/config.rs (100%) rename {executor/src => src/vm}/context.rs (96%) create mode 100644 src/vm/data_entity.rs rename {executor/src => src/vm}/executor.rs (56%) rename {executor/src => src/vm}/handler.rs (94%) rename {executor/src => src/vm}/instr_ptr.rs (76%) rename {executor/src => src/vm}/instruction_table.rs (99%) create mode 100644 src/vm/memory.rs create mode 100644 src/vm/mod.rs rename {executor/src => src/vm}/opcodes.rs (83%) create mode 100644 src/vm/rw_op.rs create mode 100644 src/vm/table_entity.rs create mode 100644 src/vm/tracer.rs create mode 100644 src/vm/value_stack.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3997eab6..f02aef9cc 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,7 +37,9 @@ jobs: uses: Swatinem/rust-cache@v2 - name: Run tests - run: cargo test --no-fail-fast + run: | + cargo test --no-fail-fast + cargo test --no-fail-fast --manifest-path=./e2e/Cargo.toml lint: name: Lint diff --git a/.gitignore b/.gitignore index f86cc3492..4e50182d4 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,7 @@ - /target/ **/*.rs.bk -rwasm/Cargo.lock -rwasm/spec/target +legacy/Cargo.lock +legacy/spec/target **/fuzz/corpus/ **/fuzz/target/ @@ -11,7 +10,9 @@ rwasm/spec/target /.idea .vscode .DS_Store -libzktrie.so /tmp/* !/tmp/.gitkeep + +target +Cargo.lock \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index 820ec8491..3f23a1286 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,15 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - [[package]] name = "allocator-api2" version = "0.2.18" @@ -18,57 +9,48 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f" [[package]] -name = "anes" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" - -[[package]] -name = "anyhow" -version = "1.0.82" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" - -[[package]] -name = "assert_matches" -version = "1.5.0" +name = "autocfg" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b34d609dfbaf33d6889b2b7106d3ca345eacad44200913df5ba02bfd31d2ba9" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] -name = "atty" -version = "0.2.14" +name = "bincode" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +checksum = "36eaf5d7b090263e8150820482d5d93cd964a81e4019913c972f4edcc6edb740" dependencies = [ - "hermit-abi", - "libc", - "winapi", + "bincode_derive", + "serde", + "unty", ] [[package]] -name = "autocfg" -version = "1.2.0" +name = "bincode_derive" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "bf95709a440f45e986983918d0e8a1f30a9b1df04918fc828670606804ac3c09" +dependencies = [ + "virtue", +] [[package]] name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bumpalo" -version = "3.16.0" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" [[package]] -name = "byteorder" -version = "1.5.0" +name = "bitvec" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] [[package]] name = "bytes" @@ -76,100 +58,12 @@ version = "1.10.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" -[[package]] -name = "cast" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" - [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" -[[package]] -name = "ciborium" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" -dependencies = [ - "ciborium-io", - "ciborium-ll", - "serde", -] - -[[package]] -name = "ciborium-io" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" - -[[package]] -name = "ciborium-ll" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" -dependencies = [ - "ciborium-io", - "half", -] - -[[package]] -name = "clap" -version = "3.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" -dependencies = [ - "bitflags", - "clap_lex", - "indexmap 1.9.3", - "textwrap", -] - -[[package]] -name = "clap_lex" -version = "0.2.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" -dependencies = [ - "os_str_bytes", -] - -[[package]] -name = "criterion" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" -dependencies = [ - "anes", - "atty", - "cast", - "ciborium", - "clap", - "criterion-plot", - "itertools", - "lazy_static", - "num-traits", - "oorandom", - "regex", - "serde", - "serde_derive", - "serde_json", - "tinytemplate", - "walkdir", -] - -[[package]] -name = "criterion-plot" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" -dependencies = [ - "cast", - "itertools", -] - [[package]] name = "crunchy" version = "0.2.2" @@ -178,15 +72,9 @@ checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" [[package]] name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "either" -version = "1.11.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "ea8a8b81cacc08888170eef4d13b775126db426d0b348bee9d18c2c1eaf123cf" [[package]] name = "equivalent" @@ -201,32 +89,23 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" [[package]] -name = "getrandom" -version = "0.2.14" +name = "funty" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" [[package]] -name = "half" -version = "2.4.1" +name = "getrandom" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +checksum = "73fea8450eea4bac3940448fb7ae50d91f034f941199fcd9d909a5a07aa455f0" dependencies = [ "cfg-if", - "crunchy", + "libc", + "r-efi", + "wasi", ] -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - [[package]] name = "hashbrown" version = "0.15.2" @@ -236,37 +115,7 @@ dependencies = [ "allocator-api2", "equivalent", "foldhash", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "hermit-abi" -version = "0.1.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" -dependencies = [ - "libc", -] - -[[package]] -name = "hex-literal" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6fe2267d4ed49bc07b63801559be28c718ea06c4738b7a03c94df7386d2cde46" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", + "rustc-std-workspace-alloc", ] [[package]] @@ -276,7 +125,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cea70ddb795996207ad57735b50c5982d8844f38ba9ee5f1aedcfb708a2aa11e" dependencies = [ "equivalent", - "hashbrown 0.15.2", + "hashbrown", ] [[package]] @@ -285,44 +134,11 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" -[[package]] -name = "itertools" -version = "0.10.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" -dependencies = [ - "either", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "leb128" -version = "0.2.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" - [[package]] name = "libc" -version = "0.2.154" +version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae743338b92ff9146ce83992f766a31066a91a8c84a45e0e9f21e7cf6de6d346" - -[[package]] -name = "libm" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" +checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" [[package]] name = "memchr" @@ -332,9 +148,9 @@ checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -360,18 +176,6 @@ dependencies = [ "syn", ] -[[package]] -name = "oorandom" -version = "11.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" - -[[package]] -name = "os_str_bytes" -version = "6.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" - [[package]] name = "paste" version = "1.0.14" @@ -380,9 +184,12 @@ checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" [[package]] name = "ppv-lite86" -version = "0.2.17" +version = "0.2.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] [[package]] name = "proc-macro-crate" @@ -411,22 +218,33 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r-efi" +version = "5.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" + +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" -version = "0.8.5" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +checksum = "9fbfd9d094a40bf3ae768db9361049ace4c0e04a4fd6b359518bd7b73a73dd97" dependencies = [ - "libc", "rand_chacha", "rand_core", ] [[package]] name = "rand_chacha" -version = "0.3.1" +version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" dependencies = [ "ppv-lite86", "rand_core", @@ -434,169 +252,53 @@ dependencies = [ [[package]] name = "rand_core" -version = "0.6.4" +version = "0.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +checksum = "99d9a13982dcf210057a8a78572b2217b667c3beacbf3a0d8b454f6f82837d38" dependencies = [ "getrandom", ] [[package]] -name = "regex" -version = "1.10.4" +name = "rustc-std-workspace-alloc" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" - -[[package]] -name = "rustversion" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" +checksum = "f9d441c3b2ebf55cebf796bfdc265d67fa09db17b7bb6bd4be75c509e1e8fec3" [[package]] name = "rwasm" -version = "0.30.0" +version = "0.0.0" dependencies = [ - "anyhow", - "assert_matches", - "byteorder", - "criterion", + "bincode", + "bitvec", + "bytes", "downcast-rs", - "hashbrown 0.15.2", - "hex-literal", - "libm", + "hashbrown", "num-traits", - "paste", - "rand", - "smallvec", - "spin", - "strum", - "strum_macros", - "wasmparser-nostd", - "wast 52.0.3", - "wat", -] - -[[package]] -name = "rwasm-e2e" -version = "0.1.0" -dependencies = [ - "anyhow", - "rwasm", - "rwasm-executor", - "wast 62.0.1", -] - -[[package]] -name = "rwasm-executor" -version = "0.1.0" -dependencies = [ - "byteorder", - "bytes", - "hashbrown 0.15.2", "num_enum", "paste", - "rwasm", + "rand", "tiny-keccak", -] - -[[package]] -name = "ryu" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" - -[[package]] -name = "same-file" -version = "1.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" -dependencies = [ - "winapi-util", + "wasmparser-nostd", ] [[package]] name = "serde" -version = "1.0.199" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c9f6e76df036c77cd94996771fb40db98187f096dd0b9af39c6c6e452ba966a" +checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.199" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "11bd257a6541e141e42ca6d24ae26f7714887b47e89aa739099104c7e4d3b7fc" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.116" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" -dependencies = [ - "itoa", - "ryu", - "serde", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "spin" -version = "0.9.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" - -[[package]] -name = "strum" -version = "0.27.1" +version = "1.0.210" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" - -[[package]] -name = "strum_macros" -version = "0.27.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f" dependencies = [ - "heck", "proc-macro2", "quote", - "rustversion", "syn", ] @@ -612,10 +314,10 @@ dependencies = [ ] [[package]] -name = "textwrap" -version = "0.16.1" +name = "tap" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" [[package]] name = "tiny-keccak" @@ -626,16 +328,6 @@ dependencies = [ "crunchy", ] -[[package]] -name = "tinytemplate" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" -dependencies = [ - "serde", - "serde_json", -] - [[package]] name = "toml_datetime" version = "0.6.8" @@ -648,7 +340,7 @@ version = "0.22.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b4795ff5edd201c7cd6dca065ae59972ce77d1b80fa0a84d94950ece7d1474" dependencies = [ - "indexmap 2.9.0", + "indexmap", "toml_datetime", "winnow", ] @@ -660,52 +352,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] -name = "unicode-width" -version = "0.1.12" +name = "unty" +version = "0.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68f5e5f3158ecfd4b8ff6fe086db7c8467a2dfdac97fe420f2b7c4aa97af66d6" +checksum = "6d49784317cd0d1ee7ec5c716dd598ec5b4483ea832a2dced265471cc0f690ae" [[package]] -name = "walkdir" -version = "2.5.0" +name = "virtue" +version = "0.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" -dependencies = [ - "same-file", - "winapi-util", -] +checksum = "051eb1abcf10076295e815102942cc58f9d5e3b4560e46e53c21e8ff6f3af7b1" [[package]] name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-encoder" -version = "0.22.1" +version = "0.14.2+wasi-0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a584273ccc2d9311f1dd19dc3fb26054661fa3e373d53ede5d1144ba07a9acd" +checksum = "9683f9a5a998d873c0d21fcbe3c083009670149a8fab228644b8bd36b2c48cb3" dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-encoder" -version = "0.31.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41763f20eafed1399fff1afb466496d3a959f58241436cfdc17e3f5ca954de16" -dependencies = [ - "leb128", -] - -[[package]] -name = "wasm-encoder" -version = "0.206.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d759312e1137f199096d80a70be685899cd7d3d09c572836bb2e9b69b4dc3b1e" -dependencies = [ - "leb128", + "wit-bindgen-rt", ] [[package]] @@ -718,160 +382,48 @@ dependencies = [ ] [[package]] -name = "wast" -version = "52.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15942180f265280eede7bc38b239e9770031d1821c02d905284216c645316430" -dependencies = [ - "leb128", - "memchr", - "unicode-width", - "wasm-encoder 0.22.1", -] - -[[package]] -name = "wast" -version = "62.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ae06f09dbe377b889fbd620ff8fa21e1d49d1d9d364983c0cdbf9870cb9f1f" -dependencies = [ - "leb128", - "memchr", - "unicode-width", - "wasm-encoder 0.31.1", -] - -[[package]] -name = "wast" -version = "206.0.0" +name = "winnow" +version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68586953ee4960b1f5d84ebf26df3b628b17e6173bc088e0acfbce431469795a" +checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" dependencies = [ - "bumpalo", - "leb128", "memchr", - "unicode-width", - "wasm-encoder 0.206.0", ] [[package]] -name = "wat" -version = "1.206.0" +name = "wit-bindgen-rt" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da4c6f2606276c6e991aebf441b2fc92c517807393f039992a3e0ad873efe4ad" +checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "wast 206.0.0", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", + "bitflags", ] [[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" +name = "wyz" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-util" -version = "0.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4d4cc384e1e73b93bafa6fb4f1df8c41695c8a91cf9c4c64358067d15a7b6c6b" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" dependencies = [ - "windows-sys", + "tap", ] [[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" +name = "zerocopy" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +checksum = "2586fea28e186957ef732a5f8b3be2da217d65c5969d4b1e17f973ebbe876879" dependencies = [ - "windows-targets", + "zerocopy-derive", ] [[package]] -name = "windows-targets" -version = "0.52.5" +name = "zerocopy-derive" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f0713a46559409d202e70e28227288446bf7841d3211583a4b53e3f6d96e7eb" +checksum = "a996a8f63c5c4448cd959ac1bab0aaa3306ccfd060472f85943ee0750f0169be" dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7088eed71e8b8dda258ecc8bac5fb1153c5cffaf2578fc8ff5d61e23578d3263" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9985fd1504e250c615ca5f281c3f7a6da76213ebd5ccc9561496568a2752afb6" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88ba073cf16d5372720ec942a8ccbf61626074c6d4dd2e745299726ce8b89670" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "87f4261229030a858f36b459e748ae97545d6f1ec60e5e0d6a3d32e0dc232ee9" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "db3c2bf3d13d5b658be73463284eaf12830ac9a26a90c717b7f771dfe97487bf" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4e4246f76bdeff09eb48875a0fd3e2af6aada79d409d33011886d3e1581517d9" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "852298e482cd67c356ddd9570386e2862b5673c85bd5f88df9ab6802b334c596" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" - -[[package]] -name = "winnow" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "63d3fcd9bba44b03821e7d699eeee959f3126dcc4aa8e4ae18ec617c2a5cea10" -dependencies = [ - "memchr", + "proc-macro2", + "quote", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 71ced4b2b..bdaf7ca0c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,29 +1,40 @@ -[workspace] -members = [ - "e2e", - "executor", - "rwasm", -] -exclude = [] -resolver = "2" - -[workspace.package] +[package] +name = "rwasm" +edition = "2021" authors = [ "Dmitry Savonin ", ] repository = "https://github.com/fluentlabs-xyz/rwasm" -edition = "2021" readme = "README.md" license = "MIT" keywords = ["wasm", "webassembly", "interpreter", "vm"] categories = ["wasm", "no-std", "virtualization"] -[workspace.dependencies] -byteorder = { version = "1.5.0", default-features = false } -hashbrown = "0.15.2" -strum = { version = "0.27.1" } -strum_macros = { version = "0.27.1" } +[dependencies] +wasmparser = { version = "0.100.1", package = "wasmparser-nostd", default-features = false } +hashbrown = { version = "0.15.2", features = ["alloc"] } tiny-keccak = { version = "2.0.2", features = ["keccak"] } paste = { version = "1.0", default-features = false } num_enum = { version = "0.7.3" } -bytes = { version = "1.10.1", default-features = false } \ No newline at end of file +bytes = { version = "1.10.1", default-features = false } +downcast-rs = { version = "2.0.1", default-features = false, features = ["sync"] } +bincode = { version = "2.0.1", default-features = false, features = ["alloc", "derive"] } +num-traits = { version = "0.2", default-features = false } +bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } + +# an rwasm legacy dependency +#rwasm-legacy = { path = "./legacy" } + +[dev-dependencies] +rand = "0.9.1" + +[features] +default = ["std"] +std = [ + "num_enum/std", + "bytes/std", + "bincode/std", + "num-traits/std", + "bitvec/std", +] +debug-print = [] diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index 6f3157927..527beabc8 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -4,11 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] -rwasm-executor = { path = "../executor" } -rwasm = { path = "../rwasm" } +rwasm = { path = ".." } +rwasm-legacy = { path = "../legacy" } anyhow = "1.0.71" -wast = "62.0.0" +wast = "=62.0.0" [features] default = ["e2e"] -e2e = ["rwasm-executor/e2e", "rwasm/e2e"] +e2e = ["rwasm-legacy/e2e"] +debug-print = ["rwasm/debug-print"] diff --git a/e2e/src/context.rs b/e2e/src/context.rs index d7fb77be7..8ef4a44f6 100644 --- a/e2e/src/context.rs +++ b/e2e/src/context.rs @@ -1,4 +1,4 @@ -use super::{TestDescriptor, TestError, TestProfile, TestSpan}; +use super::{TestDescriptor, TestError, TestProfile, TestSpan, ENABLE_32_BIT_TRANSLATOR}; use crate::handler::{ testing_context_syscall_handler, TestingContext, @@ -13,40 +13,19 @@ use crate::handler::{ }; use anyhow::Result; use rwasm::{ - core::{ImportLinker, ImportLinkerEntity, ValueType, F32, F64}, - engine::{ - bytecode::{BlockFuel, Instruction}, - RwasmConfig, - StateRouterConfig, - }, - func::FuncIdx, - module::{ImportName, Imported}, - rwasm::{BinaryFormat, BinaryFormatWriter, RwasmModule}, - value::split_i64_to_i32, - Config, - Engine, - Extern, - Func, - FuncType, - Global, - Linker, - Memory, - MemoryType, - Module, - Mutability, - Store, - Table, - TableType, - Value, -}; -use rwasm_executor::{ make_instruction_table, Caller, ExecutorConfig, InstructionTable, RwasmError, RwasmExecutor, - RwasmModule2, + RwasmModule as RwasmModule2, + RwasmModule, + Value, +}; +use rwasm_legacy::{ + core::{ImportLinker, ImportLinkerEntity, ValueType}, + module::ImportName, }; use std::{cell::RefCell, collections::HashMap, hash::Hash, rc::Rc, sync::Arc}; use wast::token::{Id, Span}; @@ -54,24 +33,13 @@ use wast::token::{Id, Span}; type TestingRwasmExecutor = RwasmExecutor; type Instance = Rc>; -const TESTING_INSTRUCTION_TABLE: InstructionTable = make_instruction_table(); - /// The context of a single Wasm test spec suite run. pub struct TestContext<'a> { - /// The wasmi config - config: Config, - /// The `wasmi` engine used for executing functions used during the test. - engine: Engine, - /// The linker for linking together Wasm test modules. - linker: Linker<()>, - /// The store to hold all runtime data during the test. - store: Store<()>, /// The list of all encountered Wasm modules belonging to the test. modules: Vec, /// The list of all instantiated modules. instances: HashMap, - import_linker: ImportLinker, - extern_types: HashMap, + extern_types: HashMap, extern_state: HashMap, /// The last touched module instance. last_instance: Option, @@ -87,19 +55,21 @@ pub struct TestContext<'a> { impl<'a> TestContext<'a> { /// Creates a new [`TestContext`] with the given [`TestDescriptor`]. - pub fn new(descriptor: &'a TestDescriptor, config: Config) -> Self { - let engine = Engine::new(&config); - let mut linker = Linker::new(&engine); - let mut store = Store::new(&engine, ()); - let default_memory = Memory::new(&mut store, MemoryType::new(1, Some(2)).unwrap()).unwrap(); - let default_table = Table::new( - &mut store, - TableType::new(ValueType::FuncRef, 10, Some(20)), - Value::default(ValueType::FuncRef), - ) - .unwrap(); + pub fn new(descriptor: &'a TestDescriptor) -> Self { + TestContext { + modules: Vec::new(), + instances: HashMap::new(), + extern_types: Default::default(), + extern_state: Default::default(), + last_instance: None, + profile: TestProfile::default(), + results: Vec::new(), + descriptor, + } + } - let import_linker = ImportLinker::from([ + pub fn import_linker() -> ImportLinker { + ImportLinker::from([ ( ImportName::new("spectest", "print"), ImportLinkerEntity { @@ -123,7 +93,11 @@ impl<'a> TestContext<'a> { ImportLinkerEntity { func_idx: FUNC_PRINT_I64, fuel_procedure: &[], - params: &[ValueType::I64], + params: if ENABLE_32_BIT_TRANSLATOR { + &[ValueType::I32; 2] + } else { + &[ValueType::I64] + }, result: &[], }, ), @@ -141,7 +115,11 @@ impl<'a> TestContext<'a> { ImportLinkerEntity { func_idx: FUNC_PRINT_F64, fuel_procedure: &[], - params: &[ValueType::F64], + params: if ENABLE_32_BIT_TRANSLATOR { + &[ValueType::F32; 2] + } else { + &[ValueType::F64] + }, result: &[], }, ), @@ -159,27 +137,20 @@ impl<'a> TestContext<'a> { ImportLinkerEntity { func_idx: FUNC_PRINT_I64_F64, fuel_procedure: &[], - params: &[ValueType::I64, ValueType::F64], + params: if ENABLE_32_BIT_TRANSLATOR { + &[ + ValueType::I32, + ValueType::I32, + ValueType::F32, + ValueType::F32, + ] + } else { + &[ValueType::I64, ValueType::F64] + }, result: &[], }, ), - ]); - - TestContext { - config, - engine, - linker, - store, - modules: Vec::new(), - instances: HashMap::new(), - import_linker, - extern_types: Default::default(), - extern_state: Default::default(), - last_instance: None, - profile: TestProfile::default(), - results: Vec::new(), - descriptor, - } + ]) } } @@ -194,21 +165,6 @@ impl TestContext<'_> { self.descriptor.spanned(span) } - /// Returns the [`Engine`] of the [`TestContext`]. - fn engine(&self) -> &Engine { - &self.engine - } - - /// Returns a shared reference to the underlying [`Store`]. - pub fn store(&self) -> &Store<()> { - &self.store - } - - /// Returns an exclusive reference to the underlying [`Store`]. - pub fn store_mut(&mut self) -> &mut Store<()> { - &mut self.store - } - /// Returns an exclusive reference to the test profile. pub fn profile(&mut self) -> &mut TestProfile { &mut self.profile @@ -223,8 +179,6 @@ impl TestContext<'_> { &mut self, mut module: wast::core::Module, ) -> Result<(), TestError> { - println!("\n --- creating module ---"); - // println!("{:?}", module); let module_name = module.id.map(|id| id.name()); let wasm = module.encode().unwrap_or_else(|error| { panic!( @@ -234,9 +188,25 @@ impl TestContext<'_> { ) }); + let mut config = rwasm_legacy::Config::default(); + config + .wasm_mutable_global(false) + .wasm_saturating_float_to_int(false) + .wasm_sign_extension(false) + .wasm_multi_value(false) + .wasm_mutable_global(true) + .wasm_saturating_float_to_int(true) + .wasm_sign_extension(true) + .wasm_multi_value(true) + .wasm_bulk_memory(true) + .wasm_reference_types(true) + .wasm_tail_call(true) + .wasm_extended_const(true); + // extract all exports first to calculate rwasm config let rwasm_config = { - let wasm_module = Module::new(self.engine(), &wasm[..])?; + let engine = rwasm_legacy::Engine::new(&config); + let wasm_module = rwasm_legacy::Module::new(&engine, &wasm[..])?; let mut states = Vec::<(String, u32)>::new(); for (k, v) in wasm_module.exports.iter() { let func_idx = v.into_func_idx(); @@ -251,44 +221,27 @@ impl TestContext<'_> { self.extern_state.insert(k.to_string(), state_value); states.push((k.to_string(), state_value)); } - // println!("states={:?}", states); - RwasmConfig { - state_router: Some(StateRouterConfig { + rwasm_legacy::engine::RwasmConfig { + state_router: Some(rwasm_legacy::engine::StateRouterConfig { states: states.into(), - opcode: Instruction::Call(u32::MAX.into()), + opcode: rwasm_legacy::engine::bytecode::Instruction::Call(u32::MAX.into()), }), entrypoint_name: None, - import_linker: Some(self.import_linker.clone()), + import_linker: Some(Self::import_linker()), wrap_import_functions: true, translate_drop_keep: false, allow_malformed_entrypoint_func_type: true, - use_32bit_mode: self.config.get_i32_translator(), + use_32bit_mode: ENABLE_32_BIT_TRANSLATOR, builtins_consume_fuel: false, } }; - let mut config = Config::default(); - config - .wasm_mutable_global(false) - .wasm_saturating_float_to_int(false) - .wasm_sign_extension(false) - .wasm_multi_value(false) - .wasm_mutable_global(true) - .wasm_saturating_float_to_int(true) - .wasm_sign_extension(true) - .wasm_multi_value(true) - .wasm_bulk_memory(true) - .wasm_reference_types(true) - .wasm_tail_call(true) - .wasm_extended_const(true); config.rwasm_config(rwasm_config); - - let engine = Engine::new(&config); - let wasm_module = Module::new(&engine, &wasm[..])?; - - let rwasm_module = RwasmModule::from_module(&wasm_module); - // encode and decode rwasm module (to tests encoding/decoding flow) + let engine = rwasm_legacy::Engine::new(&config); + let wasm_module = rwasm_legacy::Module::new(&engine, &wasm[..])?; + let rwasm_module = rwasm_legacy::rwasm::RwasmModule::from_module(&wasm_module); let mut encoded_rwasm_module = Vec::new(); + use rwasm_legacy::rwasm::BinaryFormat; rwasm_module .write_binary_to_vec(&mut encoded_rwasm_module) .unwrap(); @@ -297,29 +250,14 @@ impl TestContext<'_> { println!(); #[allow(unused)] fn trace_rwasm(rwasm_bytecode: &[u8]) { - let rwasm_module = RwasmModule::new(rwasm_bytecode).unwrap(); - let mut func_length = 0usize; - let mut expected_func_length = rwasm_module - .func_section - .first() - .copied() - .unwrap_or(u32::MAX) as usize; + let rwasm_module = RwasmModule::new(rwasm_bytecode); let mut func_index = 0usize; println!("\n -- function #{} -- ", func_index); for (i, instr) in rwasm_module.code_section.instr.iter().enumerate() { println!("{:02}: {:?}", i, instr); - func_length += 1; - if func_length == expected_func_length { + if rwasm_module.func_section.contains(&(i as u32 + 1)) { func_index += 1; - expected_func_length = rwasm_module - .func_section - .get(func_index) - .copied() - .unwrap_or(u32::MAX) as usize; - if expected_func_length != u32::MAX as usize { - println!("\n -- function #{} -- ", func_index); - } - func_length = 0; + println!("\n -- function #{} -- ", func_index); } } println!("\n") @@ -335,13 +273,7 @@ impl TestContext<'_> { executor.set_syscall_handler(testing_context_syscall_handler); executor.context_mut().state = ENTRYPOINT_FUNC_IDX; println!(" --- entrypoint ---"); - let exit_code = executor.run().map_err(|err| { - let trap_code = match err { - RwasmError::TrapCode(trap_code) => trap_code, - _ => unreachable!("not possible error: {:?}", err), - }; - TestError::Wasmi(rwasm::Error::Trap(trap_code.into())) - })?; + let exit_code = executor.run().map_err(|err| TestError::Rwasm(err))?; assert_eq!(exit_code, 0); println!(); @@ -446,12 +378,12 @@ impl TestContext<'_> { let mut caller = Caller::new(&mut instance); let flat_args: Vec; - let args = if self.store.engine().config().get_i32_translator() { + let args = if ENABLE_32_BIT_TRANSLATOR { flat_args = args .iter() .cloned() .flat_map(|v| match v { - Value::I64(v) => split_i64_to_i32(v) + Value::I64(v) => rwasm_legacy::value::split_i64_to_i32(v) .into_iter() .map(|v| Value::I32(v)) .collect(), @@ -469,13 +401,7 @@ impl TestContext<'_> { // change function state for router instance.context_mut().state = func_state; - let exit_code = instance.run().map_err(|err| { - let trap_code = match err { - RwasmError::TrapCode(trap_code) => trap_code, - _ => unreachable!("not possible error: {:?}", err), - }; - TestError::Wasmi(rwasm::Error::Trap(trap_code.into())) - })?; + let exit_code = instance.run().map_err(|err| TestError::Rwasm(err))?; // copy results let func_type = self.extern_types.get(func_name).unwrap(); let len_results = func_type.results().len(); @@ -514,8 +440,4 @@ impl TestContext<'_> { // to cover other functionality Ok(Value::I64(42)) } - - pub fn get_config(&self) -> &Config { - &self.config - } } diff --git a/e2e/src/error.rs b/e2e/src/error.rs index eb77e0929..99da7a6c2 100644 --- a/e2e/src/error.rs +++ b/e2e/src/error.rs @@ -1,11 +1,12 @@ -use rwasm::Error as WasmiError; -use rwasm_executor::RwasmError; +use rwasm::RwasmError; +use rwasm_legacy::Error as WasmiError; use std::{error::Error, fmt, fmt::Display}; /// Errors that may occur upon Wasm spec test suite execution. #[derive(Debug)] pub enum TestError { Wasmi(WasmiError), + Rwasm(RwasmError), InstanceNotRegistered { name: String, }, @@ -47,6 +48,7 @@ impl Display for TestError { ) } Self::Wasmi(wasmi_error) => Display::fmt(wasmi_error, f), + Self::Rwasm(rwasm_error) => Display::fmt(rwasm_error, f), } } } diff --git a/e2e/src/handler.rs b/e2e/src/handler.rs index 000c31094..2de8ecf48 100644 --- a/e2e/src/handler.rs +++ b/e2e/src/handler.rs @@ -1,4 +1,4 @@ -use rwasm_executor::{Caller, RwasmError, SyscallHandler}; +use rwasm::{Caller, RwasmError, SyscallHandler}; pub const ENTRYPOINT_FUNC_IDX: u32 = u32::MAX; @@ -56,7 +56,7 @@ pub(crate) fn testing_context_syscall_handler( Ok(()) } u32::MAX => { - // yeah dirty, but this is how we remember the program counter to reset, + // yeah, dirty, but this is how we remember the program counter to reset, // since we're 100% sure the function is called using `Call` // that we can safely deduct 1 from PC (for `ReturnCall` we need to deduct 2) caller.context_mut().program_counter = caller.vm().program_counter() - 1; diff --git a/e2e/src/lib.rs b/e2e/src/lib.rs index d510c8515..231edce91 100644 --- a/e2e/src/lib.rs +++ b/e2e/src/lib.rs @@ -13,12 +13,11 @@ use self::{ error::TestError, profile::TestProfile, }; -use ::rwasm::{engine::RwasmConfig, Config}; +use ::rwasm_legacy::{engine::RwasmConfig, Config}; macro_rules! define_tests { ( let folder = $test_folder:literal; - let config = $get_config:expr; let runner = $runner_fn:path; $( $(#[$attr:meta])* fn $test_name:ident($file_name:expr); )* @@ -27,7 +26,7 @@ macro_rules! define_tests { #[test] $( #[$attr] )* fn $test_name() { - $runner_fn(&format!("{}/{}", $test_folder, $file_name), $get_config) + $runner_fn(&format!("{}/{}", $test_folder, $file_name)) } )* }; @@ -35,14 +34,12 @@ macro_rules! define_tests { macro_rules! define_spec_tests { ( - let config = $get_config:expr; let runner = $runner_fn:path; $( $(#[$attr:meta])* fn $test_name:ident($file_name:expr); )* ) => { define_tests! { let folder = "testsuite"; - let config = $get_config; let runner = $runner_fn; $( @@ -53,63 +50,19 @@ macro_rules! define_spec_tests { }; } -/// Create a [`Config`] for the Wasm MVP feature set. -fn mvp_config() -> Config { - let mut config = Config::default(); - config - .wasm_mutable_global(false) - .wasm_saturating_float_to_int(false) - .wasm_sign_extension(false) - .wasm_multi_value(false); - config -} - -/// Create a [`Config`] with all Wasm feature supported by `wasmi` enabled. -/// -/// # Note -/// -/// The Wasm MVP has no Wasm proposals enabled. -fn make_config(rwasm_mode: bool) -> Config { - let mut config = mvp_config(); - // We have to enable the `mutable-global` Wasm proposal because - // it seems that the entire Wasm spec test suite is already built - // on the basis of its semantics. - config - .wasm_mutable_global(true) - .wasm_saturating_float_to_int(true) - .wasm_sign_extension(true) - .wasm_multi_value(true) - .wasm_bulk_memory(true) - .wasm_reference_types(true) - .wasm_tail_call(true) - .wasm_extended_const(false); - if rwasm_mode { - config.rwasm_config(RwasmConfig { - state_router: None, - entrypoint_name: None, - import_linker: None, - wrap_import_functions: false, - translate_drop_keep: true, - allow_malformed_entrypoint_func_type: true, - use_32bit_mode: false, - builtins_consume_fuel: false, - }); - } - config -} +pub(crate) const ENABLE_32_BIT_TRANSLATOR: bool = false; define_spec_tests! { - let config = make_config(true); let runner = run::run_wasm_spec_test; fn wasm_address("address"); - fn wasm_align("align"); //br_table + fn wasm_align("align"); fn wasm_binary_leb128("binary-leb128"); fn wasm_binary("binary"); fn wasm_block("block"); fn wasm_br("br"); fn wasm_br_if("br_if"); - fn wasm_br_table("br_table"); // br_table + fn wasm_br_table("br_table"); fn wasm_bulk("bulk"); fn wasm_call("call"); fn wasm_call_indirect("call_indirect"); diff --git a/e2e/src/run.rs b/e2e/src/run.rs index 71bd81376..f1418b209 100644 --- a/e2e/src/run.rs +++ b/e2e/src/run.rs @@ -1,14 +1,7 @@ use super::{error::TestError, TestContext, TestDescriptor}; +use crate::ENABLE_32_BIT_TRANSLATOR; use anyhow::Result; -use rwasm::{ - core::{F32, F64}, - value::split_i64_to_i32, - Config, - ExternRef, - FuncRef, - Instance, - Value, -}; +use rwasm::{split_i64_to_i32, ExternRef, FuncRef, Value, F32, F64}; use wast::{ core::{HeapType, NanPattern, WastRetCore}, lexer::Lexer, @@ -24,9 +17,9 @@ use wast::{ }; /// Runs the Wasm test spec identified by the given name. -pub fn run_wasm_spec_test(name: &str, config: Config) { +pub fn run_wasm_spec_test(name: &str) { let test = TestDescriptor::new(name); - let mut context = TestContext::new(&test, config); + let mut context = TestContext::new(&test); let mut lexer = Lexer::new(test.file()); lexer.allow_confusing_unicode(true); @@ -218,6 +211,17 @@ fn assert_trap(test_context: &TestContext, span: Span, error: TestError, message error, ); } + TestError::Rwasm(error) => { + assert!( + error.to_string().contains(message), + "{}: the directive trapped as expected but with an unexpected message\n\ + expected: {},\n\ + encountered: {}", + test_context.spanned(span), + message, + error, + ); + } unexpected => panic!( "{}: encountered unexpected error: \n\t\ found: '{unexpected}'\n\t\ @@ -229,8 +233,7 @@ fn assert_trap(test_context: &TestContext, span: Span, error: TestError, message /// Asserts that `results` match the `expected` values. fn assert_results(context: &TestContext, span: Span, results: &[Value], expected: &[WastRet]) { - let config = context.get_config(); - if config.get_i32_translator() { + if ENABLE_32_BIT_TRANSLATOR { assert_eq!( results.len(), expected.len() @@ -265,7 +268,7 @@ fn assert_results(context: &TestContext, span: Span, results: &[Value], expected // in rWASM we support only 64 bit globals, but technically both these types are having // 64 bit representation, so there is no diff, and we can safely compare them (Value::I32(result), WastRetCore::I64(expected)) => { - if config.get_i32_translator() { + if ENABLE_32_BIT_TRANSLATOR { let low = result; let high = &results[i + shift + 1] .i32() @@ -312,12 +315,8 @@ fn assert_results(context: &TestContext, span: Span, results: &[Value], expected assert!(externref.is_null()); } (Value::ExternRef(externref), WastRetCore::RefExtern(expected)) => { - let value = externref - .data(context.store()) - .expect("unexpected null element") - .downcast_ref::() - .expect("unexpected non-u32 data"); - assert_eq!(value, expected); + let value = externref.resolve_index(); + assert_eq!(value, *expected); } (result, expected) => panic!( "{}: encountered mismatch in evaluation. expected {:?} but found {:?}", @@ -403,7 +402,7 @@ fn execute_wast_invoke( let mut args = >::new(); for arg in invoke.args { let value = match arg { - wast::WastArg::Core(arg) => value(context.store_mut(), &arg).unwrap_or_else(|| { + wast::WastArg::Core(arg) => value(&arg).unwrap_or_else(|| { panic!( "{}: encountered unsupported WastArgCore argument: {arg:?}", context.spanned(span) @@ -422,7 +421,7 @@ fn execute_wast_invoke( } /// Converts the [`WastArgCore`][`wast::core::WastArgCore`] into a [`wasmi::Value`] if possible. -fn value(ctx: &mut rwasm::Store<()>, value: &wast::core::WastArgCore) -> Option { +fn value(value: &wast::core::WastArgCore) -> Option { Some(match value { wast::core::WastArgCore::I32(arg) => Value::I32(*arg), wast::core::WastArgCore::I64(arg) => Value::I64(*arg), @@ -430,7 +429,7 @@ fn value(ctx: &mut rwasm::Store<()>, value: &wast::core::WastArgCore) -> Option< wast::core::WastArgCore::F64(arg) => Value::F64(F64::from_bits(arg.bits)), wast::core::WastArgCore::RefNull(HeapType::Func) => Value::FuncRef(FuncRef::null()), wast::core::WastArgCore::RefNull(HeapType::Extern) => Value::ExternRef(ExternRef::null()), - wast::core::WastArgCore::RefExtern(value) => Value::ExternRef(ExternRef::new(ctx, *value)), + wast::core::WastArgCore::RefExtern(value) => Value::ExternRef(ExternRef::new(*value)), _ => return None, }) } diff --git a/executor/Cargo.toml b/executor/Cargo.toml deleted file mode 100644 index 60dd3ae86..000000000 --- a/executor/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "rwasm-executor" -version = "0.1.0" -authors.workspace = true -repository.workspace = true -edition.workspace = true -readme.workspace = true -license.workspace = true -keywords.workspace = true -categories.workspace = true - -[dependencies] -hashbrown = { workspace = true } -tiny-keccak = { workspace = true } -byteorder = { workspace = true } -num_enum = { workspace = true } -paste = { workspace = true } -bytes = { workspace = true } - -rwasm = { path = "../rwasm" } - -[features] -default = ["std", "bytes/std"] -std = [] -tracer = [] -e2e = [] -debug-print = ["std"] -floats = [] diff --git a/executor/src/binary_format/drop_keep.rs b/executor/src/binary_format/drop_keep.rs deleted file mode 100644 index 72006fe13..000000000 --- a/executor/src/binary_format/drop_keep.rs +++ /dev/null @@ -1,28 +0,0 @@ -use crate::binary_format::{ - BinaryFormat, - BinaryFormatError, - BinaryFormatReader, - BinaryFormatWriter, -}; -use rwasm::engine::DropKeep; - -impl<'a> BinaryFormat<'a> for DropKeep { - type SelfType = DropKeep; - - fn encoded_length(&self) -> usize { - 8 - } - - fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result { - let mut n = 0; - n += sink.write_u32_le(self.drop() as u32)?; - n += sink.write_u32_le(self.keep() as u32)?; - Ok(n) - } - - fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result { - let drop = sink.read_u32_le()?; - let keep = sink.read_u32_le()?; - Ok(DropKeep::new(drop as usize, keep as usize).unwrap()) - } -} diff --git a/executor/src/binary_format/instruction.rs b/executor/src/binary_format/instruction.rs deleted file mode 100644 index 4af69402e..000000000 --- a/executor/src/binary_format/instruction.rs +++ /dev/null @@ -1,73 +0,0 @@ -use crate::{ - binary_format::{BinaryFormat, BinaryFormatError, BinaryFormatReader}, - module::{Instruction, InstructionData}, -}; -use rwasm::{ - core::UntypedValue, - engine::{ - bytecode::{ - AddressOffset, - BlockFuel, - BranchOffset, - BranchTableTargets, - DataSegmentIdx, - ElementSegmentIdx, - FuncIdx, - GlobalIdx, - LocalDepth, - SignatureIdx, - TableIdx, - }, - CompiledFunc, - DropKeep, - }, -}; - -pub fn decode_rwasm_instruction( - sink: &mut BinaryFormatReader, -) -> Result<(Instruction, InstructionData), BinaryFormatError> { - use Instruction::*; - let byte = sink.read_u8()?; - let instruction = - Instruction::try_from(byte).map_err(|_| BinaryFormatError::IllegalOpcode(byte))?; - let data = match instruction { - LocalGet | LocalSet | LocalTee => { - InstructionData::LocalDepth(LocalDepth::read_binary(sink)?) - } - Br | BrIfEqz | BrIfNez | BrAdjust | BrAdjustIfNez => { - InstructionData::BranchOffset(BranchOffset::read_binary(sink)?) - } - BrTable => InstructionData::BranchTableTargets(BranchTableTargets::read_binary(sink)?), - ConsumeFuel => InstructionData::BlockFuel(BlockFuel::read_binary(sink)?), - Return | ReturnIfNez => InstructionData::DropKeep(DropKeep::read_binary(sink)?), - ReturnCallInternal | CallInternal => { - InstructionData::CompiledFunc(CompiledFunc::read_binary(sink)?) - } - ReturnCall | Call | RefFunc => InstructionData::FuncIdx(FuncIdx::read_binary(sink)?), - ReturnCallIndirect | CallIndirect | SignatureCheck => { - InstructionData::SignatureIdx(SignatureIdx::read_binary(sink)?) - } - GlobalGet | GlobalSet => InstructionData::GlobalIdx(GlobalIdx::read_binary(sink)?), - I32Load | I64Load | F32Load | F64Load | I32Load8S | I32Load8U | I32Load16S | I32Load16U - | I64Load8S | I64Load8U | I64Load16S | I64Load16U | I64Load32S | I64Load32U | I32Store - | I64Store | F32Store | F64Store | I32Store8 | I32Store16 | I64Store8 | I64Store16 - | I64Store32 => InstructionData::AddressOffset(AddressOffset::read_binary(sink)?), - MemoryInit | DataDrop => { - InstructionData::DataSegmentIdx(DataSegmentIdx::read_binary(sink)?) - } - TableSize | TableGrow | TableFill | TableGet | TableSet | TableCopy => { - InstructionData::TableIdx(TableIdx::read_binary(sink)?) - } - TableInit | ElemDrop => { - InstructionData::ElementSegmentIdx(ElementSegmentIdx::read_binary(sink)?) - } - I32Const | I64Const | F32Const | F64Const => { - InstructionData::UntypedValue(UntypedValue::read_binary(sink)?) - } - StackAlloc => InstructionData::StackAlloc { - max_stack_height: u32::read_binary(sink)?, - }, - _ => InstructionData::EmptyData, - }; - Ok((instruction, data)) -} diff --git a/executor/src/binary_format/module.rs b/executor/src/binary_format/module.rs deleted file mode 100644 index aba15984c..000000000 --- a/executor/src/binary_format/module.rs +++ /dev/null @@ -1,118 +0,0 @@ -use crate::{ - binary_format::{instruction::decode_rwasm_instruction, BinaryFormatError, BinaryFormatReader}, - module::RwasmModule2, -}; - -/// Rwasm magic bytes 0xef52 -const RWASM_MAGIC_BYTE_0: u8 = 0xef; -const RWASM_MAGIC_BYTE_1: u8 = 0x52; - -/// Rwasm binary version that is equal to 'R' symbol (0x52 in hex) -const RWASM_VERSION_V1: u8 = 0x01; - -/// Sections that are presented in Rwasm binary: -/// - code -/// - memory -/// - decl -/// - element -const RWASM_SECTION_CODE: u8 = 0x01; -const RWASM_SECTION_MEMORY: u8 = 0x02; -const RWASM_SECTION_FUNC: u8 = 0x03; -const RWASM_SECTION_ELEMENT: u8 = 0x04; -const RWASM_SECTION_END: u8 = 0x00; - -pub fn decode_rwasm_module( - sink: &mut BinaryFormatReader, -) -> Result { - let mut result = RwasmModule2::default(); - // magic prefix (0xef 0x52) - if sink.read_u8()? != RWASM_MAGIC_BYTE_0 || sink.read_u8()? != RWASM_MAGIC_BYTE_1 { - return Err(BinaryFormatError::MalformedWasmModule); - } - // version check - let version = sink.read_u8()?; - if version != RWASM_VERSION_V1 { - return Err(BinaryFormatError::MalformedWasmModule); - } - // code section header - sink.assert_u8(RWASM_SECTION_CODE)?; - let code_section_length = sink.read_u32_le()?; - // memory section header - sink.assert_u8(RWASM_SECTION_MEMORY)?; - let memory_section_length = sink.read_u32_le()?; - // decl section header - sink.assert_u8(RWASM_SECTION_FUNC)?; - let decl_section_length = sink.read_u32_le()?; - // element section header - sink.assert_u8(RWASM_SECTION_ELEMENT)?; - let element_section_length = sink.read_u32_le()?; - // section terminator - sink.assert_u8(RWASM_SECTION_END)?; - // read the code section - let mut code_section_sink = sink.limit_with(code_section_length as usize); - while !code_section_sink.is_empty() { - let (instr, data) = decode_rwasm_instruction(&mut code_section_sink)?; - result.code_section.push(instr); - result.instr_data.push(data); - } - sink.pos += code_section_length as usize; - // read the memory section - { - let mut memory_section_sink = sink.limit_with(memory_section_length as usize); - result - .memory_section - .resize(memory_section_length as usize, 0u8); - memory_section_sink.read_bytes(&mut result.memory_section)?; - sink.pos += memory_section_length as usize; - } - // read decl section - { - let mut decl_section_sink = sink.limit_with(decl_section_length as usize); - while !decl_section_sink.is_empty() { - result.func_section.push(decl_section_sink.read_u32_le()?); - } - sink.pos += decl_section_length as usize; - } - // read element section - { - let mut element_section_sink = sink.limit_with(element_section_length as usize); - while !element_section_sink.is_empty() { - result - .element_section - .push(element_section_sink.read_u32_le()?); - } - sink.pos += element_section_length as usize; - } - // return the final module - Ok(result) -} - -#[cfg(test)] -mod tests { - use rwasm::{ - instruction_set, - rwasm::{BinaryFormat, RwasmModule}, - }; - - #[test] - fn test_module_encoding() { - let module = RwasmModule { - code_section: instruction_set! { - I32Const(100) - I32Const(20) - I32Add - I32Const(3) - I32Add - Drop - }, - memory_section: Default::default(), - func_section: vec![1, 2, 3], - element_section: vec![5, 6, 7, 8, 9], - }; - let mut encoded_data = Vec::new(); - module.write_binary_to_vec(&mut encoded_data).unwrap(); - assert_eq!(module.encoded_length(), encoded_data.len()); - let module2 = RwasmModule::read_from_slice(&encoded_data).unwrap(); - assert_eq!(module, module2); - } -} diff --git a/executor/src/binary_format/number.rs b/executor/src/binary_format/number.rs deleted file mode 100644 index e72a4f283..000000000 --- a/executor/src/binary_format/number.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::binary_format::{ - reader_writer::{BinaryFormatReader, BinaryFormatWriter}, - BinaryFormat, - BinaryFormatError, -}; - -macro_rules! impl_primitive_format { - ($ty:ty, $write_method:ident, $read_method:ident) => { - impl<'a> BinaryFormat<'a> for $ty { - type SelfType = $ty; - fn encoded_length(&self) -> usize { - core::mem::size_of::<$ty>() - } - fn write_binary( - &self, - sink: &mut BinaryFormatWriter<'a>, - ) -> Result { - sink.$write_method(*self) - } - fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result<$ty, BinaryFormatError> { - sink.$read_method() - } - } - }; -} - -impl_primitive_format!(u16, write_u16_le, read_u16_le); -impl_primitive_format!(i16, write_i16_le, read_i16_le); -impl_primitive_format!(u32, write_u32_le, read_u32_le); -impl_primitive_format!(i32, write_i32_le, read_i32_le); -impl_primitive_format!(u64, write_u64_le, read_u64_le); -impl_primitive_format!(i64, write_i64_le, read_i64_le); diff --git a/executor/src/binary_format/reader_writer.rs b/executor/src/binary_format/reader_writer.rs deleted file mode 100644 index 770c69c5b..000000000 --- a/executor/src/binary_format/reader_writer.rs +++ /dev/null @@ -1,269 +0,0 @@ -use crate::binary_format::BinaryFormatError; -use alloc::vec::Vec; -use byteorder::{BigEndian, ByteOrder, LittleEndian}; - -pub struct BinaryFormatWriter<'a> { - pub sink: &'a mut [u8], - pub pos: usize, -} - -impl<'a> BinaryFormatWriter<'a> { - pub fn new(sink: &'a mut [u8]) -> Self { - Self { sink, pos: 0 } - } - - pub fn write_u8(&mut self, value: u8) -> Result { - let n = self.require(1)?; - self.sink[self.pos] = value; - self.skip(n) - } - - pub fn write_u16_be(&mut self, value: u16) -> Result { - let n = self.require(2)?; - BigEndian::write_u16(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_u16_le(&mut self, value: u16) -> Result { - let n = self.require(2)?; - LittleEndian::write_u16(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_i16_be(&mut self, value: i16) -> Result { - let n = self.require(2)?; - BigEndian::write_i16(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_i16_le(&mut self, value: i16) -> Result { - let n = self.require(2)?; - LittleEndian::write_i16(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_u32_be(&mut self, value: u32) -> Result { - let n = self.require(4)?; - BigEndian::write_u32(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_u32_le(&mut self, value: u32) -> Result { - let n = self.require(4)?; - LittleEndian::write_u32(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_i32_be(&mut self, value: i32) -> Result { - let n = self.require(4)?; - BigEndian::write_i32(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_i32_le(&mut self, value: i32) -> Result { - let n = self.require(4)?; - LittleEndian::write_i32(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_u64_be(&mut self, value: u64) -> Result { - let n = self.require(8)?; - BigEndian::write_u64(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_u64_le(&mut self, value: u64) -> Result { - let n = self.require(8)?; - LittleEndian::write_u64(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_i64_be(&mut self, value: i64) -> Result { - let n = self.require(8)?; - BigEndian::write_i64(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_i64_le(&mut self, value: i64) -> Result { - let n = self.require(8)?; - LittleEndian::write_i64(&mut self.sink[self.pos..], value); - self.skip(n) - } - - pub fn write_bytes(&mut self, bytes: &[u8]) -> Result { - let n = self.require(bytes.len())?; - self.sink[self.pos..(self.pos + n)].copy_from_slice(bytes); - self.skip(n) - } - - fn require(&self, n: usize) -> Result { - if self.sink.len() < self.pos + n { - Err(BinaryFormatError::NeedMore(self.pos + n - self.sink.len())) - } else { - Ok(n) - } - } - - pub fn reset(&mut self) { - self.pos = 0; - } - - pub fn pos(&self) -> usize { - self.pos - } - - fn skip(&mut self, n: usize) -> Result { - assert!(self.sink.len() >= self.pos + n); - self.pos += n; - Ok(n) - } - - pub fn to_vec(&self) -> Vec { - self.sink[0..self.pos].to_vec() - } -} - -pub struct BinaryFormatReader<'a> { - pub sink: &'a [u8], - pub pos: usize, -} - -impl<'a> BinaryFormatReader<'a> { - pub fn new(sink: &'a [u8]) -> Self { - Self { sink, pos: 0 } - } - - pub fn limit_with(&self, length: usize) -> Self { - Self { - sink: &self.sink[self.pos..(self.pos + length)], - pos: 0, - } - } - - pub fn is_empty(&self) -> bool { - self.pos >= self.sink.len() - } - - pub fn pos(&self) -> usize { - self.pos - } - - pub fn assert_u8(&mut self, value: u8) -> Result { - if self.read_u8()? != value { - Err(BinaryFormatError::MalformedWasmModule) - } else { - Ok(1) - } - } - - pub fn read_u8(&mut self) -> Result { - self.require(1)?; - let result = self.sink[self.pos]; - self.skip(1)?; - Ok(result) - } - - pub fn read_u16_be(&mut self) -> Result { - self.require(2)?; - let result = BigEndian::read_u16(&self.sink[self.pos..]); - self.skip(2)?; - Ok(result) - } - - pub fn read_u16_le(&mut self) -> Result { - self.require(2)?; - let result = LittleEndian::read_u16(&self.sink[self.pos..]); - self.skip(2)?; - Ok(result) - } - - pub fn read_i16_be(&mut self) -> Result { - self.require(2)?; - let result = BigEndian::read_i16(&self.sink[self.pos..]); - self.skip(2)?; - Ok(result) - } - - pub fn read_i16_le(&mut self) -> Result { - self.require(2)?; - let result = LittleEndian::read_i16(&self.sink[self.pos..]); - self.skip(2)?; - Ok(result) - } - - pub fn read_u32_be(&mut self) -> Result { - self.require(4)?; - let result = BigEndian::read_u32(&self.sink[self.pos..]); - self.skip(4)?; - Ok(result) - } - - pub fn read_u32_le(&mut self) -> Result { - self.require(4)?; - let result = LittleEndian::read_u32(&self.sink[self.pos..]); - self.skip(4)?; - Ok(result) - } - - pub fn read_i32_be(&mut self) -> Result { - self.require(4)?; - let result = BigEndian::read_i32(&self.sink[self.pos..]); - self.skip(4)?; - Ok(result) - } - - pub fn read_i32_le(&mut self) -> Result { - self.require(4)?; - let result = LittleEndian::read_i32(&self.sink[self.pos..]); - self.skip(4)?; - Ok(result) - } - - pub fn read_u64_be(&mut self) -> Result { - self.require(8)?; - let result = BigEndian::read_u64(&self.sink[self.pos..]); - self.skip(8)?; - Ok(result) - } - - pub fn read_u64_le(&mut self) -> Result { - self.require(8)?; - let result = LittleEndian::read_u64(&self.sink[self.pos..]); - self.skip(8)?; - Ok(result) - } - - pub fn read_i64_be(&mut self) -> Result { - self.require(8)?; - let result = BigEndian::read_i64(&self.sink[self.pos..]); - self.skip(8)?; - Ok(result) - } - - pub fn read_i64_le(&mut self) -> Result { - self.require(8)?; - let result = LittleEndian::read_i64(&self.sink[self.pos..]); - self.skip(8)?; - Ok(result) - } - - pub fn read_bytes(&mut self, bytes: &mut [u8]) -> Result<(), BinaryFormatError> { - self.require(bytes.len())?; - bytes.copy_from_slice(&self.sink[self.pos..(self.pos + bytes.len())]); - self.skip(bytes.len()) - } - - fn require(&self, n: usize) -> Result<(), BinaryFormatError> { - if self.sink.len() < self.pos + n { - Err(BinaryFormatError::NeedMore(self.pos + n - self.sink.len())) - } else { - Ok(()) - } - } - - fn skip(&mut self, n: usize) -> Result<(), BinaryFormatError> { - assert!(self.sink.len() >= self.pos + n); - self.pos += n; - Ok(()) - } -} diff --git a/executor/src/binary_format/utils.rs b/executor/src/binary_format/utils.rs deleted file mode 100644 index dcc68512a..000000000 --- a/executor/src/binary_format/utils.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::binary_format::{ - reader_writer::{BinaryFormatReader, BinaryFormatWriter}, - BinaryFormat, - BinaryFormatError, -}; -use rwasm::{ - core::UntypedValue, - engine::{ - bytecode::{ - AddressOffset, - BlockFuel, - BranchOffset, - BranchTableTargets, - DataSegmentIdx, - ElementSegmentIdx, - FuncIdx, - GlobalIdx, - LocalDepth, - SignatureIdx, - TableIdx, - }, - const_pool::ConstRef, - CompiledFunc, - }, -}; - -impl<'a> BinaryFormat<'a> for UntypedValue { - type SelfType = UntypedValue; - - fn encoded_length(&self) -> usize { - 8 - } - - fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result { - self.to_bits().write_binary(sink) - } - - fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result { - Ok(UntypedValue::from_bits(u64::read_binary(sink)?)) - } -} - -macro_rules! impl_default_idx { - ($name:ident, $to_method:ident, $nested_type:ident) => { - impl<'a> BinaryFormat<'a> for $name { - type SelfType = $name; - fn encoded_length(&self) -> usize { - core::mem::size_of::<$nested_type>() - } - fn write_binary( - &self, - sink: &mut BinaryFormatWriter<'a>, - ) -> Result { - ((*self).$to_method() as $nested_type).write_binary(sink) - } - fn read_binary( - sink: &mut BinaryFormatReader<'a>, - ) -> Result { - Ok($name::from($nested_type::read_binary(sink)?)) - } - } - }; -} - -impl_default_idx!(FuncIdx, to_u32, u32); -impl_default_idx!(TableIdx, to_u32, u32); -impl_default_idx!(SignatureIdx, to_u32, u32); -impl_default_idx!(LocalDepth, to_usize, u32); -impl_default_idx!(GlobalIdx, to_u32, u32); -impl_default_idx!(DataSegmentIdx, to_u32, u32); -impl_default_idx!(ElementSegmentIdx, to_u32, u32); -impl_default_idx!(BranchTableTargets, to_usize, u32); -impl_default_idx!(BlockFuel, to_u64, u32); -impl_default_idx!(AddressOffset, into_inner, u32); -impl_default_idx!(BranchOffset, to_i32, i32); -impl_default_idx!(CompiledFunc, to_u32, u32); -impl_default_idx!(ConstRef, to_usize, u32); diff --git a/executor/src/lib.rs b/executor/src/lib.rs deleted file mode 100644 index b8cdd558c..000000000 --- a/executor/src/lib.rs +++ /dev/null @@ -1,39 +0,0 @@ -#![cfg_attr(not(feature = "std"), no_std)] -#![allow(dead_code)] -#![warn(unused_crate_dependencies)] - -mod binary_format; -mod config; -mod context; -mod executor; -mod handler; -mod instr_ptr; -mod instruction_table; -mod memory; -mod module; -mod opcodes; -mod types; -mod utils; - -extern crate alloc; -extern crate core; - -pub use config::*; -pub use context::*; -pub use executor::*; -pub use handler::*; -pub use instruction_table::*; -pub use module::*; -pub use rwasm::{ - core::{HostError, TrapCode}, - engine::{bytecode::Instruction, stack::ValueStackPtr, RwasmConfig, StateRouterConfig}, - memory::MemoryEntity, - rwasm::{ - instruction::InstructionExtra, - BinaryFormat, - InstructionSet, - RwasmModule, - RwasmModuleInstance, - }, -}; -pub use types::*; diff --git a/executor/src/memory.rs b/executor/src/memory.rs deleted file mode 100644 index 0b3a91b79..000000000 --- a/executor/src/memory.rs +++ /dev/null @@ -1,161 +0,0 @@ -use bytes::BytesMut; -use rwasm::{ - core::{Pages, TrapCode}, - engine::executor::EntityGrowError, - errors::MemoryError, - store::ResourceLimiterRef, - MemoryType, -}; - -pub struct GlobalMemory { - pub shared_memory: BytesMut, - pub memory_type: MemoryType, - pub current_pages: Pages, -} - -impl GlobalMemory { - pub fn new( - memory_type: MemoryType, - limiter: &mut ResourceLimiterRef<'_>, - ) -> Result { - let initial_pages = memory_type.initial_pages(); - let initial_len = initial_pages.to_bytes(); - let maximum_pages = memory_type.maximum_pages().unwrap_or_else(Pages::max); - let maximum_len = maximum_pages.to_bytes(); - if let Some(limiter) = limiter.as_resource_limiter() { - if !limiter.memory_growing(0, initial_len.unwrap_or(usize::MAX), maximum_len)? { - return Err(MemoryError::OutOfBoundsAllocation); - } - } - if let Some(initial_len) = initial_len { - let mut shared_memory = BytesMut::with_capacity(initial_len); - shared_memory.resize(initial_len, 0); - let memory = Self { - shared_memory, - memory_type, - current_pages: initial_pages, - }; - Ok(memory) - } else { - let err = MemoryError::OutOfBoundsAllocation; - if let Some(limiter) = limiter.as_resource_limiter() { - limiter.memory_grow_failed(&err) - } - Err(err) - } - } - - /// Returns the memory type of the linear memory. - pub fn ty(&self) -> MemoryType { - self.memory_type - } - - /// Returns the amount of pages in use by the linear memory. - pub fn current_pages(&self) -> Pages { - self.current_pages - } - - /// Grows the linear memory by the given amount of new pages. - /// - /// Returns the amount of pages before the operation upon success. - /// - /// # Errors - /// - /// If the linear memory would grow beyond its maximum limit after - /// the grow operation. - pub fn grow( - &mut self, - additional: Pages, - limiter: &mut ResourceLimiterRef<'_>, - ) -> Result { - let current_pages = self.current_pages(); - if additional == Pages::from(0) { - // Nothing to do in this case. Bail out early. - return Ok(current_pages); - } - - let maximum_pages = self.ty().maximum_pages().unwrap_or_else(Pages::max); - let desired_pages = current_pages.checked_add(additional); - - // ResourceLimiter gets the first look at the request. - if let Some(limiter) = limiter.as_resource_limiter() { - let current_size = current_pages.to_bytes().unwrap_or(usize::MAX); - let desired_size = desired_pages - .unwrap_or_else(Pages::max) - .to_bytes() - .unwrap_or(usize::MAX); - let maximum_size = maximum_pages.to_bytes(); - match limiter.memory_growing(current_size, desired_size, maximum_size) { - Ok(true) => (), - Ok(false) => return Err(EntityGrowError::InvalidGrow), - Err(_) => return Err(EntityGrowError::TrapCode(TrapCode::GrowthOperationLimited)), - } - } - - let mut ret: Result = Err(EntityGrowError::InvalidGrow); - - if let Some(new_pages) = desired_pages { - if new_pages <= maximum_pages { - if let Some(new_size) = new_pages.to_bytes() { - // At this point, it is okay to grow the underlying virtual memory - // by the given number of additional pages. - assert!(new_size >= self.shared_memory.len()); - self.shared_memory.resize(new_size, 0); - self.current_pages = new_pages; - ret = Ok(current_pages) - } - } - } - - // If there was an error, ResourceLimiter gets to see. - if ret.is_err() { - if let Some(limiter) = limiter.as_resource_limiter() { - limiter.memory_grow_failed(&MemoryError::OutOfBoundsGrowth) - } - } - - ret - } - - /// Returns a shared slice to the bytes underlying to the byte buffer. - pub fn data(&self) -> &[u8] { - self.shared_memory.as_ref() - } - - /// Returns an exclusive slice to the bytes underlying to the byte buffer. - pub fn data_mut(&mut self) -> &mut [u8] { - self.shared_memory.as_mut() - } - - /// Reads `n` bytes from `memory[offset..offset+n]` into `buffer` - /// where `n` is the length of `buffer`. - /// - /// # Errors - /// - /// If this operation accesses out of bounds linear memory. - pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), MemoryError> { - let len_buffer = buffer.len(); - let slice = self - .data() - .get(offset..(offset + len_buffer)) - .ok_or(MemoryError::OutOfBoundsAccess)?; - buffer.copy_from_slice(slice); - Ok(()) - } - - /// Writes `n` bytes to `memory[offset..offset+n]` from `buffer` - /// where `n` if the length of `buffer`. - /// - /// # Errors - /// - /// If this operation accesses out of bounds linear memory. - pub fn write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), MemoryError> { - let len_buffer = buffer.len(); - let slice = self - .data_mut() - .get_mut(offset..(offset + len_buffer)) - .ok_or(MemoryError::OutOfBoundsAccess)?; - slice.copy_from_slice(buffer); - Ok(()) - } -} diff --git a/executor/src/types.rs b/executor/src/types.rs deleted file mode 100644 index 1776ff4c0..000000000 --- a/executor/src/types.rs +++ /dev/null @@ -1,49 +0,0 @@ -use rwasm::{ - core::{HostError, TrapCode}, - errors::MemoryError, - rwasm::BinaryFormatError, -}; - -pub const N_DEFAULT_STACK_SIZE: usize = 4096; -pub const N_MAX_STACK_SIZE: usize = 4096; -pub const N_MAX_TABLE_SIZE: usize = 1024; - -pub const TABLE_ELEMENT_NULL: u32 = 0; -pub const FUNC_REF_OFFSET: u32 = 1000; - -#[derive(Debug)] -pub enum RwasmError { - MalformedBinary, - TrapCode(TrapCode), - UnknownExternalFunction(u32), - ExecutionHalted(i32), - MemoryError(MemoryError), - HostInterruption(Box), - FloatsAreDisabled, - NotAllowedInFuelMode, -} - -impl RwasmError { - pub fn unwrap_exit_code(&self) -> i32 { - match self { - RwasmError::ExecutionHalted(exit_code) => *exit_code, - _ => unreachable!("runtime: can't unwrap exit code from error"), - } - } -} - -impl From for RwasmError { - fn from(_value: BinaryFormatError) -> Self { - Self::MalformedBinary - } -} -impl From for RwasmError { - fn from(value: TrapCode) -> Self { - Self::TrapCode(value) - } -} -impl From for RwasmError { - fn from(value: MemoryError) -> Self { - Self::MemoryError(value) - } -} diff --git a/executor/src/utils.rs b/executor/src/utils.rs deleted file mode 100644 index 6e7652dba..000000000 --- a/executor/src/utils.rs +++ /dev/null @@ -1,83 +0,0 @@ -#[macro_export] -macro_rules! impl_visit_load { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) -> Result<(), TrapCode> { - let offset = match vm.ip.data() { - InstructionData::AddressOffset(value) => *value, - _ => unreachable!("rwasm: missing instr data"), - }; - vm.execute_load_extend(offset, UntypedValue::$untyped_ident) - } - wrap_function_result!($visit_ident); - )* - } -} - -#[macro_export] -macro_rules! impl_visit_store { - ( $( fn $visit_ident:ident($untyped_ident:ident, $type_size:literal); )* ) => { - $( - #[inline(always)] - pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) -> Result<(), TrapCode> { - let offset = match vm.ip.data() { - InstructionData::AddressOffset(value) => *value, - _ => unreachable!("rwasm: missing instr data"), - }; - vm.execute_store_wrap(offset, UntypedValue::$untyped_ident, $type_size) - } - wrap_function_result!($visit_ident); - )* - } -} - -#[macro_export] -macro_rules! impl_visit_unary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - pub(crate) fn $visit_ident(exec: &mut RwasmExecutor) { - exec.execute_unary(UntypedValue::$untyped_ident) - } - )* - } -} - -#[macro_export] -macro_rules! impl_visit_binary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) { - vm.execute_binary(UntypedValue::$untyped_ident) - } - )* - } -} - -#[macro_export] -macro_rules! impl_visit_fallible_unary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) -> Result<(), TrapCode> { - vm.try_execute_unary(UntypedValue::$untyped_ident) - } - wrap_function_result!($visit_ident); - )* - } -} - -#[macro_export] -macro_rules! impl_visit_fallible_binary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) -> Result<(), TrapCode> { - vm.try_execute_binary(UntypedValue::$untyped_ident) - } - wrap_function_result!($visit_ident); - )* - } -} diff --git a/rwasm/Cargo.toml b/legacy/Cargo.toml similarity index 84% rename from rwasm/Cargo.toml rename to legacy/Cargo.toml index 09f105e82..0a227b984 100644 --- a/rwasm/Cargo.toml +++ b/legacy/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "rwasm" +name = "rwasm-legacy" version = "0.30.0" documentation = "" description = "rwasm" @@ -18,10 +18,11 @@ num-traits = { version = "0.2", default-features = false } downcast-rs = { version = "1.2.0", default-features = false } paste = "1" byteorder = { version = "1.5.0", default-features = false } +hashbrown = { version = "0.15.2", features = ["alloc"] } + # strum is used only with test cfg -strum = { workspace = true, optional = true } -strum_macros = { workspace = true, optional = true } -hashbrown = { workspace = true } +strum = { version = "0.27.1", optional = true } +strum_macros = { version = "0.27.1", optional = true } [dev-dependencies] hex-literal = "0.4.1" diff --git a/rwasm/README.md b/legacy/README.md similarity index 100% rename from rwasm/README.md rename to legacy/README.md diff --git a/rwasm/src/arena/component_vec.rs b/legacy/src/arena/component_vec.rs similarity index 100% rename from rwasm/src/arena/component_vec.rs rename to legacy/src/arena/component_vec.rs diff --git a/rwasm/src/arena/dedup.rs b/legacy/src/arena/dedup.rs similarity index 100% rename from rwasm/src/arena/dedup.rs rename to legacy/src/arena/dedup.rs diff --git a/rwasm/src/arena/guarded.rs b/legacy/src/arena/guarded.rs similarity index 100% rename from rwasm/src/arena/guarded.rs rename to legacy/src/arena/guarded.rs diff --git a/rwasm/src/arena/mod.rs b/legacy/src/arena/mod.rs similarity index 100% rename from rwasm/src/arena/mod.rs rename to legacy/src/arena/mod.rs diff --git a/rwasm/src/arena/tests.rs b/legacy/src/arena/tests.rs similarity index 100% rename from rwasm/src/arena/tests.rs rename to legacy/src/arena/tests.rs diff --git a/rwasm/src/core/host_error.rs b/legacy/src/core/host_error.rs similarity index 100% rename from rwasm/src/core/host_error.rs rename to legacy/src/core/host_error.rs diff --git a/rwasm/src/core/import_linker.rs b/legacy/src/core/import_linker.rs similarity index 100% rename from rwasm/src/core/import_linker.rs rename to legacy/src/core/import_linker.rs diff --git a/rwasm/src/core/mod.rs b/legacy/src/core/mod.rs similarity index 100% rename from rwasm/src/core/mod.rs rename to legacy/src/core/mod.rs diff --git a/rwasm/src/core/nan_preserving_float.rs b/legacy/src/core/nan_preserving_float.rs similarity index 100% rename from rwasm/src/core/nan_preserving_float.rs rename to legacy/src/core/nan_preserving_float.rs diff --git a/rwasm/src/core/rwasm.rs b/legacy/src/core/rwasm.rs similarity index 100% rename from rwasm/src/core/rwasm.rs rename to legacy/src/core/rwasm.rs diff --git a/rwasm/src/core/trap.rs b/legacy/src/core/trap.rs similarity index 100% rename from rwasm/src/core/trap.rs rename to legacy/src/core/trap.rs diff --git a/rwasm/src/core/units.rs b/legacy/src/core/units.rs similarity index 100% rename from rwasm/src/core/units.rs rename to legacy/src/core/units.rs diff --git a/rwasm/src/core/untyped.rs b/legacy/src/core/untyped.rs similarity index 99% rename from rwasm/src/core/untyped.rs rename to legacy/src/core/untyped.rs index 5d9f4c598..26cb701d2 100644 --- a/rwasm/src/core/untyped.rs +++ b/legacy/src/core/untyped.rs @@ -1494,6 +1494,7 @@ macro_rules! impl_decode_untyped_slice { #[allow(non_snake_case)] #[inline] + #[allow(unused_variables, unused_mut, unused_assignments)] fn decode_untyped_slice_i32(results: &[UntypedValue], origin_params: &[ValueType]) -> Result { let mut i = 0; match origin_params { @@ -1596,6 +1597,7 @@ macro_rules! impl_encode_untyped_slice { #[allow(non_snake_case)] #[inline] + #[allow(unused_variables, unused_mut, unused_assignments)] fn encode_untyped_slice_i32(self, results: &mut [UntypedValue], origin_results: Vec) -> Result<(), UntypedError> { let mut i = 0; match origin_results.as_slice() { diff --git a/rwasm/src/core/value.rs b/legacy/src/core/value.rs similarity index 100% rename from rwasm/src/core/value.rs rename to legacy/src/core/value.rs diff --git a/rwasm/src/engine/bytecode/instr_meta.rs b/legacy/src/engine/bytecode/instr_meta.rs similarity index 100% rename from rwasm/src/engine/bytecode/instr_meta.rs rename to legacy/src/engine/bytecode/instr_meta.rs diff --git a/rwasm/src/engine/bytecode/mod.rs b/legacy/src/engine/bytecode/mod.rs similarity index 100% rename from rwasm/src/engine/bytecode/mod.rs rename to legacy/src/engine/bytecode/mod.rs diff --git a/rwasm/src/engine/bytecode/stack_height.rs b/legacy/src/engine/bytecode/stack_height.rs similarity index 100% rename from rwasm/src/engine/bytecode/stack_height.rs rename to legacy/src/engine/bytecode/stack_height.rs diff --git a/rwasm/src/engine/bytecode/tests.rs b/legacy/src/engine/bytecode/tests.rs similarity index 100% rename from rwasm/src/engine/bytecode/tests.rs rename to legacy/src/engine/bytecode/tests.rs diff --git a/rwasm/src/engine/bytecode/utils.rs b/legacy/src/engine/bytecode/utils.rs similarity index 100% rename from rwasm/src/engine/bytecode/utils.rs rename to legacy/src/engine/bytecode/utils.rs diff --git a/rwasm/src/engine/cache.rs b/legacy/src/engine/cache.rs similarity index 100% rename from rwasm/src/engine/cache.rs rename to legacy/src/engine/cache.rs diff --git a/rwasm/src/engine/code_map.rs b/legacy/src/engine/code_map.rs similarity index 100% rename from rwasm/src/engine/code_map.rs rename to legacy/src/engine/code_map.rs diff --git a/rwasm/src/engine/config.rs b/legacy/src/engine/config.rs similarity index 99% rename from rwasm/src/engine/config.rs rename to legacy/src/engine/config.rs index 4e0e4480c..a044cf17f 100644 --- a/rwasm/src/engine/config.rs +++ b/legacy/src/engine/config.rs @@ -217,7 +217,7 @@ pub struct FuelCosts { /// # Note /// /// If this is zero then processing [`DropKeep`] costs nothing. - branch_kept_per_fuel: u64, + pub branch_kept_per_fuel: u64, /// Determines how many function locals consume one fuel per function call. /// /// # Note @@ -225,19 +225,19 @@ pub struct FuelCosts { /// - This is also applied to all function parameters since they are translated to local /// variable slots. /// - If this is zero then processing function locals costs nothing. - func_locals_per_fuel: u64, + pub func_locals_per_fuel: u64, /// How many memory bytes can be processed per fuel in a `bulk-memory` instruction. /// /// # Note /// /// If this is zero then processing memory bytes costs nothing. - memory_bytes_per_fuel: u64, + pub memory_bytes_per_fuel: u64, /// How many table elements can be processed per fuel in a `bulk-table` instruction. /// /// # Note /// /// If this is zero then processing table elements costs nothing. - table_elements_per_fuel: u64, + pub table_elements_per_fuel: u64, } impl FuelCosts { diff --git a/rwasm/src/engine/const_pool.rs b/legacy/src/engine/const_pool.rs similarity index 100% rename from rwasm/src/engine/const_pool.rs rename to legacy/src/engine/const_pool.rs diff --git a/rwasm/src/engine/executor.rs b/legacy/src/engine/executor.rs similarity index 100% rename from rwasm/src/engine/executor.rs rename to legacy/src/engine/executor.rs diff --git a/rwasm/src/engine/func_args.rs b/legacy/src/engine/func_args.rs similarity index 100% rename from rwasm/src/engine/func_args.rs rename to legacy/src/engine/func_args.rs diff --git a/rwasm/src/engine/func_builder/control_frame.rs b/legacy/src/engine/func_builder/control_frame.rs similarity index 100% rename from rwasm/src/engine/func_builder/control_frame.rs rename to legacy/src/engine/func_builder/control_frame.rs diff --git a/rwasm/src/engine/func_builder/control_stack.rs b/legacy/src/engine/func_builder/control_stack.rs similarity index 100% rename from rwasm/src/engine/func_builder/control_stack.rs rename to legacy/src/engine/func_builder/control_stack.rs diff --git a/rwasm/src/engine/func_builder/error.rs b/legacy/src/engine/func_builder/error.rs similarity index 100% rename from rwasm/src/engine/func_builder/error.rs rename to legacy/src/engine/func_builder/error.rs diff --git a/rwasm/src/engine/func_builder/inst_builder.rs b/legacy/src/engine/func_builder/inst_builder.rs similarity index 100% rename from rwasm/src/engine/func_builder/inst_builder.rs rename to legacy/src/engine/func_builder/inst_builder.rs diff --git a/rwasm/src/engine/func_builder/labels.rs b/legacy/src/engine/func_builder/labels.rs similarity index 100% rename from rwasm/src/engine/func_builder/labels.rs rename to legacy/src/engine/func_builder/labels.rs diff --git a/rwasm/src/engine/func_builder/locals_registry.rs b/legacy/src/engine/func_builder/locals_registry.rs similarity index 100% rename from rwasm/src/engine/func_builder/locals_registry.rs rename to legacy/src/engine/func_builder/locals_registry.rs diff --git a/rwasm/src/engine/func_builder/mod.rs b/legacy/src/engine/func_builder/mod.rs similarity index 100% rename from rwasm/src/engine/func_builder/mod.rs rename to legacy/src/engine/func_builder/mod.rs diff --git a/rwasm/src/engine/func_builder/translator.rs b/legacy/src/engine/func_builder/translator.rs similarity index 100% rename from rwasm/src/engine/func_builder/translator.rs rename to legacy/src/engine/func_builder/translator.rs diff --git a/rwasm/src/engine/func_builder/translator_i32.rs b/legacy/src/engine/func_builder/translator_i32.rs similarity index 100% rename from rwasm/src/engine/func_builder/translator_i32.rs rename to legacy/src/engine/func_builder/translator_i32.rs diff --git a/rwasm/src/engine/func_builder/value_stack.rs b/legacy/src/engine/func_builder/value_stack.rs similarity index 100% rename from rwasm/src/engine/func_builder/value_stack.rs rename to legacy/src/engine/func_builder/value_stack.rs diff --git a/rwasm/src/engine/func_types.rs b/legacy/src/engine/func_types.rs similarity index 100% rename from rwasm/src/engine/func_types.rs rename to legacy/src/engine/func_types.rs diff --git a/rwasm/src/engine/mod.rs b/legacy/src/engine/mod.rs similarity index 100% rename from rwasm/src/engine/mod.rs rename to legacy/src/engine/mod.rs diff --git a/rwasm/src/engine/resumable.rs b/legacy/src/engine/resumable.rs similarity index 100% rename from rwasm/src/engine/resumable.rs rename to legacy/src/engine/resumable.rs diff --git a/rwasm/src/engine/stack/frames.rs b/legacy/src/engine/stack/frames.rs similarity index 100% rename from rwasm/src/engine/stack/frames.rs rename to legacy/src/engine/stack/frames.rs diff --git a/rwasm/src/engine/stack/mod.rs b/legacy/src/engine/stack/mod.rs similarity index 100% rename from rwasm/src/engine/stack/mod.rs rename to legacy/src/engine/stack/mod.rs diff --git a/rwasm/src/engine/stack/values/mod.rs b/legacy/src/engine/stack/values/mod.rs similarity index 100% rename from rwasm/src/engine/stack/values/mod.rs rename to legacy/src/engine/stack/values/mod.rs diff --git a/rwasm/src/engine/stack/values/sp.rs b/legacy/src/engine/stack/values/sp.rs similarity index 100% rename from rwasm/src/engine/stack/values/sp.rs rename to legacy/src/engine/stack/values/sp.rs diff --git a/rwasm/src/engine/stack/values/tests.rs b/legacy/src/engine/stack/values/tests.rs similarity index 100% rename from rwasm/src/engine/stack/values/tests.rs rename to legacy/src/engine/stack/values/tests.rs diff --git a/rwasm/src/engine/tests.rs b/legacy/src/engine/tests.rs similarity index 100% rename from rwasm/src/engine/tests.rs rename to legacy/src/engine/tests.rs diff --git a/rwasm/src/engine/tracer.rs b/legacy/src/engine/tracer.rs similarity index 100% rename from rwasm/src/engine/tracer.rs rename to legacy/src/engine/tracer.rs diff --git a/rwasm/src/engine/traits.rs b/legacy/src/engine/traits.rs similarity index 100% rename from rwasm/src/engine/traits.rs rename to legacy/src/engine/traits.rs diff --git a/rwasm/src/error.rs b/legacy/src/error.rs similarity index 100% rename from rwasm/src/error.rs rename to legacy/src/error.rs diff --git a/rwasm/src/externref.rs b/legacy/src/externref.rs similarity index 100% rename from rwasm/src/externref.rs rename to legacy/src/externref.rs diff --git a/rwasm/src/foreach_tuple.rs b/legacy/src/foreach_tuple.rs similarity index 100% rename from rwasm/src/foreach_tuple.rs rename to legacy/src/foreach_tuple.rs diff --git a/rwasm/src/func/caller.rs b/legacy/src/func/caller.rs similarity index 100% rename from rwasm/src/func/caller.rs rename to legacy/src/func/caller.rs diff --git a/rwasm/src/func/error.rs b/legacy/src/func/error.rs similarity index 100% rename from rwasm/src/func/error.rs rename to legacy/src/func/error.rs diff --git a/rwasm/src/func/func_type.rs b/legacy/src/func/func_type.rs similarity index 100% rename from rwasm/src/func/func_type.rs rename to legacy/src/func/func_type.rs diff --git a/rwasm/src/func/funcref.rs b/legacy/src/func/funcref.rs similarity index 100% rename from rwasm/src/func/funcref.rs rename to legacy/src/func/funcref.rs diff --git a/rwasm/src/func/into_func.rs b/legacy/src/func/into_func.rs similarity index 100% rename from rwasm/src/func/into_func.rs rename to legacy/src/func/into_func.rs diff --git a/rwasm/src/func/mod.rs b/legacy/src/func/mod.rs similarity index 100% rename from rwasm/src/func/mod.rs rename to legacy/src/func/mod.rs diff --git a/rwasm/src/func/typed_func.rs b/legacy/src/func/typed_func.rs similarity index 100% rename from rwasm/src/func/typed_func.rs rename to legacy/src/func/typed_func.rs diff --git a/rwasm/src/global.rs b/legacy/src/global.rs similarity index 100% rename from rwasm/src/global.rs rename to legacy/src/global.rs diff --git a/rwasm/src/instance/builder.rs b/legacy/src/instance/builder.rs similarity index 100% rename from rwasm/src/instance/builder.rs rename to legacy/src/instance/builder.rs diff --git a/rwasm/src/instance/exports.rs b/legacy/src/instance/exports.rs similarity index 100% rename from rwasm/src/instance/exports.rs rename to legacy/src/instance/exports.rs diff --git a/rwasm/src/instance/mod.rs b/legacy/src/instance/mod.rs similarity index 100% rename from rwasm/src/instance/mod.rs rename to legacy/src/instance/mod.rs diff --git a/rwasm/src/lib.rs b/legacy/src/lib.rs similarity index 100% rename from rwasm/src/lib.rs rename to legacy/src/lib.rs diff --git a/rwasm/src/limits.rs b/legacy/src/limits.rs similarity index 100% rename from rwasm/src/limits.rs rename to legacy/src/limits.rs diff --git a/rwasm/src/linker.rs b/legacy/src/linker.rs similarity index 100% rename from rwasm/src/linker.rs rename to legacy/src/linker.rs diff --git a/rwasm/src/memory/buffer.rs b/legacy/src/memory/buffer.rs similarity index 100% rename from rwasm/src/memory/buffer.rs rename to legacy/src/memory/buffer.rs diff --git a/rwasm/src/memory/data.rs b/legacy/src/memory/data.rs similarity index 100% rename from rwasm/src/memory/data.rs rename to legacy/src/memory/data.rs diff --git a/rwasm/src/memory/error.rs b/legacy/src/memory/error.rs similarity index 100% rename from rwasm/src/memory/error.rs rename to legacy/src/memory/error.rs diff --git a/rwasm/src/memory/mod.rs b/legacy/src/memory/mod.rs similarity index 100% rename from rwasm/src/memory/mod.rs rename to legacy/src/memory/mod.rs diff --git a/rwasm/src/memory/tests.rs b/legacy/src/memory/tests.rs similarity index 100% rename from rwasm/src/memory/tests.rs rename to legacy/src/memory/tests.rs diff --git a/rwasm/src/module/builder.rs b/legacy/src/module/builder.rs similarity index 100% rename from rwasm/src/module/builder.rs rename to legacy/src/module/builder.rs diff --git a/rwasm/src/module/compile/block_type.rs b/legacy/src/module/compile/block_type.rs similarity index 100% rename from rwasm/src/module/compile/block_type.rs rename to legacy/src/module/compile/block_type.rs diff --git a/rwasm/src/module/compile/mod.rs b/legacy/src/module/compile/mod.rs similarity index 100% rename from rwasm/src/module/compile/mod.rs rename to legacy/src/module/compile/mod.rs diff --git a/rwasm/src/module/custom_section.rs b/legacy/src/module/custom_section.rs similarity index 100% rename from rwasm/src/module/custom_section.rs rename to legacy/src/module/custom_section.rs diff --git a/rwasm/src/module/data.rs b/legacy/src/module/data.rs similarity index 100% rename from rwasm/src/module/data.rs rename to legacy/src/module/data.rs diff --git a/rwasm/src/module/element.rs b/legacy/src/module/element.rs similarity index 100% rename from rwasm/src/module/element.rs rename to legacy/src/module/element.rs diff --git a/rwasm/src/module/error.rs b/legacy/src/module/error.rs similarity index 100% rename from rwasm/src/module/error.rs rename to legacy/src/module/error.rs diff --git a/rwasm/src/module/export.rs b/legacy/src/module/export.rs similarity index 100% rename from rwasm/src/module/export.rs rename to legacy/src/module/export.rs diff --git a/rwasm/src/module/global.rs b/legacy/src/module/global.rs similarity index 100% rename from rwasm/src/module/global.rs rename to legacy/src/module/global.rs diff --git a/rwasm/src/module/import.rs b/legacy/src/module/import.rs similarity index 100% rename from rwasm/src/module/import.rs rename to legacy/src/module/import.rs diff --git a/rwasm/src/module/init_expr.rs b/legacy/src/module/init_expr.rs similarity index 100% rename from rwasm/src/module/init_expr.rs rename to legacy/src/module/init_expr.rs diff --git a/rwasm/src/module/instantiate/error.rs b/legacy/src/module/instantiate/error.rs similarity index 100% rename from rwasm/src/module/instantiate/error.rs rename to legacy/src/module/instantiate/error.rs diff --git a/rwasm/src/module/instantiate/mod.rs b/legacy/src/module/instantiate/mod.rs similarity index 100% rename from rwasm/src/module/instantiate/mod.rs rename to legacy/src/module/instantiate/mod.rs diff --git a/rwasm/src/module/instantiate/pre.rs b/legacy/src/module/instantiate/pre.rs similarity index 100% rename from rwasm/src/module/instantiate/pre.rs rename to legacy/src/module/instantiate/pre.rs diff --git a/rwasm/src/module/instantiate/tests.rs b/legacy/src/module/instantiate/tests.rs similarity index 100% rename from rwasm/src/module/instantiate/tests.rs rename to legacy/src/module/instantiate/tests.rs diff --git a/rwasm/src/module/mod.rs b/legacy/src/module/mod.rs similarity index 100% rename from rwasm/src/module/mod.rs rename to legacy/src/module/mod.rs diff --git a/rwasm/src/module/parser.rs b/legacy/src/module/parser.rs similarity index 100% rename from rwasm/src/module/parser.rs rename to legacy/src/module/parser.rs diff --git a/rwasm/src/module/read.rs b/legacy/src/module/read.rs similarity index 100% rename from rwasm/src/module/read.rs rename to legacy/src/module/read.rs diff --git a/rwasm/src/module/utils.rs b/legacy/src/module/utils.rs similarity index 100% rename from rwasm/src/module/utils.rs rename to legacy/src/module/utils.rs diff --git a/rwasm/src/reftype.rs b/legacy/src/reftype.rs similarity index 100% rename from rwasm/src/reftype.rs rename to legacy/src/reftype.rs diff --git a/rwasm/src/rwasm/binary_format/drop_keep.rs b/legacy/src/rwasm/binary_format/drop_keep.rs similarity index 75% rename from rwasm/src/rwasm/binary_format/drop_keep.rs rename to legacy/src/rwasm/binary_format/drop_keep.rs index 922dc1c7e..32848cf91 100644 --- a/rwasm/src/rwasm/binary_format/drop_keep.rs +++ b/legacy/src/rwasm/binary_format/drop_keep.rs @@ -12,14 +12,14 @@ impl<'a> BinaryFormat<'a> for DropKeep { fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result { let mut n = 0; - n += sink.write_u32_le(self.drop() as u32)?; - n += sink.write_u32_le(self.keep() as u32)?; + n += sink.write_u16_le(self.drop())?; + n += sink.write_u16_le(self.keep())?; Ok(n) } fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result { - let drop = sink.read_u32_le()?; - let keep = sink.read_u32_le()?; + let drop = sink.read_u16_le()?; + let keep = sink.read_u16_le()?; Ok(DropKeep::new(drop as usize, keep as usize).unwrap()) } } diff --git a/rwasm/src/rwasm/binary_format/instruction.rs b/legacy/src/rwasm/binary_format/instruction.rs similarity index 90% rename from rwasm/src/rwasm/binary_format/instruction.rs rename to legacy/src/rwasm/binary_format/instruction.rs index 1dfdf0cfc..814de7a14 100644 --- a/rwasm/src/rwasm/binary_format/instruction.rs +++ b/legacy/src/rwasm/binary_format/instruction.rs @@ -1,5 +1,4 @@ use crate::{ - arena::ArenaIndex, core::UntypedValue, engine::{ bytecode::{ @@ -522,75 +521,10 @@ impl<'a> BinaryFormat<'a> for Instruction { } pub trait InstructionExtra { - fn aux_value(&self) -> Option; - fn code_value(&self) -> u8; fn info(&self) -> (u8, usize); } impl InstructionExtra for Instruction { - fn aux_value(&self) -> Option { - let value: UntypedValue = match self { - Instruction::LocalGet(val) - | Instruction::LocalSet(val) - | Instruction::LocalTee(val) => UntypedValue::from(val.to_usize() as u64), - Instruction::Br(val) - | Instruction::BrIfEqz(val) - | Instruction::BrIfNez(val) - | Instruction::BrAdjust(val) - | Instruction::BrAdjustIfNez(val) => val.to_i32().into(), - Instruction::BrTable(val) => (val.to_usize() as u64).into(), - Instruction::ConsumeFuel(val) => val.to_u64().into(), - Instruction::ReturnCallInternal(val) | Instruction::CallInternal(val) => { - (val.into_usize() as u64).into() - } - Instruction::ReturnCall(val) | Instruction::Call(val) => val.to_u32().into(), - Instruction::ReturnCallIndirect(val) | Instruction::CallIndirect(val) => { - val.to_u32().into() - } - Instruction::GlobalGet(val) | Instruction::GlobalSet(val) => val.to_u32().into(), - Instruction::I32Load(val) - | Instruction::I64Load(val) - | Instruction::F32Load(val) - | Instruction::F64Load(val) - | Instruction::I32Load8S(val) - | Instruction::I32Load8U(val) - | Instruction::I32Load16S(val) - | Instruction::I32Load16U(val) - | Instruction::I64Load8S(val) - | Instruction::I64Load8U(val) - | Instruction::I64Load16S(val) - | Instruction::I64Load16U(val) - | Instruction::I64Load32S(val) - | Instruction::I64Load32U(val) - | Instruction::I32Store(val) - | Instruction::I64Store(val) - | Instruction::F32Store(val) - | Instruction::F64Store(val) - | Instruction::I32Store8(val) - | Instruction::I32Store16(val) - | Instruction::I64Store8(val) - | Instruction::I64Store16(val) - | Instruction::I64Store32(val) => val.into_inner().into(), - Instruction::MemoryInit(val) | Instruction::DataDrop(val) => val.to_u32().into(), - Instruction::TableSize(val) - | Instruction::TableGrow(val) - | Instruction::TableFill(val) - | Instruction::TableGet(val) - | Instruction::TableSet(val) - | Instruction::TableCopy(val) => val.to_u32().into(), - Instruction::TableInit(val) | Instruction::ElemDrop(val) => val.to_u32().into(), - Instruction::RefFunc(val) => val.to_u32().into(), - Instruction::ConstRef(val) => (val.to_usize() as u64).into(), - _ => return None, - }; - Some(value) - } - - fn code_value(&self) -> u8 { - let (code_value, _) = self.info(); - code_value - } - fn info(&self) -> (u8, usize) { let mut sink = [0u8; RWASM_MAX_INSTRUCTION_SIZE_BYTES]; let mut binary_writer = BinaryFormatWriter::new(&mut sink); diff --git a/rwasm/src/rwasm/binary_format/instruction_set.rs b/legacy/src/rwasm/binary_format/instruction_set.rs similarity index 100% rename from rwasm/src/rwasm/binary_format/instruction_set.rs rename to legacy/src/rwasm/binary_format/instruction_set.rs diff --git a/executor/src/binary_format/mod.rs b/legacy/src/rwasm/binary_format/mod.rs similarity index 82% rename from executor/src/binary_format/mod.rs rename to legacy/src/rwasm/binary_format/mod.rs index 4d2982152..bdd7128a2 100644 --- a/executor/src/binary_format/mod.rs +++ b/legacy/src/rwasm/binary_format/mod.rs @@ -1,15 +1,20 @@ mod drop_keep; pub mod instruction; -pub mod module; +mod instruction_set; +mod module; mod number; pub mod reader_writer; mod utils; -pub use self::reader_writer::{BinaryFormatReader, BinaryFormatWriter}; +pub use self::{ + module::{RWASM_MAGIC_BYTE_0, RWASM_MAGIC_BYTE_1, RWASM_VERSION_V1}, + reader_writer::{BinaryFormatReader, BinaryFormatWriter}, +}; use alloc::vec::Vec; #[derive(Debug, Copy, Clone)] pub enum BinaryFormatError { + ReachedUnreachable, NeedMore(usize), MalformedWasmModule, IllegalOpcode(u8), diff --git a/legacy/src/rwasm/binary_format/module.rs b/legacy/src/rwasm/binary_format/module.rs new file mode 100644 index 000000000..f7ebcd350 --- /dev/null +++ b/legacy/src/rwasm/binary_format/module.rs @@ -0,0 +1,136 @@ +use crate::{ + engine::bytecode::Instruction, + rwasm::{BinaryFormat, BinaryFormatError, BinaryFormatReader, BinaryFormatWriter, RwasmModule}, +}; + +/// Rwasm magic bytes 0xef52 +pub const RWASM_MAGIC_BYTE_0: u8 = 0xef; +pub const RWASM_MAGIC_BYTE_1: u8 = 0x52; + +/// Rwasm binary version that is equal to 'R' symbol (0x52 in hex) +pub const RWASM_VERSION_V1: u8 = 0x01; + +/// Rwasm module encoding follows EIP-3540 standard but stores rWASM opcodes +/// instead of EVM. +/// +/// https://eips.ethereum.org/EIPS/eip-3540 +impl<'a> BinaryFormat<'a> for RwasmModule { + type SelfType = RwasmModule; + + fn encoded_length(&self) -> usize { + 3 + // sig + version + size_of::() + self.code_section.encoded_length() + // code section + size_of::() + self.memory_section.len() + // memory section + size_of::() + self.element_section.len() * size_of::() + // element section + size_of::() + // source pc + size_of::() + self.func_section.len() * size_of::() // decl section + } + + fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result { + let mut n = 0; + // magic prefix (0xef 0x52 0x01) + n += sink.write_u8(RWASM_MAGIC_BYTE_0)?; + n += sink.write_u8(RWASM_MAGIC_BYTE_1)?; + n += sink.write_u8(RWASM_VERSION_V1)?; + // code section + let code_section_length = self.code_section.len() as u64; + n += sink.write_u64_le(code_section_length)?; + for opcode in self.code_section.instrs().iter() { + n += opcode.write_binary(sink)?; + } + // memory section + let memory_section_length = self.memory_section.len() as u64; + n += sink.write_u64_le(memory_section_length)?; + n += sink.write_bytes(&self.memory_section)?; + // element section + let element_section_length = self.element_section.len() as u64; + n += sink.write_u64_le(element_section_length)?; + for x in self.element_section.iter() { + n += x.write_binary(sink)?; + } + // source pc + n += sink.write_u32_le(self.source_pc)?; + // func section + let func_section_length = self.func_section.len() as u64; + n += sink.write_u64_le(func_section_length)?; + for x in self.func_section.iter() { + n += x.write_binary(sink)?; + } + Ok(n) + } + + fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result { + let mut result = RwasmModule::default(); + // magic prefix (0xef 0x52) + if sink.read_u8()? != RWASM_MAGIC_BYTE_0 || sink.read_u8()? != RWASM_MAGIC_BYTE_1 { + return Err(BinaryFormatError::MalformedWasmModule); + } + // version check + let version = sink.read_u8()?; + if version != RWASM_VERSION_V1 { + return Err(BinaryFormatError::MalformedWasmModule); + } + // code section header + let code_section_length = sink.read_u64_le()?; + for _ in 0..code_section_length { + result.code_section.push(Instruction::read_binary(sink)?); + } + // memory section + let memory_section_length = sink.read_u64_le()?; + { + result + .memory_section + .resize(memory_section_length as usize, 0u8); + sink.read_bytes(&mut result.memory_section)?; + } + // element section + let element_section_length = sink.read_u64_le()?; + { + for _ in 0..element_section_length { + result.element_section.push(sink.read_u32_le()?); + } + } + // source pc + result.source_pc = sink.read_u32_le()?; + // func section + let decl_section_length = sink.read_u64_le()?; + { + for _ in 0..decl_section_length { + result.func_section.push(sink.read_u32_le()?); + } + } + // return the final module + Ok(result) + } +} + +#[cfg(test)] +mod tests { + use crate::{ + instruction_set, + rwasm::{BinaryFormat, RwasmModule}, + }; + + #[test] + fn test_module_encoding() { + let module = RwasmModule { + code_section: instruction_set! { + I32Const(100) + I32Const(20) + I32Add + I32Const(3) + I32Add + Drop + }, + memory_section: Default::default(), + func_section: vec![1, 2, 3], + element_section: vec![5, 6, 7, 8, 9], + source_pc: 0, + }; + let mut encoded_data = Vec::new(); + module.write_binary_to_vec(&mut encoded_data).unwrap(); + assert_eq!(module.encoded_length(), encoded_data.len()); + let module2 = RwasmModule::read_from_slice(&encoded_data).unwrap(); + assert_eq!(module, module2); + } +} diff --git a/rwasm/src/rwasm/binary_format/number.rs b/legacy/src/rwasm/binary_format/number.rs similarity index 100% rename from rwasm/src/rwasm/binary_format/number.rs rename to legacy/src/rwasm/binary_format/number.rs diff --git a/rwasm/src/rwasm/binary_format/reader_writer.rs b/legacy/src/rwasm/binary_format/reader_writer.rs similarity index 100% rename from rwasm/src/rwasm/binary_format/reader_writer.rs rename to legacy/src/rwasm/binary_format/reader_writer.rs diff --git a/rwasm/src/rwasm/binary_format/utils.rs b/legacy/src/rwasm/binary_format/utils.rs similarity index 100% rename from rwasm/src/rwasm/binary_format/utils.rs rename to legacy/src/rwasm/binary_format/utils.rs diff --git a/rwasm/src/rwasm/drop_keep.rs b/legacy/src/rwasm/drop_keep.rs similarity index 100% rename from rwasm/src/rwasm/drop_keep.rs rename to legacy/src/rwasm/drop_keep.rs diff --git a/rwasm/src/rwasm/instruction_set.rs b/legacy/src/rwasm/instruction_set.rs similarity index 100% rename from rwasm/src/rwasm/instruction_set.rs rename to legacy/src/rwasm/instruction_set.rs diff --git a/rwasm/src/rwasm/mod.rs b/legacy/src/rwasm/mod.rs similarity index 100% rename from rwasm/src/rwasm/mod.rs rename to legacy/src/rwasm/mod.rs diff --git a/rwasm/src/rwasm/reduced_module.rs b/legacy/src/rwasm/reduced_module.rs similarity index 83% rename from rwasm/src/rwasm/reduced_module.rs rename to legacy/src/rwasm/reduced_module.rs index 928c50315..bbc0c8aa8 100644 --- a/rwasm/src/rwasm/reduced_module.rs +++ b/legacy/src/rwasm/reduced_module.rs @@ -20,29 +20,23 @@ use crate::{ }; use alloc::{string::ToString, vec::Vec}; -#[derive(Debug, Default, PartialEq, Clone, Eq, Hash)] -pub struct RwasmModuleInstance { - pub module: RwasmModule, - pub func_segments: Vec, - pub start: usize, -} - #[derive(Debug, Default, PartialEq, Clone, Eq, Hash)] pub struct RwasmModule { pub code_section: InstructionSet, pub memory_section: Vec, - pub func_section: Vec, pub element_section: Vec, + pub source_pc: u32, + pub func_section: Vec, } impl From for RwasmModule { - fn from(value: InstructionSet) -> Self { - let code_section_len = value.len() as u32; + fn from(code_section: InstructionSet) -> Self { Self { - code_section: value, + code_section, memory_section: vec![], - func_section: vec![code_section_len], element_section: vec![], + source_pc: 0, + func_section: vec![0], } } } @@ -115,29 +109,11 @@ impl RwasmModule { Self::from(instruction_set) } - pub fn instantiate(self) -> RwasmModuleInstance { - let mut func_segments = vec![0u32]; - let mut total_func_len = 0u32; - for func_len in self.func_section.iter().take(self.func_section.len() - 1) { - total_func_len += *func_len; - func_segments.push(total_func_len); - } - let source_pc = func_segments - .last() - .copied() - .expect("rwasm: empty function section"); - RwasmModuleInstance { - module: self, - start: source_pc as usize, - func_segments, - } - } - pub fn from_module(module: &Module) -> Self { // build code & func sections let mut code_section = InstructionSet::new(); - let mut func_section = Vec::new(); - for compiled_func in module.compiled_funcs.iter() { + let mut func_section = vec![0]; + for (i, compiled_func) in module.compiled_funcs.iter().enumerate() { let (mut instr_begin, instr_end) = module.engine().instr_ptr(*compiled_func); let length_before = code_section.len(); while instr_begin != instr_end { @@ -145,11 +121,16 @@ impl RwasmModule { instr_begin.add(1); } let function_length = code_section.len() - length_before; - func_section.push(function_length as u32); + if i != module.compiled_funcs.len() - 1 { + func_section.push(func_section.last().unwrap() + function_length as u32); + } } // build element section - let element_section = module.element_segments[0] - .items + let element_section = module + .element_segments + .get(0) + .map(|v| v.items.clone()) + .unwrap_or_default() .items() .iter() .map(|v| { @@ -161,13 +142,15 @@ impl RwasmModule { .into_u32() }) .collect::>(); - // build memory section + // build a memory section let memory_section = Vec::from(&*module.data_segments[0].bytes); + let source_pc = func_section.last().copied().unwrap_or(0); Self { code_section, memory_section, func_section, element_section, + source_pc, } } @@ -184,7 +167,7 @@ impl RwasmModule { let mut builder = ModuleBuilder::new(engine); // the main function has empty inputs and outputs let empty_func_type = builder.ensure_empty_func_type_exists(); - // push functions and init engine's code map + // push functions and init the engine's code map builder .push_funcs( (0..self.func_section.len()) diff --git a/rwasm/src/rwasm/segment_builder.rs b/legacy/src/rwasm/segment_builder.rs similarity index 100% rename from rwasm/src/rwasm/segment_builder.rs rename to legacy/src/rwasm/segment_builder.rs diff --git a/rwasm/src/rwasm/tests.rs b/legacy/src/rwasm/tests.rs similarity index 100% rename from rwasm/src/rwasm/tests.rs rename to legacy/src/rwasm/tests.rs diff --git a/rwasm/src/rwasm/translator.rs b/legacy/src/rwasm/translator.rs similarity index 100% rename from rwasm/src/rwasm/translator.rs rename to legacy/src/rwasm/translator.rs diff --git a/rwasm/src/rwasm/types.rs b/legacy/src/rwasm/types.rs similarity index 100% rename from rwasm/src/rwasm/types.rs rename to legacy/src/rwasm/types.rs diff --git a/rwasm/src/store.rs b/legacy/src/store.rs similarity index 100% rename from rwasm/src/store.rs rename to legacy/src/store.rs diff --git a/rwasm/src/table/element.rs b/legacy/src/table/element.rs similarity index 100% rename from rwasm/src/table/element.rs rename to legacy/src/table/element.rs diff --git a/rwasm/src/table/error.rs b/legacy/src/table/error.rs similarity index 100% rename from rwasm/src/table/error.rs rename to legacy/src/table/error.rs diff --git a/rwasm/src/table/mod.rs b/legacy/src/table/mod.rs similarity index 100% rename from rwasm/src/table/mod.rs rename to legacy/src/table/mod.rs diff --git a/rwasm/src/table/tests.rs b/legacy/src/table/tests.rs similarity index 100% rename from rwasm/src/table/tests.rs rename to legacy/src/table/tests.rs diff --git a/rwasm/src/value.rs b/legacy/src/value.rs similarity index 100% rename from rwasm/src/value.rs rename to legacy/src/value.rs diff --git a/rwasm/src/rwasm/binary_format/mod.rs b/rwasm/src/rwasm/binary_format/mod.rs deleted file mode 100644 index 66d50f52b..000000000 --- a/rwasm/src/rwasm/binary_format/mod.rs +++ /dev/null @@ -1,51 +0,0 @@ -mod drop_keep; -pub mod instruction; -mod instruction_set; -mod module; -mod number; -pub mod reader_writer; -mod utils; - -pub use self::{ - module::{ - RWASM_MAGIC_BYTE_0, - RWASM_MAGIC_BYTE_1, - RWASM_SECTION_CODE, - RWASM_SECTION_ELEMENT, - RWASM_SECTION_END, - RWASM_SECTION_FUNC, - RWASM_SECTION_MEMORY, - RWASM_VERSION_V1, - }, - reader_writer::{BinaryFormatReader, BinaryFormatWriter}, -}; -use alloc::vec::Vec; - -#[derive(Debug, Copy, Clone)] -pub enum BinaryFormatError { - ReachedUnreachable, - NeedMore(usize), - MalformedWasmModule, - IllegalOpcode(u8), -} - -pub trait BinaryFormat<'a> { - type SelfType; - - fn encoded_length(&self) -> usize; - - fn write_binary_to_vec(&self, buffer: &'a mut Vec) -> Result { - buffer.resize(self.encoded_length(), 0u8); - let mut sink = BinaryFormatWriter::<'a>::new(buffer.as_mut_slice()); - self.write_binary(&mut sink) - } - - fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result; - - fn read_from_slice(sink: &'a [u8]) -> Result { - let mut binary_format_reader = BinaryFormatReader::<'a>::new(sink); - Self::read_binary(&mut binary_format_reader) - } - - fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result; -} diff --git a/rwasm/src/rwasm/binary_format/module.rs b/rwasm/src/rwasm/binary_format/module.rs deleted file mode 100644 index c162710a7..000000000 --- a/rwasm/src/rwasm/binary_format/module.rs +++ /dev/null @@ -1,178 +0,0 @@ -use crate::{ - engine::bytecode::Instruction, - rwasm::{BinaryFormat, BinaryFormatError, BinaryFormatReader, BinaryFormatWriter, RwasmModule}, -}; - -/// Rwasm magic bytes 0xef52 -pub const RWASM_MAGIC_BYTE_0: u8 = 0xef; -pub const RWASM_MAGIC_BYTE_1: u8 = 0x52; - -/// Rwasm binary version that is equal to 'R' symbol (0x52 in hex) -pub const RWASM_VERSION_V1: u8 = 0x01; - -/// Sections that are presented in Rwasm binary: -/// - code -/// - memory -/// - decl -/// - element -pub const RWASM_SECTION_CODE: u8 = 0x01; -pub const RWASM_SECTION_MEMORY: u8 = 0x02; -pub const RWASM_SECTION_FUNC: u8 = 0x03; -pub const RWASM_SECTION_ELEMENT: u8 = 0x04; -pub const RWASM_SECTION_END: u8 = 0x00; - -/// Rwasm module encoding follows EIP-3540 standard but stores rWASM opcodes -/// instead of EVM. -/// -/// https://eips.ethereum.org/EIPS/eip-3540 -impl<'a> BinaryFormat<'a> for RwasmModule { - type SelfType = RwasmModule; - - fn encoded_length(&self) -> usize { - 2 + // sig - 1 + // version - 5 + // code section header - 5 + // memory section header - 5 + // func section header - 5 + // element section header - 1 + // terminator - self.code_section.encoded_length() + // code section - self.memory_section.len() + // memory section - self.func_section.len() * core::mem::size_of::() + // decl section - self.element_section.len() * core::mem::size_of::() // element section - } - - fn write_binary(&self, sink: &mut BinaryFormatWriter<'a>) -> Result { - // magic prefix (0xef 0x00) - let mut n = sink.write_u8(RWASM_MAGIC_BYTE_0)?; - n += sink.write_u8(RWASM_MAGIC_BYTE_1)?; - // version (0x52 = R symbol) - n += sink.write_u8(RWASM_VERSION_V1)?; - // code section header - let code_section_length = self.code_section.encoded_length() as u32; - n += sink.write_u8(RWASM_SECTION_CODE)?; - n += sink.write_u32_le(code_section_length)?; - // memory section header - let memory_section_length = self.memory_section.len() as u32; - n += sink.write_u8(RWASM_SECTION_MEMORY)?; - n += sink.write_u32_le(memory_section_length)?; - // decl section header - let decl_section_length = - self.func_section.len() as u32 * (core::mem::size_of::() as u32); - n += sink.write_u8(RWASM_SECTION_FUNC)?; - n += sink.write_u32_le(decl_section_length)?; - // element section header - let element_section_length = - self.element_section.len() as u32 * (core::mem::size_of::() as u32); - n += sink.write_u8(RWASM_SECTION_ELEMENT)?; - n += sink.write_u32_le(element_section_length)?; - // section terminator - n += sink.write_u8(RWASM_SECTION_END)?; - // write code/decl/element sections - for opcode in self.code_section.instrs().iter() { - n += opcode.write_binary(sink)?; - } - n += sink.write_bytes(&self.memory_section)?; - for x in self.func_section.iter() { - n += x.write_binary(sink)?; - } - for x in self.element_section.iter() { - n += x.write_binary(sink)?; - } - Ok(n) - } - - fn read_binary(sink: &mut BinaryFormatReader<'a>) -> Result { - let mut result = RwasmModule::default(); - // magic prefix (0xef 0x52) - if sink.read_u8()? != RWASM_MAGIC_BYTE_0 || sink.read_u8()? != RWASM_MAGIC_BYTE_1 { - return Err(BinaryFormatError::MalformedWasmModule); - } - // version check - let version = sink.read_u8()?; - if version != RWASM_VERSION_V1 { - return Err(BinaryFormatError::MalformedWasmModule); - } - // code section header - sink.assert_u8(RWASM_SECTION_CODE)?; - let code_section_length = sink.read_u32_le()?; - // memory section header - sink.assert_u8(RWASM_SECTION_MEMORY)?; - let memory_section_length = sink.read_u32_le()?; - // decl section header - sink.assert_u8(RWASM_SECTION_FUNC)?; - let decl_section_length = sink.read_u32_le()?; - // element section header - sink.assert_u8(RWASM_SECTION_ELEMENT)?; - let element_section_length = sink.read_u32_le()?; - // section terminator - sink.assert_u8(RWASM_SECTION_END)?; - // read code section - let mut code_section_sink = sink.limit_with(code_section_length as usize); - while !code_section_sink.is_empty() { - result - .code_section - .push(Instruction::read_binary(&mut code_section_sink)?); - } - sink.pos += code_section_length as usize; - // read memory section - { - let mut memory_section_sink = sink.limit_with(memory_section_length as usize); - result - .memory_section - .resize(memory_section_length as usize, 0u8); - memory_section_sink.read_bytes(&mut result.memory_section)?; - sink.pos += memory_section_length as usize; - } - // read decl section - { - let mut decl_section_sink = sink.limit_with(decl_section_length as usize); - while !decl_section_sink.is_empty() { - result.func_section.push(decl_section_sink.read_u32_le()?); - } - sink.pos += decl_section_length as usize; - } - // read element section - { - let mut element_section_sink = sink.limit_with(element_section_length as usize); - while !element_section_sink.is_empty() { - result - .element_section - .push(element_section_sink.read_u32_le()?); - } - sink.pos += element_section_length as usize; - } - // return the final module - Ok(result) - } -} - -#[cfg(test)] -mod tests { - use crate::{ - instruction_set, - rwasm::{BinaryFormat, RwasmModule}, - }; - - #[test] - fn test_module_encoding() { - let module = RwasmModule { - code_section: instruction_set! { - I32Const(100) - I32Const(20) - I32Add - I32Const(3) - I32Add - Drop - }, - memory_section: Default::default(), - func_section: vec![1, 2, 3], - element_section: vec![5, 6, 7, 8, 9], - }; - let mut encoded_data = Vec::new(); - module.write_binary_to_vec(&mut encoded_data).unwrap(); - assert_eq!(module.encoded_length(), encoded_data.len()); - let module2 = RwasmModule::read_from_slice(&encoded_data).unwrap(); - assert_eq!(module, module2); - } -} diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..668ffd324 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,12 @@ +#![cfg_attr(not(feature = "std"), no_std)] +#![allow(dead_code)] +#![warn(unused_crate_dependencies)] + +mod types; +mod vm; + +extern crate alloc; +extern crate core; + +pub use types::*; +pub use vm::*; diff --git a/src/types/address_offset.rs b/src/types/address_offset.rs new file mode 100644 index 000000000..7bf1826ba --- /dev/null +++ b/src/types/address_offset.rs @@ -0,0 +1,23 @@ +use bincode::{Decode, Encode}; + +/// A linear memory access offset. +/// +/// # Note +/// +/// Used to calculate the effective address of linear memory access. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct AddressOffset(u32); + +impl From for AddressOffset { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl AddressOffset { + /// Returns the inner `u32` index. + pub fn into_inner(self) -> u32 { + self.0 + } +} diff --git a/src/types/block_fuel.rs b/src/types/block_fuel.rs new file mode 100644 index 000000000..8baabdaac --- /dev/null +++ b/src/types/block_fuel.rs @@ -0,0 +1,47 @@ +use crate::RwasmError; +use bincode::{Decode, Encode}; + +/// The accumulated fuel to execute a block via [`Instruction::ConsumeFuel`]. +/// +/// [`Instruction::ConsumeFuel`]: [`super::Instruction::ConsumeFuel`] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct BlockFuel(u32); + +impl TryFrom for BlockFuel { + type Error = RwasmError; + + fn try_from(index: u64) -> Result { + match u32::try_from(index) { + Ok(index) => Ok(Self(index)), + Err(_) => Err(RwasmError::BlockFuelOutOfBounds), + } + } +} + +impl From for BlockFuel { + fn from(value: u32) -> Self { + BlockFuel(value) + } +} + +impl BlockFuel { + /// Bump the fuel by `amount` if possible. + /// + /// # Errors + /// + /// If the new fuel amount after this operation is out of bounds. + pub fn bump_by(&mut self, amount: u64) -> Result<(), RwasmError> { + let new_amount = self + .to_u64() + .checked_add(amount) + .ok_or(RwasmError::BlockFuelOutOfBounds)?; + self.0 = u32::try_from(new_amount).map_err(|_| RwasmError::BlockFuelOutOfBounds)?; + Ok(()) + } + + /// Returns the index value as `u64`. + pub fn to_u64(self) -> u64 { + u64::from(self.0) + } +} diff --git a/src/types/branch_offset.rs b/src/types/branch_offset.rs new file mode 100644 index 000000000..22e021ee5 --- /dev/null +++ b/src/types/branch_offset.rs @@ -0,0 +1,64 @@ +use crate::RwasmError; +use bincode::{Decode, Encode}; + +/// A signed offset for branch instructions. +/// +/// This defines how much the instruction pointer is offset +/// upon taking the respective branch. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +pub struct BranchOffset(i32); + +impl From for BranchOffset { + fn from(index: i32) -> Self { + Self(index) + } +} + +impl BranchOffset { + /// Creates an uninitalized [`BranchOffset`]. + pub fn uninit() -> Self { + Self(0) + } + + /// Creates an initialized [`BranchOffset`] from `src` to `dst`. + /// + /// # Errors + /// + /// If the resulting [`BranchOffset`] is out of bounds. + /// + /// # Panics + /// + /// If the resulting [`BranchOffset`] is uninitialized, aka equal to 0. + pub fn from_src_to_dst(src: u32, dst: u32) -> Result { + fn make_err() -> RwasmError { + RwasmError::BranchOffsetOutOfBounds + } + let src = i64::from(src); + let dst = i64::from(dst); + let offset = dst.checked_sub(src).ok_or_else(make_err)?; + let offset = i32::try_from(offset).map_err(|_| make_err())?; + Ok(Self(offset)) + } + + /// Returns `true` if the [`BranchOffset`] has been initialized. + pub fn is_init(self) -> bool { + self.to_i32() != 0 + } + + /// Initializes the [`BranchOffset`] with a proper value. + /// + /// # Panics + /// + /// - If the [`BranchOffset`] have already been initialized. + /// - If the given [`BranchOffset`] is not properly initialized. + pub fn init(&mut self, valid_offset: BranchOffset) { + assert!(valid_offset.is_init()); + assert!(!self.is_init()); + *self = valid_offset; + } + + /// Returns the `i32` representation of the [`BranchOffset`]. + pub fn to_i32(self) -> i32 { + self.0 + } +} diff --git a/src/types/drop_keep.rs b/src/types/drop_keep.rs new file mode 100644 index 000000000..98c4cc072 --- /dev/null +++ b/src/types/drop_keep.rs @@ -0,0 +1,79 @@ +use bincode::{Decode, Encode}; +use core::{fmt, fmt::Display}; + +/// Defines how many stack values are going to be dropped and kept after branching. +#[derive(Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +pub struct DropKeep { + pub drop: u16, + pub keep: u16, +} + +impl fmt::Debug for DropKeep { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.debug_struct("DropKeep") + .field("drop", &self.drop()) + .field("keep", &self.keep()) + .finish() + } +} + +/// An error that may occur upon operating on [`DropKeep`]. +#[derive(Debug, Copy, Clone)] +pub enum DropKeepError { + /// The number of kept elements exceeds the engine's limits. + KeepOutOfBounds, + /// The number of dropped elements exceeds the engine's limits. + DropOutOfBounds, +} + +impl Display for DropKeepError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + DropKeepError::KeepOutOfBounds => { + write!(f, "number of kept elements exceeds engine limits") + } + DropKeepError::DropOutOfBounds => { + write!(f, "number of dropped elements exceeds engine limits") + } + } + } +} + +impl DropKeep { + pub fn none() -> Self { + Self { drop: 0, keep: 0 } + } + + /// Returns the number of stack values to keep. + pub fn keep(self) -> u16 { + self.keep + } + + pub fn add_keep(&mut self, delta: u16) { + self.keep += delta; + } + + /// Returns the number of stack values to drop. + pub fn drop(self) -> u16 { + self.drop + } + + /// Returns `true` if the [`DropKeep`] does nothing. + pub fn is_noop(self) -> bool { + self.drop == 0 + } + + /// Creates a new [`DropKeep`] with the given amounts to drop and keep. + /// + /// # Errors + /// + /// - If `keep` is larger than `drop`. + /// - If `keep` is out of bounds. (max 4095) + /// - If `drop` is out of bounds. (delta to keep max 4095) + pub fn new(drop: usize, keep: usize) -> Result { + let keep = u16::try_from(keep).map_err(|_| DropKeepError::KeepOutOfBounds)?; + let drop = u16::try_from(drop).map_err(|_| DropKeepError::KeepOutOfBounds)?; + // Now we can cast `drop` and `keep` to `u16` values safely. + Ok(Self { drop, keep }) + } +} diff --git a/src/types/error.rs b/src/types/error.rs new file mode 100644 index 000000000..2055d1585 --- /dev/null +++ b/src/types/error.rs @@ -0,0 +1,68 @@ +use crate::types::HostError; +use core::fmt::Formatter; + +#[derive(Debug)] +pub enum RwasmError { + MalformedBinary, + UnknownExternalFunction(u32), + ExecutionHalted(i32), + HostInterruption(Box), + FloatsAreDisabled, + NotAllowedInFuelMode, + UnreachableCodeReached, + MemoryOutOfBounds, + TableOutOfBounds, + IndirectCallToNull, + IntegerDivisionByZero, + IntegerOverflow, + BadConversionToInteger, + StackOverflow, + BadSignature, + OutOfFuel, + GrowthOperationLimited, + UnresolvedFunction, + BranchOffsetOutOfBounds, + BlockFuelOutOfBounds, + BranchTableTargetsOutOfBounds, +} + +impl core::fmt::Display for RwasmError { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + match self { + RwasmError::MalformedBinary => write!(f, "malformed binary"), + RwasmError::UnknownExternalFunction(_) => write!(f, "unknown external function"), + RwasmError::ExecutionHalted(_) => write!(f, "execution halted"), + RwasmError::HostInterruption(_) => write!(f, "host interruption"), + RwasmError::FloatsAreDisabled => write!(f, "floats are disabled"), + RwasmError::NotAllowedInFuelMode => write!(f, "not allowed in fuel mode"), + RwasmError::UnreachableCodeReached => write!(f, "unreachable code reached"), + RwasmError::MemoryOutOfBounds => write!(f, "out of bounds memory access"), + RwasmError::TableOutOfBounds => { + write!(f, "undefined element: out of bounds table access") + } + RwasmError::IndirectCallToNull => write!(f, "uninitialized element 2"), + RwasmError::IntegerDivisionByZero => write!(f, "integer divide by zero"), + RwasmError::IntegerOverflow => write!(f, "integer overflow"), + RwasmError::BadConversionToInteger => write!(f, "invalid conversion to integer"), + RwasmError::StackOverflow => write!(f, "call stack exhausted"), + RwasmError::BadSignature => write!(f, "indirect call type mismatch"), + RwasmError::OutOfFuel => write!(f, "out of fuel"), + RwasmError::GrowthOperationLimited => write!(f, "growth operation limited"), + RwasmError::UnresolvedFunction => write!(f, "unresolved function"), + RwasmError::BranchOffsetOutOfBounds => write!(f, "branch offset out of bounds"), + RwasmError::BlockFuelOutOfBounds => write!(f, "block fuel out of bounds"), + RwasmError::BranchTableTargetsOutOfBounds => { + write!(f, "branch table targets are out of bounds") + } + } + } +} + +impl RwasmError { + pub fn unwrap_exit_code(&self) -> i32 { + match self { + RwasmError::ExecutionHalted(exit_code) => *exit_code, + _ => unreachable!("runtime: can't unwrap exit code from error"), + } + } +} diff --git a/src/types/fuel_costs.rs b/src/types/fuel_costs.rs new file mode 100644 index 000000000..42b30e603 --- /dev/null +++ b/src/types/fuel_costs.rs @@ -0,0 +1,105 @@ +use crate::types::{DropKeep, UntypedValue}; +use core::num::NonZeroU64; + +/// Type storing all kinds of fuel costs of instructions. +#[derive(Debug, Copy, Clone)] +pub struct FuelCosts { + /// The base fuel costs for all instructions. + pub base: u64, + /// The fuel cost for instruction operating on Wasm entities. + /// + /// # Note + /// + /// A Wasm entity is one of `func`, `global`, `memory` or `table`. + /// Those instructions are usually a bit more costly since they need + /// multiple indirect accesses through the Wasm instance and store. + pub entity: u64, + /// The fuel cost offset for `memory.load` instructions. + pub load: u64, + /// The fuel cost offset for `memory.store` instructions. + pub store: u64, + /// The fuel cost offset for `call` and `call_indirect` instructions. + pub call: u64, + /// Determines how many moved stack values consume one fuel upon a branch or return + /// instruction. + /// + /// # Note + /// + /// If this is zero, then processing [`DropKeep`] costs nothing. + branch_kept_per_fuel: u64, + /// Determines how many function locals consume one fuel per function call. + /// + /// # Note + /// + /// - This is also applied to all function parameters since they are translated to local + /// variable slots. + /// - If this is zero then processing function locals costs nothing. + func_locals_per_fuel: u64, + /// How many memory bytes can be processed per fuel in a `bulk-memory` instruction? + /// + /// # Note + /// + /// If this is zero, then processing memory bytes costs nothing. + memory_bytes_per_fuel: u64, + /// How many table elements can be processed per fuel in a `bulk-table` instruction? + /// + /// # Note + /// + /// If this is zero, then processing table elements costs nothing. + table_elements_per_fuel: u64, +} + +impl FuelCosts { + /// Returns the fuel consumption of the number of items with costs per items. + fn costs_per(len_items: u64, items_per_fuel: u64) -> u64 { + NonZeroU64::new(items_per_fuel) + .map(|items_per_fuel| len_items / items_per_fuel) + .unwrap_or(0) + } + + /// Returns the fuel consumption for branches and returns using the given [`DropKeep`]. + pub fn fuel_for_drop_keep(&self, drop_keep: DropKeep) -> u64 { + if drop_keep.drop == 0 { + return 0; + } + Self::costs_per(u64::from(drop_keep.keep), self.branch_kept_per_fuel) + } + + /// Returns the fuel consumption for calling a function with the amount of local variables. + /// + /// # Note + /// + /// Function parameters are also treated as local variables. + pub fn fuel_for_locals(&self, locals: u64) -> u64 { + Self::costs_per(locals, self.func_locals_per_fuel) + } + + /// Returns the fuel consumption for processing the amount of memory bytes. + pub fn fuel_for_bytes(&self, bytes: u64) -> u64 { + Self::costs_per(bytes, self.memory_bytes_per_fuel) + } + + /// Returns the fuel consumption for processing the amount of table elements. + pub fn fuel_for_elements(&self, elements: u64) -> u64 { + Self::costs_per(elements, self.table_elements_per_fuel) + } +} + +impl Default for FuelCosts { + fn default() -> Self { + let memory_bytes_per_fuel = 64; + let bytes_per_register = size_of::() as u64; + let registers_per_fuel = memory_bytes_per_fuel / bytes_per_register; + Self { + base: 1, + entity: 1, + load: 1, + store: 1, + call: 1, + func_locals_per_fuel: registers_per_fuel, + branch_kept_per_fuel: registers_per_fuel, + memory_bytes_per_fuel, + table_elements_per_fuel: registers_per_fuel, + } + } +} diff --git a/src/types/func_ref.rs b/src/types/func_ref.rs new file mode 100644 index 000000000..f41a4b587 --- /dev/null +++ b/src/types/func_ref.rs @@ -0,0 +1,41 @@ +use crate::{UntypedValue, FUNC_REF_NULL, FUNC_REF_OFFSET}; + +#[derive(Clone, Debug)] +pub struct FuncRef(u32); + +impl FuncRef { + pub fn new(func_idx: u32) -> Self { + Self(func_idx + FUNC_REF_OFFSET) + } + + pub fn null() -> Self { + Self(FUNC_REF_NULL) + } + + pub fn resolve_index(&self) -> u32 { + assert!(!self.is_null(), "rwasm: resolve of null func ref"); + self.0 - FUNC_REF_OFFSET + } + + pub fn is_null(&self) -> bool { + self.0 == FUNC_REF_NULL + } +} + +impl From for FuncRef { + fn from(value: UntypedValue) -> Self { + let value = value.as_u32(); + if value == 0 { + Self::null() + } else { + Self(value) + } + } +} +impl Into for FuncRef { + fn into(self) -> UntypedValue { + UntypedValue::from(self.0) + } +} + +pub type ExternRef = FuncRef; diff --git a/src/types/host_error.rs b/src/types/host_error.rs new file mode 100644 index 000000000..690cc022c --- /dev/null +++ b/src/types/host_error.rs @@ -0,0 +1,13 @@ +use core::fmt::{Debug, Display}; +use downcast_rs::{impl_downcast, DowncastSync}; + +/// Trait that allows the host to return a custom error. +/// +/// It should be useful for representing custom traps, +/// troubles at instantiation time or other host-specific conditions. +/// +/// Types that implement this trait can automatically be converted to `wasmi::Error` and +/// `wasmi::Trap` and will be represented as a boxed `HostError`. You can then use the various +/// methods on `wasmi::Error` to get your custom error type back +pub trait HostError: 'static + Display + Debug + DowncastSync {} +impl_downcast!(HostError); diff --git a/src/types/import_linker.rs b/src/types/import_linker.rs new file mode 100644 index 000000000..052b7658b --- /dev/null +++ b/src/types/import_linker.rs @@ -0,0 +1,74 @@ +use crate::{ImportName, ValueType}; +use core::ops::Deref; +use hashbrown::HashMap; +use std::ops::DerefMut; + +#[derive(Debug, Default, Clone)] +pub struct ImportLinker { + func_by_name: HashMap, +} + +impl Deref for ImportLinker { + type Target = HashMap; + + fn deref(&self) -> &Self::Target { + &self.func_by_name + } +} +impl DerefMut for ImportLinker { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.func_by_name + } +} + +#[derive(Debug, Clone)] +pub struct ImportLinkerEntity { + pub func_idx: u32, + pub block_fuel: u32, + pub params: &'static [ValueType], + pub result: &'static [ValueType], +} + +impl From<[(&'static str, &'static str, ImportLinkerEntity); N]> for ImportLinker { + fn from(arr: [(&'static str, &'static str, ImportLinkerEntity); N]) -> Self { + Self { + func_by_name: HashMap::from_iter(arr.into_iter().map( + |(module_name, fn_name, entity)| (ImportName::new(module_name, fn_name), entity), + )), + } + } +} + +impl From<[(ImportName, ImportLinkerEntity); N]> for ImportLinker { + fn from(arr: [(ImportName, ImportLinkerEntity); N]) -> Self { + Self { + func_by_name: HashMap::from(arr), + } + } +} + +impl ImportLinker { + pub fn insert_function( + &mut self, + import_name: ImportName, + func_idx: u32, + block_fuel: u32, + params: &'static [ValueType], + result: &'static [ValueType], + ) { + let last_value = self.func_by_name.insert( + import_name, + ImportLinkerEntity { + func_idx, + block_fuel, + params, + result, + }, + ); + assert!(last_value.is_none(), "rwasm: import linker name collision"); + } + + pub fn resolve_by_import_name(&self, import_name: &ImportName) -> Option<&ImportLinkerEntity> { + self.func_by_name.get(import_name) + } +} diff --git a/src/types/import_name.rs b/src/types/import_name.rs new file mode 100644 index 000000000..516d3af06 --- /dev/null +++ b/src/types/import_name.rs @@ -0,0 +1,44 @@ +/// The name or namespace of an imported item. +#[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)] +pub struct ImportName { + /// The name of the [`Module`] that defines the imported item. + /// + /// [`Module`]: [`super::Module`] + pub(crate) module: Box, + /// The name of the imported item within the [`Module`] namespace. + /// + /// [`Module`]: [`super::Module`] + pub(crate) field: Box, +} + +impl core::fmt::Display for ImportName { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + let module_name = &*self.module; + let field_name = &*self.field; + write!(f, "{module_name}::{field_name}") + } +} + +impl ImportName { + /// Creates a new [`Import`] item. + pub fn new(module: &str, field: &str) -> Self { + Self { + module: module.into(), + field: field.into(), + } + } + + /// Returns the name of the [`Module`] that defines the imported item. + /// + /// [`Module`]: [`super::Module`] + pub fn module(&self) -> &str { + &self.module + } + + /// Returns the name of the imported item within the [`Module`] namespace. + /// + /// [`Module`]: [`super::Module`] + pub fn name(&self) -> &str { + &self.field + } +} diff --git a/executor/src/module.rs b/src/types/instruction.rs similarity index 70% rename from executor/src/module.rs rename to src/types/instruction.rs index 92617d040..4f8efa823 100644 --- a/executor/src/module.rs +++ b/src/types/instruction.rs @@ -1,81 +1,23 @@ -use crate::binary_format::{module::decode_rwasm_module, BinaryFormatReader}; -use core::fmt::Formatter; -use rwasm::{ - core::UntypedValue, - engine::{ - bytecode::{ - AddressOffset, - BlockFuel, - BranchOffset, - BranchTableTargets, - DataSegmentIdx, - ElementSegmentIdx, - FuncIdx, - GlobalIdx, - LocalDepth, - SignatureIdx, - TableIdx, - }, +use crate::{ + types::{ + AddressOffset, + BlockFuel, + BranchOffset, + BranchTableTargets, CompiledFunc, + DataSegmentIdx, DropKeep, + ElementSegmentIdx, + FuncIdx, + GlobalIdx, + LocalDepth, + SignatureIdx, + TableIdx, + UntypedValue, }, + StackAlloc, }; - -#[derive(Default)] -pub struct RwasmModule2 { - pub code_section: Vec, - pub instr_data: Vec, - pub memory_section: Vec, - pub func_section: Vec, - pub element_section: Vec, - pub source_pc: u32, - pub func_segments: Vec, -} - -impl RwasmModule2 { - pub fn new_or_empty(sink: &[u8]) -> Self { - if sink.is_empty() { - Self::empty() - } else { - Self::new(sink) - } - } - - pub fn empty() -> Self { - Self { - code_section: vec![Instruction::Return], - instr_data: vec![InstructionData::EmptyData], - memory_section: vec![], - func_section: vec![1], - element_section: vec![], - source_pc: 0, - func_segments: vec![0], - } - } - - pub fn new(sink: &[u8]) -> Self { - let mut binary_format_reader = BinaryFormatReader::new(sink); - let mut module = decode_rwasm_module(&mut binary_format_reader) - .unwrap_or_else(|_| unreachable!("rwasm: invalid module")); - module.instantiate(); - module - } - - pub fn instantiate(&mut self) { - let mut func_segments = vec![0u32]; - let mut total_func_len = 0u32; - for func_len in self.func_section.iter().take(self.func_section.len() - 1) { - total_func_len += *func_len; - func_segments.push(total_func_len); - } - let source_pc = func_segments - .last() - .copied() - .expect("rwasm: empty function section"); - self.source_pc = source_pc; - self.func_segments = func_segments; - } -} +use core::fmt::Formatter; #[derive( Debug, @@ -90,7 +32,7 @@ impl RwasmModule2 { num_enum::TryFromPrimitive, )] #[repr(u8)] -pub enum Instruction { +pub enum Opcode { Unreachable = 0x00, LocalGet = 0x01, LocalSet = 0x02, @@ -293,7 +235,7 @@ pub enum Instruction { } #[cfg(feature = "std")] -impl core::fmt::Display for Instruction { +impl core::fmt::Display for Opcode { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { let name = format!("{:?}", self); let name: Vec<_> = name.split('(').collect(); @@ -301,8 +243,8 @@ impl core::fmt::Display for Instruction { } } -#[derive(Default, Debug)] -pub enum InstructionData { +#[derive(Default, Clone, Copy, Debug, PartialEq, Ord, PartialOrd, Eq, Hash)] +pub enum OpcodeData { #[default] EmptyData, LocalDepth(LocalDepth), @@ -319,7 +261,22 @@ pub enum InstructionData { TableIdx(TableIdx), ElementSegmentIdx(ElementSegmentIdx), UntypedValue(UntypedValue), - StackAlloc { - max_stack_height: u32, - }, + StackAlloc(StackAlloc), +} + +impl OpcodeData { + pub fn update_branch_offset>(&mut self, offset: I) { + if let OpcodeData::BranchOffset(old_offset) = self { + *old_offset = offset.into(); + } else { + unreachable!("rwasm: opcode data is not a branch offset") + } + } +} + +#[derive(Default, Debug, PartialEq, Clone, Eq, Hash)] +pub struct OpcodeMeta { + pub index: usize, + pub pos: usize, + pub opcode: u8, } diff --git a/src/types/instruction_set.rs b/src/types/instruction_set.rs new file mode 100644 index 000000000..384836e5d --- /dev/null +++ b/src/types/instruction_set.rs @@ -0,0 +1,446 @@ +use crate::types::{ + AddressOffset, + BlockFuel, + BranchOffset, + BranchTableTargets, + CompiledFunc, + DataSegmentIdx, + DropKeep, + ElementSegmentIdx, + FuncIdx, + GlobalIdx, + LocalDepth, + Opcode, + OpcodeData, + SignatureIdx, + StackAlloc, + TableIdx, + UntypedValue, +}; +use alloc::vec::Vec; +use bincode::{ + de::Decoder, + enc::Encoder, + error::{AllowedEnumVariants, DecodeError, EncodeError}, + Decode, + Encode, +}; +use core::ops::{Deref, DerefMut}; +use num_enum::TryFromPrimitive; + +#[derive(Debug, PartialEq, Clone, Eq, Hash)] +pub struct InstructionSet { + pub instr: Vec<(Opcode, OpcodeData)>, +} + +impl Deref for InstructionSet { + type Target = Vec<(Opcode, OpcodeData)>; + + fn deref(&self) -> &Self::Target { + &self.instr + } +} + +impl DerefMut for InstructionSet { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.instr + } +} + +impl Default for InstructionSet { + fn default() -> Self { + Self { + instr: vec![(Opcode::Return, OpcodeData::DropKeep(DropKeep::default()))], + } + } +} + +macro_rules! impl_opcode { + ($opcode:ident($data_type:ident)) => { + paste::paste! { + pub fn [< op_ $opcode:snake >]>(&mut self, value: I) -> u32 { + self.push( + Opcode::$opcode, + OpcodeData::$data_type(value.into()), + ) + } + } + }; + ($opcode:ident) => { + paste::paste! { + pub fn [< op_ $opcode:snake >](&mut self) -> u32 { + self.push(Opcode::$opcode, OpcodeData::EmptyData) + } + } + }; +} + +impl InstructionSet { + pub fn new() -> Self { + Self::default() + } + + pub fn push(&mut self, opcode: Opcode, data: OpcodeData) -> u32 { + let idx = self.instr.len() as u32; + self.instr.push((opcode, data)); + idx + } + + pub fn clear(&mut self) { + self.instr.clear(); + } + + fn is_return_last(&self) -> bool { + self.instr + .last() + .map(|instr| match instr.0 { + Opcode::Return => true, + _ => false, + }) + .unwrap_or_default() + } + + pub fn finalize(&mut self, inject_return: bool) { + // inject return in the end (it's used mostly for unit tests) + if inject_return && !self.is_return_last() { + self.op_return(DropKeep::default()); + } + } + + impl_opcode!(LocalGet(LocalDepth)); + impl_opcode!(LocalSet(LocalDepth)); + impl_opcode!(LocalTee(LocalDepth)); + impl_opcode!(Br(BranchOffset)); + impl_opcode!(BrIfEqz(BranchOffset)); + impl_opcode!(BrIfNez(BranchOffset)); + impl_opcode!(BrAdjust(BranchOffset)); + impl_opcode!(BrAdjustIfNez(BranchOffset)); + impl_opcode!(BrTable(BranchTableTargets)); + impl_opcode!(Unreachable); + impl_opcode!(ConsumeFuel(BlockFuel)); + impl_opcode!(Return(DropKeep)); + impl_opcode!(ReturnIfNez(DropKeep)); + impl_opcode!(ReturnCallInternal(CompiledFunc)); + impl_opcode!(ReturnCall(FuncIdx)); + impl_opcode!(ReturnCallIndirect(SignatureIdx)); + impl_opcode!(CallInternal(CompiledFunc)); + impl_opcode!(Call(FuncIdx)); + impl_opcode!(CallIndirect(SignatureIdx)); + impl_opcode!(SignatureCheck(SignatureIdx)); + impl_opcode!(Drop); + impl_opcode!(Select); + impl_opcode!(GlobalGet(GlobalIdx)); + impl_opcode!(GlobalSet(GlobalIdx)); + impl_opcode!(I32Load(AddressOffset)); + impl_opcode!(I64Load(AddressOffset)); + impl_opcode!(F32Load(AddressOffset)); + impl_opcode!(F64Load(AddressOffset)); + impl_opcode!(I32Load8S(AddressOffset)); + impl_opcode!(I32Load8U(AddressOffset)); + impl_opcode!(I32Load16S(AddressOffset)); + impl_opcode!(I32Load16U(AddressOffset)); + impl_opcode!(I64Load8S(AddressOffset)); + impl_opcode!(I64Load8U(AddressOffset)); + impl_opcode!(I64Load16S(AddressOffset)); + impl_opcode!(I64Load16U(AddressOffset)); + impl_opcode!(I64Load32S(AddressOffset)); + impl_opcode!(I64Load32U(AddressOffset)); + impl_opcode!(I32Store(AddressOffset)); + impl_opcode!(I64Store(AddressOffset)); + impl_opcode!(F32Store(AddressOffset)); + impl_opcode!(F64Store(AddressOffset)); + impl_opcode!(I32Store8(AddressOffset)); + impl_opcode!(I32Store16(AddressOffset)); + impl_opcode!(I64Store8(AddressOffset)); + impl_opcode!(I64Store16(AddressOffset)); + impl_opcode!(I64Store32(AddressOffset)); + impl_opcode!(MemorySize); + impl_opcode!(MemoryGrow); + impl_opcode!(MemoryFill); + impl_opcode!(MemoryCopy); + impl_opcode!(MemoryInit(DataSegmentIdx)); + impl_opcode!(DataDrop(DataSegmentIdx)); + impl_opcode!(TableSize(TableIdx)); + impl_opcode!(TableGrow(TableIdx)); + impl_opcode!(TableFill(TableIdx)); + impl_opcode!(TableGet(TableIdx)); + impl_opcode!(TableSet(TableIdx)); + impl_opcode!(TableCopy(TableIdx)); + impl_opcode!(TableInit(TableIdx)); + pub fn op_table_init_checked(&mut self, table_idx: TableIdx, elem_idx: ElementSegmentIdx) { + self.push(Opcode::TableInit, OpcodeData::ElementSegmentIdx(elem_idx)); + self.push(Opcode::TableGet, OpcodeData::TableIdx(table_idx)); + } + impl_opcode!(ElemDrop(ElementSegmentIdx)); + impl_opcode!(RefFunc(FuncIdx)); + impl_opcode!(I32Const(UntypedValue)); + impl_opcode!(I64Const(UntypedValue)); + impl_opcode!(F32Const(UntypedValue)); + impl_opcode!(F64Const(UntypedValue)); + impl_opcode!(I32Eqz); + impl_opcode!(I32Eq); + impl_opcode!(I32Ne); + impl_opcode!(I32LtS); + impl_opcode!(I32LtU); + impl_opcode!(I32GtS); + impl_opcode!(I32GtU); + impl_opcode!(I32LeS); + impl_opcode!(I32LeU); + impl_opcode!(I32GeS); + impl_opcode!(I32GeU); + impl_opcode!(I64Eqz); + impl_opcode!(I64Eq); + impl_opcode!(I64Ne); + impl_opcode!(I64LtS); + impl_opcode!(I64LtU); + impl_opcode!(I64GtS); + impl_opcode!(I64GtU); + impl_opcode!(I64LeS); + impl_opcode!(I64LeU); + impl_opcode!(I64GeS); + impl_opcode!(I64GeU); + impl_opcode!(F32Eq); + impl_opcode!(F32Ne); + impl_opcode!(F32Lt); + impl_opcode!(F32Gt); + impl_opcode!(F32Le); + impl_opcode!(F32Ge); + impl_opcode!(F64Eq); + impl_opcode!(F64Ne); + impl_opcode!(F64Lt); + impl_opcode!(F64Gt); + impl_opcode!(F64Le); + impl_opcode!(F64Ge); + impl_opcode!(I32Clz); + impl_opcode!(I32Ctz); + impl_opcode!(I32Popcnt); + impl_opcode!(I32Add); + impl_opcode!(I32Sub); + impl_opcode!(I32Mul); + impl_opcode!(I32DivS); + impl_opcode!(I32DivU); + impl_opcode!(I32RemS); + impl_opcode!(I32RemU); + impl_opcode!(I32And); + impl_opcode!(I32Or); + impl_opcode!(I32Xor); + impl_opcode!(I32Shl); + impl_opcode!(I32ShrS); + impl_opcode!(I32ShrU); + impl_opcode!(I32Rotl); + impl_opcode!(I32Rotr); + impl_opcode!(I64Clz); + impl_opcode!(I64Ctz); + impl_opcode!(I64Popcnt); + impl_opcode!(I64Add); + impl_opcode!(I64Sub); + impl_opcode!(I64Mul); + impl_opcode!(I64DivS); + impl_opcode!(I64DivU); + impl_opcode!(I64RemS); + impl_opcode!(I64RemU); + impl_opcode!(I64And); + impl_opcode!(I64Or); + impl_opcode!(I64Xor); + impl_opcode!(I64Shl); + impl_opcode!(I64ShrS); + impl_opcode!(I64ShrU); + impl_opcode!(I64Rotl); + impl_opcode!(I64Rotr); + impl_opcode!(F32Abs); + impl_opcode!(F32Neg); + impl_opcode!(F32Ceil); + impl_opcode!(F32Floor); + impl_opcode!(F32Trunc); + impl_opcode!(F32Nearest); + impl_opcode!(F32Sqrt); + impl_opcode!(F32Add); + impl_opcode!(F32Sub); + impl_opcode!(F32Mul); + impl_opcode!(F32Div); + impl_opcode!(F32Min); + impl_opcode!(F32Max); + impl_opcode!(F32Copysign); + impl_opcode!(F64Abs); + impl_opcode!(F64Neg); + impl_opcode!(F64Ceil); + impl_opcode!(F64Floor); + impl_opcode!(F64Trunc); + impl_opcode!(F64Nearest); + impl_opcode!(F64Sqrt); + impl_opcode!(F64Add); + impl_opcode!(F64Sub); + impl_opcode!(F64Mul); + impl_opcode!(F64Div); + impl_opcode!(F64Min); + impl_opcode!(F64Max); + impl_opcode!(F64Copysign); + impl_opcode!(I32WrapI64); + impl_opcode!(I32TruncF32S); + impl_opcode!(I32TruncF32U); + impl_opcode!(I32TruncF64S); + impl_opcode!(I32TruncF64U); + impl_opcode!(I64ExtendI32S); + impl_opcode!(I64ExtendI32U); + impl_opcode!(I64TruncF32S); + impl_opcode!(I64TruncF32U); + impl_opcode!(I64TruncF64S); + impl_opcode!(I64TruncF64U); + impl_opcode!(F32ConvertI32S); + impl_opcode!(F32ConvertI32U); + impl_opcode!(F32ConvertI64S); + impl_opcode!(F32ConvertI64U); + impl_opcode!(F32DemoteF64); + impl_opcode!(F64ConvertI32S); + impl_opcode!(F64ConvertI32U); + impl_opcode!(F64ConvertI64S); + impl_opcode!(F64ConvertI64U); + impl_opcode!(F64PromoteF32); + impl_opcode!(I32Extend8S); + impl_opcode!(I32Extend16S); + impl_opcode!(I64Extend8S); + impl_opcode!(I64Extend16S); + impl_opcode!(I64Extend32S); + impl_opcode!(I32TruncSatF32S); + impl_opcode!(I32TruncSatF32U); + impl_opcode!(I32TruncSatF64S); + impl_opcode!(I32TruncSatF64U); + impl_opcode!(I64TruncSatF32S); + impl_opcode!(I64TruncSatF32U); + impl_opcode!(I64TruncSatF64S); + impl_opcode!(I64TruncSatF64U); + impl_opcode!(StackAlloc(StackAlloc)); +} + +#[macro_export] +macro_rules! instruction_set_internal { + // Nothing left to do + ($code:ident, ) => {}; + ($code:ident, $x:ident ($v:expr) $($rest:tt)*) => { + _ = crate::types::Opcode::$x; + paste::paste! { + $code.[< op_ $x:snake >]($v); + } + $crate::instruction_set_internal!($code, $($rest)*); + }; + // Default opcode without any inputs + ($code:ident, $x:ident $($rest:tt)*) => { + _ = crate::types::Opcode::$x; + paste::paste! { + $code.[< op_ $x:snake >](); + } + $crate::instruction_set_internal!($code, $($rest)*); + }; + // Function calls + ($code:ident, .$function:ident ($($args:expr),* $(,)?) $($rest:tt)*) => { + $code.$function($($args,)*); + $crate::instruction_set_internal!($code, $($rest)*); + }; +} + +#[macro_export] +macro_rules! instruction_set { + ($($args:tt)*) => {{ + let mut code = $crate::types::InstructionSet::new(); + $crate::instruction_set_internal!(code, $($args)*); + code + }}; +} + +impl Encode for InstructionSet { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + let length = self.instr.len() as u64; + Encode::encode(&length, encoder)?; + for instr in &self.instr { + let instr_value = instr.0 as u8; + Encode::encode(&instr_value, encoder)?; + encode_instruction_data(&instr.1, encoder)?; + } + Ok(()) + } +} + +impl Decode for InstructionSet { + fn decode>(decoder: &mut D) -> Result { + let length: u64 = Decode::decode(decoder)?; + let mut instr: Vec<(Opcode, OpcodeData)> = Vec::with_capacity(length as usize); + for _ in 0..length as usize { + let instr_value: u8 = Decode::decode(decoder)?; + let opcode = Opcode::try_from_primitive(instr_value) + .map_err(|_| instruction_not_found_err(instr_value))?; + let opcode_data = decode_instruction_data(&opcode, decoder)?; + instr.push((opcode, opcode_data)); + } + Ok(Self { instr }) + } +} + +fn encode_instruction_data( + instruction_data: &OpcodeData, + encoder: &mut E, +) -> Result<(), EncodeError> { + match instruction_data { + OpcodeData::EmptyData => Ok(()), + OpcodeData::LocalDepth(value) => Encode::encode(&value, encoder), + OpcodeData::BranchOffset(value) => Encode::encode(&value, encoder), + OpcodeData::BranchTableTargets(value) => Encode::encode(&value, encoder), + OpcodeData::BlockFuel(value) => Encode::encode(&value, encoder), + OpcodeData::DropKeep(value) => Encode::encode(&value, encoder), + OpcodeData::CompiledFunc(value) => Encode::encode(&value, encoder), + OpcodeData::FuncIdx(value) => Encode::encode(&value, encoder), + OpcodeData::SignatureIdx(value) => Encode::encode(&value, encoder), + OpcodeData::GlobalIdx(value) => Encode::encode(&value, encoder), + OpcodeData::AddressOffset(value) => Encode::encode(&value, encoder), + OpcodeData::DataSegmentIdx(value) => Encode::encode(&value, encoder), + OpcodeData::TableIdx(value) => Encode::encode(&value, encoder), + OpcodeData::ElementSegmentIdx(value) => Encode::encode(&value, encoder), + OpcodeData::UntypedValue(value) => Encode::encode(&value, encoder), + OpcodeData::StackAlloc(value) => Encode::encode(&value, encoder), + } +} + +fn decode_instruction_data>( + instruction: &Opcode, + decoder: &mut D, +) -> Result { + use Opcode::*; + let instruction_data = match instruction { + LocalGet | LocalSet | LocalTee => OpcodeData::LocalDepth(Decode::decode(decoder)?), + Br | BrIfEqz | BrIfNez | BrAdjust | BrAdjustIfNez => { + OpcodeData::BranchOffset(Decode::decode(decoder)?) + } + BrTable => OpcodeData::BranchTableTargets(Decode::decode(decoder)?), + ConsumeFuel => OpcodeData::BlockFuel(Decode::decode(decoder)?), + Return | ReturnIfNez => OpcodeData::DropKeep(Decode::decode(decoder)?), + ReturnCallInternal | CallInternal => OpcodeData::CompiledFunc(Decode::decode(decoder)?), + ReturnCall | Call | RefFunc => OpcodeData::FuncIdx(Decode::decode(decoder)?), + ReturnCallIndirect | CallIndirect | SignatureCheck => { + OpcodeData::SignatureIdx(Decode::decode(decoder)?) + } + GlobalGet | GlobalSet => OpcodeData::GlobalIdx(Decode::decode(decoder)?), + I32Load | I64Load | F32Load | F64Load | I32Load8S | I32Load8U | I32Load16S | I32Load16U + | I64Load8S | I64Load8U | I64Load16S | I64Load16U | I64Load32S | I64Load32U | I32Store + | I64Store | F32Store | F64Store | I32Store8 | I32Store16 | I64Store8 | I64Store16 + | I64Store32 => OpcodeData::AddressOffset(Decode::decode(decoder)?), + MemoryInit | DataDrop => OpcodeData::DataSegmentIdx(Decode::decode(decoder)?), + TableSize | TableGrow | TableFill | TableGet | TableSet | TableCopy => { + OpcodeData::TableIdx(Decode::decode(decoder)?) + } + TableInit | ElemDrop => OpcodeData::ElementSegmentIdx(Decode::decode(decoder)?), + I32Const | I64Const | F32Const | F64Const => { + OpcodeData::UntypedValue(Decode::decode(decoder)?) + } + StackAlloc => OpcodeData::StackAlloc(Decode::decode(decoder)?), + _ => OpcodeData::EmptyData, + }; + Ok(instruction_data) +} + +fn instruction_not_found_err(instr_value: u8) -> DecodeError { + static RANGE: AllowedEnumVariants = AllowedEnumVariants::Range { min: 0, max: 0xc6 }; + DecodeError::UnexpectedVariant { + type_name: "Instruction", + allowed: &RANGE, + found: instr_value as u32, + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs new file mode 100644 index 000000000..770334292 --- /dev/null +++ b/src/types/mod.rs @@ -0,0 +1,71 @@ +mod address_offset; +mod block_fuel; +mod branch_offset; +mod drop_keep; +mod error; +mod fuel_costs; +mod func_ref; +mod host_error; +mod import_linker; +mod import_name; +mod instruction; +mod instruction_set; +mod module; +mod nan_preserving_float; +mod units; +mod untyped_value; +mod utils; +mod value; + +pub const N_DEFAULT_STACK_SIZE: usize = 4096; +pub const N_MAX_STACK_SIZE: usize = 4096; +pub const N_MAX_TABLE_SIZE: usize = 1024; +pub const N_MAX_RECURSION_DEPTH: usize = 1024; + +pub const N_MAX_DATA_SEGMENTS: usize = 1024; + +pub const DEFAULT_TABLE_LIMIT: usize = 1024; +pub const DEFAULT_MEMORY_LIMIT: usize = 10000; + +pub const DEFAULT_MIN_VALUE_STACK_HEIGHT: usize = 1024; +pub const DEFAULT_MAX_VALUE_STACK_HEIGHT: usize = 1024; + +pub const FUNC_REF_NULL: u32 = 0; +pub const FUNC_REF_OFFSET: u32 = 1000; + +/// This constant is driven by WebAssembly standard, default +/// memory page size is 64kB +pub const N_BYTES_PER_MEMORY_PAGE: u32 = 65536; + +/// We have a hard limit for max possible memory used +/// that is equal to ~64mB +pub const N_MAX_MEMORY_PAGES: u32 = 1024; + +/// To optimize a proving process, we have to limit the max +/// number of pages, tables, etc. We found 1024 is enough. +pub const N_MAX_TABLES: u32 = 1024; + +pub const N_MAX_STACK_HEIGHT: usize = 4096; + +pub const DEFAULT_MEMORY_INDEX: u32 = 0; + +pub type CompiledFunc = u32; + +pub use address_offset::*; +pub use block_fuel::*; +pub use branch_offset::*; +pub use drop_keep::*; +pub use error::*; +pub use fuel_costs::*; +pub use func_ref::*; +pub use host_error::*; +pub use import_linker::*; +pub use import_name::*; +pub use instruction::*; +pub use instruction_set::*; +pub use module::*; +pub use nan_preserving_float::*; +pub use units::*; +pub use untyped_value::*; +pub use utils::*; +pub use value::*; diff --git a/src/types/module.rs b/src/types/module.rs new file mode 100644 index 000000000..4d5278051 --- /dev/null +++ b/src/types/module.rs @@ -0,0 +1,128 @@ +use crate::types::InstructionSet; +use bincode::{ + de::Decoder, + enc::Encoder, + error::{DecodeError, EncodeError}, + Decode, + Encode, +}; + +#[derive(Default, Debug, PartialEq)] +pub struct RwasmModule { + pub code_section: InstructionSet, + pub memory_section: Vec, + pub element_section: Vec, + pub source_pc: u32, + pub func_section: Vec, +} + +impl RwasmModule { + pub fn new_or_empty(sink: &[u8]) -> Self { + if sink.is_empty() { + Self::empty() + } else { + Self::new(sink) + } + } + + pub fn empty() -> Self { + Self { + code_section: InstructionSet::default(), + memory_section: vec![], + element_section: vec![], + source_pc: 0, + func_section: vec![0], + } + } + + pub fn new(sink: &[u8]) -> Self { + let module: RwasmModule; + (module, _) = bincode::decode_from_slice(sink, bincode::config::legacy()) + .unwrap_or_else(|_| unreachable!("rwasm: malformed rwasm binary")); + module + } + + pub fn instantiate(&mut self) { + let source_pc = self + .func_section + .last() + .copied() + .expect("rwasm: empty function section"); + self.source_pc = source_pc; + } +} + +/// Rwasm magic bytes 0xef52 +const RWASM_MAGIC_BYTE_0: u8 = 0xef; +const RWASM_MAGIC_BYTE_1: u8 = 0x52; + +/// Rwasm binary version that is equal to the 'R' symbol (0x52 in hex) +const RWASM_VERSION_V1: u8 = 0x01; + +impl Encode for RwasmModule { + fn encode(&self, encoder: &mut E) -> Result<(), EncodeError> { + Encode::encode(&RWASM_MAGIC_BYTE_0, encoder)?; + Encode::encode(&RWASM_MAGIC_BYTE_1, encoder)?; + Encode::encode(&RWASM_VERSION_V1, encoder)?; + Encode::encode(&self.code_section, encoder)?; + Encode::encode(&self.memory_section, encoder)?; + Encode::encode(&self.element_section, encoder)?; + Encode::encode(&self.source_pc, encoder)?; + Encode::encode(&self.func_section, encoder)?; + Ok(()) + } +} + +impl Decode for RwasmModule { + fn decode>(decoder: &mut D) -> Result { + let sig0: u8 = Decode::decode(decoder)?; + let sig1: u8 = Decode::decode(decoder)?; + if sig0 != RWASM_MAGIC_BYTE_0 || sig1 != RWASM_MAGIC_BYTE_1 { + return Err(DecodeError::Other("rwasm: invalid magic bytes")); + } + let version: u8 = Decode::decode(decoder)?; + if version != RWASM_VERSION_V1 { + return Err(DecodeError::Other("rwasm: not supported version")); + } + let code_section: InstructionSet = Decode::decode(decoder)?; + let memory_section: Vec = Decode::decode(decoder)?; + let element_section: Vec = Decode::decode(decoder)?; + let source_pc: u32 = Decode::decode(decoder)?; + let func_segments: Vec = Decode::decode(decoder)?; + Ok(Self { + code_section, + memory_section, + element_section, + source_pc, + func_section: func_segments, + }) + } +} + +#[cfg(test)] +mod tests { + use crate::{instruction_set, types::RwasmModule}; + + #[test] + fn test_module_encoding() { + let module = RwasmModule { + code_section: instruction_set! { + I32Const(100) + I32Const(20) + I32Add + I32Const(3) + I32Add + Drop + }, + memory_section: Default::default(), + func_section: vec![0, 1, 2, 3, 4], + element_section: vec![5, 6, 7, 8, 9], + source_pc: 7, + }; + let encoded_module = bincode::encode_to_vec(&module, bincode::config::legacy()).unwrap(); + let module2: RwasmModule; + (module2, _) = + bincode::decode_from_slice(&encoded_module, bincode::config::legacy()).unwrap(); + assert_eq!(module, module2); + } +} diff --git a/src/types/nan_preserving_float.rs b/src/types/nan_preserving_float.rs new file mode 100644 index 000000000..b44a81106 --- /dev/null +++ b/src/types/nan_preserving_float.rs @@ -0,0 +1,270 @@ +macro_rules! impl_binop { + ($for:ty, $is:ty, $op:ident, $func_name:ident) => { + impl> ::core::ops::$op for $for { + type Output = Self; + + #[inline] + fn $func_name(self, other: T) -> Self { + Self( + ::core::ops::$op::$func_name( + <$is>::from_bits(self.0), + <$is>::from_bits(other.into().0), + ) + .to_bits(), + ) + } + } + }; +} + +macro_rules! float { + ( + $( #[$docs:meta] )* + struct $for:ident($rep:ty as $is:ty); + ) => { + float!( + $(#[$docs])* + struct $for($rep as $is, #bits = 1 << (::core::mem::size_of::<$is>() * 8 - 1)); + ); + }; + ( + $( #[$docs:meta] )* + struct $for:ident($rep:ty as $is:ty, #bits = $sign_bit:expr); + ) => { + $(#[$docs])* + #[derive(Copy, Clone)] + pub struct $for($rep); + + impl_binop!($for, $is, Add, add); + impl_binop!($for, $is, Sub, sub); + impl_binop!($for, $is, Mul, mul); + impl_binop!($for, $is, Div, div); + impl_binop!($for, $is, Rem, rem); + + impl $for { + /// Creates a float from its underlying bits. + #[inline] + pub fn from_bits(other: $rep) -> Self { + Self(other) + } + + /// Returns the underlying bits of the float. + #[inline] + pub fn to_bits(self) -> $rep { + self.0 + } + + /// Creates a float from the respective primitive float type. + #[inline] + pub fn from_float(float: $is) -> Self { + Self(float.to_bits()) + } + + /// Returns the respective primitive float type. + #[inline] + pub fn to_float(self) -> $is { + <$is>::from_bits(self.0) + } + + /// Returns `true` if the float is not a number (NaN). + #[inline] + pub fn is_nan(self) -> ::core::primitive::bool { + self.to_float().is_nan() + } + + /// Returns the absolute value of the float. + #[must_use] + #[inline] + pub fn abs(self) -> Self { + Self(self.0 & !$sign_bit) + } + + /// Returns the fractional part of the float. + #[must_use] + #[inline] + pub fn fract(self) -> Self { + Self::from_float( + ::num_traits::float::FloatCore::fract(self.to_float()) + ) + } + + /// Returns the minimum float between `self` and `other`. + #[must_use] + #[inline] + pub fn min(self, other: Self) -> Self { + Self::from(self.to_float().min(other.to_float())) + } + + /// Returns the maximum float between `self` and `other`. + #[must_use] + #[inline] + pub fn max(self, other: Self) -> Self { + Self::from(self.to_float().max(other.to_float())) + } + } + + impl ::core::convert::From<$is> for $for { + #[inline] + fn from(float: $is) -> $for { + Self::from_float(float) + } + } + + impl ::core::convert::From<$for> for $is { + #[inline] + fn from(float: $for) -> $is { + float.to_float() + } + } + + impl ::core::ops::Neg for $for { + type Output = Self; + + #[inline] + fn neg(self) -> Self { + Self(self.0 ^ $sign_bit) + } + } + + impl + ::core::marker::Copy> ::core::cmp::PartialEq for $for { + #[inline] + fn eq(&self, other: &T) -> ::core::primitive::bool { + <$is as ::core::convert::From>::from(*self) + .eq(&<$is as ::core::convert::From>::from((*other).into())) + } + } + + impl + ::core::marker::Copy> ::core::cmp::PartialOrd for $for { + #[inline] + fn partial_cmp(&self, other: &T) -> ::core::option::Option<::core::cmp::Ordering> { + <$is as ::core::convert::From>::from(*self) + .partial_cmp(&<$is as ::core::convert::From>::from((*other).into())) + } + } + + impl ::core::fmt::Debug for $for { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + <$is as ::core::fmt::Debug>::fmt( + &<$is as ::core::convert::From>::from(*self), + f, + ) + } + } + + impl ::core::fmt::Display for $for { + fn fmt(&self, f: &mut ::core::fmt::Formatter) -> ::core::fmt::Result { + <$is as ::core::fmt::Display>::fmt( + &<$is as ::core::convert::From>::from(*self), + f, + ) + } + } + }; +} + +float! { + /// A NaN preserving `f32` type. + struct F32(u32 as f32); +} + +float! { + /// A NaN preserving `f64` type. + struct F64(u64 as f64); +} + +impl From for F32 { + #[inline] + fn from(other: u32) -> Self { + Self::from_bits(other) + } +} + +impl From for u32 { + #[inline] + fn from(other: F32) -> Self { + other.to_bits() + } +} + +impl From for F64 { + #[inline] + fn from(other: u64) -> Self { + Self::from_bits(other) + } +} + +impl From for u64 { + #[inline] + fn from(other: F64) -> Self { + other.to_bits() + } +} + +#[cfg(test)] +mod tests { + extern crate rand; + + use self::rand::Rng; + use super::{F32, F64}; + use core::{ + fmt::Debug, + iter, + ops::{Add, Div, Mul, Neg, Sub}, + }; + + fn test_ops(iter: I) + where + T: Add + + Div + + Mul + + Sub + + Neg + + Copy + + Debug + + PartialEq, + F: Into + + Add + + Div + + Mul + + Sub + + Neg + + Copy + + Debug, + I: IntoIterator, + { + for (a, b) in iter { + assert_eq!((a + b).into(), a.into() + b.into()); + assert_eq!((a - b).into(), a.into() - b.into()); + assert_eq!((a * b).into(), a.into() * b.into()); + assert_eq!((a / b).into(), a.into() / b.into()); + assert_eq!((-a).into(), -a.into()); + assert_eq!((-b).into(), -b.into()); + } + } + + #[test] + fn test_ops_f32() { + let mut rng = rand::rng(); + let iter = iter::repeat(()).map(|_| rng.random()); + + test_ops::(iter.take(1000)); + } + + #[test] + fn test_ops_f64() { + let mut rng = rand::rng(); + let iter = iter::repeat(()).map(|_| rng.random()); + + test_ops::(iter.take(1000)); + } + + #[test] + fn test_neg_nan_f32() { + assert_eq!((-F32(0xff80_3210)).0, 0x7f80_3210); + } + + #[test] + fn test_neg_nan_f64() { + assert_eq!((-F64(0xff80_3210_0000_0000)).0, 0x7f80_3210_0000_0000); + } +} diff --git a/src/types/units.rs b/src/types/units.rs new file mode 100644 index 000000000..18484bc8b --- /dev/null +++ b/src/types/units.rs @@ -0,0 +1,324 @@ +/// An amount of linear memory pages. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Pages(u32); + +impl Pages { + /// The maximum number of pages on the `wasm32` target. + /// + /// # Note + /// + /// This is the maximum since WebAssembly is a 32-bit platform + /// and a page is 2^16 bytes in size. Therefore, there can be at + /// most 2^16 pages of a single linear memory so that all bytes + /// are still accessible. + pub const fn max() -> Self { + Self(65536) // 2^16 + } + + pub fn into_inner(self) -> u32 { + self.0 + } +} + +impl From for Pages { + /// Creates an `amount` of [`Pages`]. + /// + /// # Note + /// + /// This is infallible since `u16` cannot represent invalid amounts + /// of [`Pages`]. However, `u16` can also not represent [`Pages::max()`]. + /// + /// [`Pages::max()`]: struct.Pages.html#method.max + fn from(amount: u16) -> Self { + Self(u32::from(amount)) + } +} + +impl Pages { + /// Creates a new amount of [`Pages`] if the amount is within bounds. + /// + /// Returns `None` if the given `amount` of [`Pages`] exceeds [`Pages::max()`]. + /// + /// [`Pages::max()`]: struct.Pages.html#method.max + pub fn new(amount: u32) -> Option { + if amount > u32::from(Self::max()) { + return None; + } + Some(Self(amount)) + } + + pub const fn new_unchecked(amount: u32) -> Self { + Self(amount) + } + + /// Adds the given amount of pages to `self`. + /// + /// Returns `Some` if the result is within bounds and `None` otherwise. + pub fn checked_add(self, rhs: T) -> Option + where + T: Into, + { + let lhs: u32 = self.into(); + let rhs: u32 = rhs.into(); + lhs.checked_add(rhs).and_then(Self::new) + } + + /// Substracts the given amount of pages from `self`. + /// + /// Returns `None` if the subtraction underflows or the result is out of bounds. + pub fn checked_sub(self, rhs: T) -> Option + where + T: Into, + { + let lhs: u32 = self.into(); + let rhs: u32 = rhs.into(); + lhs.checked_sub(rhs).and_then(Self::new) + } + + /// Returns the amount of bytes required for the amount of [`Pages`]. + /// + /// Returns `None` if the amount of pages represented by `self` cannot + /// be represented as bytes on the executing platform. + pub fn to_bytes(self) -> Option { + Bytes::new(self).map(Into::into) + } +} + +impl From for u32 { + fn from(pages: Pages) -> Self { + pages.0 + } +} + +/// An amount of bytes of a linear memory. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +#[repr(transparent)] +pub struct Bytes(usize); + +impl Bytes { + /// A 16-bit platform cannot represent the size of a single Wasm page. + const fn max16() -> u64 { + i16::MAX as u64 + 1 + } + + /// A 32-bit platform can represent at most i32::MAX + 1 Wasm pages. + const fn max32() -> u64 { + i32::MAX as u64 + 1 + } + + /// A 64-bit platform can represent all possible u32::MAX + 1 Wasm pages. + const fn max64() -> u64 { + u32::MAX as u64 + 1 + } + + /// The bytes per WebAssembly linear memory page. + /// + /// # Note + /// + /// As mandated by the WebAssembly specification every linear memory page + /// has exactly 2^16 (65536) bytes. + pub const fn per_page() -> Self { + Self(65536) // 2^16 + } + + /// Creates [`Bytes`] from the given amount of [`Pages`] if possible. + /// + /// Returns `None` if the amount of bytes is out of bounds. This may + /// happen for example when trying to allocate bytes for more than + /// `i16::MAX + 1` pages on a 32-bit platform since that amount would + /// not be representable by a pointer sized `usize`. + fn new(pages: Pages) -> Option { + if cfg!(target_pointer_width = "16") { + Self::new16(pages) + } else if cfg!(target_pointer_width = "32") { + Self::new32(pages) + } else if cfg!(target_pointer_width = "64") { + Self::new64(pages) + } else { + None + } + } + + /// Creates [`Bytes`] from the given amount of [`Pages`] as if + /// on a 16-bit platform if possible. + /// + /// Returns `None` otherwise. + /// + /// # Note + /// + /// This API exists in isolation for cross-platform testing purposes. + fn new16(pages: Pages) -> Option { + Self::new_impl(pages, Bytes::max16()) + } + + /// Creates [`Bytes`] from the given amount of [`Pages`] as if + /// on a 32-bit platform if possible. + /// + /// Returns `None` otherwise. + /// + /// # Note + /// + /// This API exists in isolation for cross-platform testing purposes. + fn new32(pages: Pages) -> Option { + Self::new_impl(pages, Bytes::max32()) + } + + /// Creates [`Bytes`] from the given amount of [`Pages`] as if + /// on a 64-bit platform if possible. + /// + /// Returns `None` otherwise. + /// + /// # Note + /// + /// This API exists in isolation for cross-platform testing purposes. + fn new64(pages: Pages) -> Option { + Self::new_impl(pages, Bytes::max64()) + } + + /// Actual underlying implementation of [`Bytes::new`]. + fn new_impl(pages: Pages, max: u64) -> Option { + let pages = u64::from(u32::from(pages)); + let bytes_per_page = usize::from(Self::per_page()) as u64; + let bytes = pages + .checked_mul(bytes_per_page) + .filter(|&amount| amount <= max)?; + Some(Self(bytes as usize)) + } +} + +impl From for usize { + #[inline] + fn from(bytes: Bytes) -> Self { + bytes.0 + } +} + +#[cfg(test)] +mod tests { + use super::*; + + fn pages(amount: u32) -> Pages { + Pages::new(amount).unwrap() + } + + fn bytes(amount: usize) -> Bytes { + Bytes(amount) + } + + #[test] + fn pages_max() { + assert_eq!(Pages::max(), pages(u32::from(u16::MAX) + 1)); + } + + #[test] + fn pages_new() { + assert_eq!(Pages::new(0), Some(Pages(0))); + assert_eq!(Pages::new(1), Some(Pages(1))); + assert_eq!(Pages::new(1000), Some(Pages(1000))); + assert_eq!( + Pages::new(u32::from(u16::MAX)), + Some(Pages(u32::from(u16::MAX))) + ); + assert_eq!(Pages::new(u32::from(u16::MAX) + 1), Some(Pages::max())); + assert_eq!(Pages::new(u32::from(u16::MAX) + 2), None); + assert_eq!(Pages::new(u32::MAX), None); + } + + #[test] + fn pages_checked_add() { + let max_pages = u32::from(Pages::max()); + + assert_eq!(pages(0).checked_add(0u32), Some(pages(0))); + assert_eq!(pages(0).checked_add(1u32), Some(pages(1))); + assert_eq!(pages(1).checked_add(0u32), Some(pages(1))); + + assert_eq!(pages(0).checked_add(max_pages), Some(Pages::max())); + assert_eq!(pages(0).checked_add(Pages::max()), Some(Pages::max())); + assert_eq!(pages(1).checked_add(max_pages), None); + assert_eq!(pages(1).checked_add(Pages::max()), None); + + assert_eq!(Pages::max().checked_add(0u32), Some(Pages::max())); + assert_eq!(Pages::max().checked_add(1u32), None); + assert_eq!(pages(0).checked_add(u32::MAX), None); + + for i in 0..100 { + for j in 0..100 { + assert_eq!(pages(i).checked_add(pages(j)), Some(pages(i + j))); + } + } + } + + #[test] + fn pages_checked_sub() { + let max_pages = u32::from(Pages::max()); + + assert_eq!(pages(0).checked_sub(0u32), Some(pages(0))); + assert_eq!(pages(0).checked_sub(1u32), None); + assert_eq!(pages(1).checked_sub(0u32), Some(pages(1))); + assert_eq!(pages(1).checked_sub(1u32), Some(pages(0))); + + assert_eq!(Pages::max().checked_sub(Pages::max()), Some(pages(0))); + assert_eq!(Pages::max().checked_sub(u32::MAX), None); + assert_eq!(Pages::max().checked_sub(1u32), Some(pages(max_pages - 1))); + + for i in 0..100 { + for j in 0..100 { + assert_eq!(pages(i).checked_sub(pages(j)), i.checked_sub(j).map(pages)); + } + } + } + + #[test] + fn pages_to_bytes() { + assert_eq!(pages(0).to_bytes(), Some(0)); + if cfg!(target_pointer_width = "16") { + assert_eq!(pages(1).to_bytes(), None); + } + if cfg!(target_pointer_width = "32") || cfg!(target_pointer_width = "64") { + let bytes_per_page = usize::from(Bytes::per_page()); + for n in 1..10 { + assert_eq!(pages(n as u32).to_bytes(), Some(n * bytes_per_page)); + } + } + } + + #[test] + fn bytes_new16() { + assert_eq!(Bytes::new16(pages(0)), Some(bytes(0))); + assert_eq!(Bytes::new16(pages(1)), None); + assert!(Bytes::new16(Pages::max()).is_none()); + } + + #[test] + fn bytes_new32() { + assert_eq!(Bytes::new32(pages(0)), Some(bytes(0))); + assert_eq!(Bytes::new32(pages(1)), Some(Bytes::per_page())); + let bytes_per_page = usize::from(Bytes::per_page()); + for n in 2..10 { + assert_eq!( + Bytes::new32(pages(n as u32)), + Some(bytes(n * bytes_per_page)) + ); + } + assert!(Bytes::new32(pages(i16::MAX as u32 + 1)).is_some()); + assert!(Bytes::new32(pages(i16::MAX as u32 + 2)).is_none()); + assert!(Bytes::new32(Pages::max()).is_none()); + } + + #[test] + fn bytes_new64() { + assert_eq!(Bytes::new64(pages(0)), Some(bytes(0))); + assert_eq!(Bytes::new64(pages(1)), Some(Bytes::per_page())); + let bytes_per_page = usize::from(Bytes::per_page()); + for n in 2..10 { + assert_eq!( + Bytes::new64(pages(n as u32)), + Some(bytes(n * bytes_per_page)) + ); + } + assert!(Bytes::new64(Pages(u32::from(u16::MAX) + 1)).is_some()); + assert!(Bytes::new64(Pages(u32::from(u16::MAX) + 2)).is_none()); + assert!(Bytes::new64(Pages::max()).is_some()); + } +} diff --git a/src/types/untyped_value.rs b/src/types/untyped_value.rs new file mode 100644 index 000000000..92c3d3f91 --- /dev/null +++ b/src/types/untyped_value.rs @@ -0,0 +1,1661 @@ +use crate::{ + split_i64_to_i32, + ArithmeticOps, + ExtendInto, + Float, + Integer, + LittleEndianConvert, + LoadInto, + RwasmError, + SignExtendFrom, + StoreFrom, + TruncateSaturateInto, + TryTruncateInto, + ValueType, + WrapInto, + F32, + F64, +}; +use alloc::vec::Vec; +use bincode::{Decode, Encode}; +use core::{ + fmt::{self, Display, Formatter}, + ops::{Neg, Shl, Shr}, +}; +use paste::paste; + +/// An untyped value. +/// +/// Provides a dense and simple interface to all functional Wasm operations. +#[derive(Debug, Copy, Clone, Default, Hash, PartialEq, Eq, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct UntypedValue { + /// This inner value is required to have enough bits to represent + /// all fundamental WebAssembly types `i32`, `i64`, `f32` and `f64`. + bits: u64, +} + +impl Display for UntypedValue { + fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { + let name = format!("{:?}", self.bits); + write!(f, "{}", name) + } +} + +impl UntypedValue { + pub fn from_bits(bits: u64) -> Self { + Self { bits } + } + /// Returns the underlying bits of the [`UntypedValue`]. + pub fn to_bits(self) -> u64 { + self.bits + } +} + +macro_rules! impl_from_untyped_for_int { + ( $( $int:ty ),* $(,)? ) => { + $( + impl From for $int { + fn from(untyped: UntypedValue) -> Self { + untyped.to_bits() as _ + } + } + )* + }; +} +impl_from_untyped_for_int!(i8, i16, i32, i64, u8, u16, u32, u64); + +macro_rules! impl_from_untyped_for_float { + ( $( $float:ty ),* $(,)? ) => { + $( + impl From for $float { + fn from(untyped: UntypedValue) -> Self { + Self::from_bits(untyped.to_bits() as _) + } + } + )* + }; +} +impl_from_untyped_for_float!(f32, f64, F32, F64); + +impl From for bool { + fn from(untyped: UntypedValue) -> Self { + untyped.to_bits() != 0 + } +} + +macro_rules! impl_from_unsigned_prim { + ( $( $prim:ty ),* $(,)? ) => { + $( + impl From<$prim> for UntypedValue { + fn from(value: $prim) -> Self { + Self { bits: value as _ } + } + } + )* + }; +} +#[rustfmt::skip] +impl_from_unsigned_prim!( + bool, u8, u16, u32, u64, usize, +); + +macro_rules! impl_from_signed_prim { + ( $( $prim:ty as $base:ty ),* $(,)? ) => { + $( + impl From<$prim> for UntypedValue { + fn from(value: $prim) -> Self { + Self { bits: value as $base as _ } + } + } + )* + }; +} +#[rustfmt::skip] +impl_from_signed_prim!( + i8 as u8, + i16 as u16, + i32 as u32, + i64 as u64, +); + +macro_rules! impl_from_float { + ( $( $float:ty ),* $(,)? ) => { + $( + impl From<$float> for UntypedValue { + fn from(value: $float) -> Self { + Self { + bits: value.to_bits() as _, + } + } + } + )* + }; +} +impl_from_float!(f32, f64, F32, F64); + +macro_rules! op { + ( $operator:tt ) => {{ + |lhs, rhs| lhs $operator rhs + }}; +} + +/// Calculates the effective address of a linear memory access. +/// +/// # Errors +/// +/// If the resulting effective address overflows. +fn effective_address(address: u32, offset: u32) -> Result { + offset + .checked_add(address) + .map(|address| address as usize) + .ok_or(RwasmError::MemoryOutOfBounds) +} + +impl UntypedValue { + /// Executes a generic `T.loadN_[s|u]` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + fn load_extend(memory: &[u8], address: Self, offset: u32) -> Result + where + T: Into, + U: LittleEndianConvert + ExtendInto, + { + let raw_address = u32::from(address); + let address = effective_address(raw_address, offset)?; + let mut buffer = <::Bytes as Default>::default(); + buffer.load_into(memory, address)?; + let value: Self = ::from_le_bytes(buffer) + .extend_into() + .into(); + Ok(value) + } + + /// Executes a generic `T.load` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + fn load(memory: &[u8], address: Self, offset: u32) -> Result + where + T: LittleEndianConvert + ExtendInto + Into, + { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i32.load` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i32_load(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load::(memory, address, offset) + } + + /// Executes the `i64.load` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i64_load(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load::(memory, address, offset) + } + + /// Executes the `f32.load` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn f32_load(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load::(memory, address, offset) + } + + /// Executes the `f64.load` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn f64_load(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load::(memory, address, offset) + } + + /// Executes the `i32.load8_s` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i32_load8_s(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i32.load8_u` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i32_load8_u(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i32.load16_s` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i32_load16_s(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i32.load16_u` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i32_load16_u(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i64.load8_s` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i64_load8_s(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i64.load8_u` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i64_load8_u(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i64.load16_s` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i64_load16_s(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i64.load16_u` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i64_load16_u(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i64.load32_s` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i64_load32_s(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes the `i64.load32_u` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` loads out of bounds from `memory`. + pub fn i64_load32_u(memory: &[u8], address: Self, offset: u32) -> Result { + Self::load_extend::(memory, address, offset) + } + + /// Executes a generic `T.store[N]` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + fn store_wrap( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> + where + T: From + WrapInto, + U: LittleEndianConvert, + { + let raw_address = u32::from(address); + let address = effective_address(raw_address, offset)?; + let wrapped = T::from(value).wrap_into(); + let buffer = ::into_le_bytes(wrapped); + buffer.store_from(memory, address)?; + Ok(()) + } + + /// Executes a generic `T.store` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + fn store( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> + where + T: From + WrapInto + LittleEndianConvert, + { + Self::store_wrap::(memory, address, offset, value) + } + + /// Executes the `i32.store` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn i32_store( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store::(memory, address, offset, value) + } + + /// Executes the `i64.store` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn i64_store( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store::(memory, address, offset, value) + } + + /// Executes the `f32.store` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn f32_store( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store::(memory, address, offset, value) + } + + /// Executes the `f64.store` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn f64_store( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store::(memory, address, offset, value) + } + + /// Executes the `i32.store8` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn i32_store8( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store_wrap::(memory, address, offset, value) + } + + /// Executes the `i32.store16` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn i32_store16( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store_wrap::(memory, address, offset, value) + } + + /// Executes the `i64.store8` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn i64_store8( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store_wrap::(memory, address, offset, value) + } + + /// Executes the `i64.store16` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn i64_store16( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store_wrap::(memory, address, offset, value) + } + + /// Executes the `i64.store32` Wasm operation. + /// + /// # Errors + /// + /// - If `address + offset` overflows. + /// - If `address + offset` stores out of bounds from `memory`. + pub fn i64_store32( + memory: &mut [u8], + address: Self, + offset: u32, + value: Self, + ) -> Result<(), RwasmError> { + Self::store_wrap::(memory, address, offset, value) + } + + /// Execute an infallible generic operation on `T` that returns an `R`. + fn execute_unary(self, op: fn(T) -> R) -> Self + where + T: From, + R: Into, + { + op(T::from(self)).into() + } + + /// Execute an infallible generic operation on `T` that returns an `R`. + fn try_execute_unary(self, op: fn(T) -> Result) -> Result + where + T: From, + R: Into, + { + op(T::from(self)).map(Into::into) + } + + /// Execute an infallible generic operation on `T` that returns an `R`. + fn execute_binary(self, rhs: Self, op: fn(T, T) -> R) -> Self + where + T: From, + R: Into, + { + op(T::from(self), T::from(rhs)).into() + } + + /// Execute a fallible generic operation on `T` that returns an `R`. + fn try_execute_binary( + self, + rhs: Self, + op: fn(T, T) -> Result, + ) -> Result + where + T: From, + R: Into, + { + op(T::from(self), T::from(rhs)).map(Into::into) + } + + /// Execute `i32.add` Wasm operation. + pub fn i32_add(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::add) + } + + /// Execute `i64.add` Wasm operation. + pub fn i64_add(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::add) + } + + /// Execute `i32.sub` Wasm operation. + pub fn i32_sub(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::sub) + } + + /// Execute `i64.sub` Wasm operation. + pub fn i64_sub(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::sub) + } + + /// Execute `i32.mul` Wasm operation. + pub fn i32_mul(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::mul) + } + + /// Execute `i64.mul` Wasm operation. + pub fn i64_mul(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::mul) + } + + /// Execute `i32.div_s` Wasm operation. + /// + /// # Errors + /// + /// - If `rhs` is equal to zero. + /// - If the operation result overflows. + pub fn i32_div_s(self, rhs: Self) -> Result { + self.try_execute_binary(rhs, >::div) + } + + /// Execute `i64.div_s` Wasm operation. + /// + /// # Errors + /// + /// - If `rhs` is equal to zero. + /// - If the operation result overflows. + pub fn i64_div_s(self, rhs: Self) -> Result { + self.try_execute_binary(rhs, >::div) + } + + /// Execute `i32.div_u` Wasm operation. + /// + /// # Errors + /// + /// - If `rhs` is equal to zero. + /// - If the operation result overflows. + pub fn i32_div_u(self, rhs: Self) -> Result { + self.try_execute_binary(rhs, >::div) + } + + /// Execute `i64.div_u` Wasm operation. + /// + /// # Errors + /// + /// - If `rhs` is equal to zero. + /// - If the operation result overflows. + pub fn i64_div_u(self, rhs: Self) -> Result { + self.try_execute_binary(rhs, >::div) + } + + /// Execute `i32.rem_s` Wasm operation. + /// + /// # Errors + /// + /// - If `rhs` is equal to zero. + /// - If the operation result overflows. + pub fn i32_rem_s(self, rhs: Self) -> Result { + self.try_execute_binary(rhs, >::rem) + } + + /// Execute `i64.rem_s` Wasm operation. + /// + /// # Errors + /// + /// - If `rhs` is equal to zero. + /// - If the operation result overflows. + pub fn i64_rem_s(self, rhs: Self) -> Result { + self.try_execute_binary(rhs, >::rem) + } + + /// Execute `i32.rem_u` Wasm operation. + /// + /// # Errors + /// + /// - If `rhs` is equal to zero. + /// - If the operation result overflows. + pub fn i32_rem_u(self, rhs: Self) -> Result { + self.try_execute_binary(rhs, >::rem) + } + + /// Execute `i64.rem_u` Wasm operation. + /// + /// # Errors + /// + /// - If `rhs` is equal to zero. + /// - If the operation result overflows. + pub fn i64_rem_u(self, rhs: Self) -> Result { + self.try_execute_binary(rhs, >::rem) + } + + /// Execute `i32.and` Wasm operation. + pub fn i32_and(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(&)) + } + + /// Execute `i64.and` Wasm operation. + pub fn i64_and(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(&)) + } + + /// Execute `i32.or` Wasm operation. + pub fn i32_or(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(|)) + } + + /// Execute `i64.or` Wasm operation. + pub fn i64_or(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(|)) + } + + /// Execute `i32.xor` Wasm operation. + pub fn i32_xor(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(^)) + } + + /// Execute `i64.xor` Wasm operation. + pub fn i64_xor(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(^)) + } + + /// Execute `i32.shl` Wasm operation. + pub fn i32_shl(self, rhs: Self) -> Self { + self.execute_binary::(rhs, |lhs, rhs| lhs.shl(rhs & 0x1F)) + } + + /// Execute `i64.shl` Wasm operation. + pub fn i64_shl(self, rhs: Self) -> Self { + self.execute_binary::(rhs, |lhs, rhs| lhs.shl(rhs & 0x3F)) + } + + /// Execute `i32.shr_s` Wasm operation. + pub fn i32_shr_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, |lhs, rhs| lhs.shr(rhs & 0x1F)) + } + + /// Execute `i64.shr_s` Wasm operation. + pub fn i64_shr_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, |lhs, rhs| lhs.shr(rhs & 0x3F)) + } + + /// Execute `i32.shr_u` Wasm operation. + pub fn i32_shr_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, |lhs, rhs| lhs.shr(rhs & 0x1F)) + } + + /// Execute `i64.shr_u` Wasm operation. + pub fn i64_shr_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, |lhs, rhs| lhs.shr(rhs & 0x3F)) + } + + /// Execute `i32.clz` Wasm operation. + pub fn i32_clz(self) -> Self { + self.execute_unary(>::leading_zeros) + } + + /// Execute `i64.clz` Wasm operation. + pub fn i64_clz(self) -> Self { + self.execute_unary(>::leading_zeros) + } + + /// Execute `i32.ctz` Wasm operation. + pub fn i32_ctz(self) -> Self { + self.execute_unary(>::trailing_zeros) + } + + /// Execute `i64.ctz` Wasm operation. + pub fn i64_ctz(self) -> Self { + self.execute_unary(>::trailing_zeros) + } + + /// Execute `i32.popcnt` Wasm operation. + pub fn i32_popcnt(self) -> Self { + self.execute_unary(>::count_ones) + } + + /// Execute `i64.popcnt` Wasm operation. + pub fn i64_popcnt(self) -> Self { + self.execute_unary(>::count_ones) + } + + /// Execute `i32.rotl` Wasm operation. + pub fn i32_rotl(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::rotl) + } + + /// Execute `i64.rotl` Wasm operation. + pub fn i64_rotl(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::rotl) + } + + /// Execute `i32.rotr` Wasm operation. + pub fn i32_rotr(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::rotr) + } + + /// Execute `i64.rotr` Wasm operation. + pub fn i64_rotr(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::rotr) + } + + /// Execute `i32.eqz` Wasm operation. + pub fn i32_eqz(self) -> Self { + self.execute_unary::(|value| value == 0) + } + + /// Execute `i64.eqz` Wasm operation. + pub fn i64_eqz(self) -> Self { + self.execute_unary::(|value| value == 0) + } + + /// Execute `i32.eq` Wasm operation. + pub fn i32_eq(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(==)) + } + + /// Execute `i64.eq` Wasm operation. + pub fn i64_eq(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(==)) + } + + /// Execute `f32.eq` Wasm operation. + pub fn f32_eq(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(==)) + } + + /// Execute `f64.eq` Wasm operation. + pub fn f64_eq(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(==)) + } + + /// Execute `i32.ne` Wasm operation. + pub fn i32_ne(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(!=)) + } + + /// Execute `i64.ne` Wasm operation. + pub fn i64_ne(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(!=)) + } + + /// Execute `f32.ne` Wasm operation. + pub fn f32_ne(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(!=)) + } + + /// Execute `f64.ne` Wasm operation. + pub fn f64_ne(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(!=)) + } + + /// Execute `i32.lt_s` Wasm operation. + pub fn i32_lt_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<)) + } + + /// Execute `i64.lt_s` Wasm operation. + pub fn i64_lt_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<)) + } + + /// Execute `i32.lt_u` Wasm operation. + pub fn i32_lt_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<)) + } + + /// Execute `i64.lt_u` Wasm operation. + pub fn i64_lt_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<)) + } + + /// Execute `f32.lt` Wasm operation. + pub fn f32_lt(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<)) + } + + /// Execute `f64.lt` Wasm operation. + pub fn f64_lt(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<)) + } + + /// Execute `i32.le_s` Wasm operation. + pub fn i32_le_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<=)) + } + + /// Execute `i64.le_s` Wasm operation. + pub fn i64_le_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<=)) + } + + /// Execute `i32.le_u` Wasm operation. + pub fn i32_le_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<=)) + } + + /// Execute `i64.le_u` Wasm operation. + pub fn i64_le_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<=)) + } + + /// Execute `f32.le` Wasm operation. + pub fn f32_le(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<=)) + } + + /// Execute `f64.le` Wasm operation. + pub fn f64_le(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(<=)) + } + + /// Execute `i32.gt_s` Wasm operation. + pub fn i32_gt_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>)) + } + + /// Execute `i64.gt_s` Wasm operation. + pub fn i64_gt_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>)) + } + + /// Execute `i32.gt_u` Wasm operation. + pub fn i32_gt_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>)) + } + + /// Execute `i64.gt_u` Wasm operation. + pub fn i64_gt_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>)) + } + + /// Execute `f32.gt` Wasm operation. + pub fn f32_gt(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>)) + } + + /// Execute `f64.gt` Wasm operation. + pub fn f64_gt(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>)) + } + + /// Execute `i32.ge_s` Wasm operation. + pub fn i32_ge_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>=)) + } + + /// Execute `i64.ge_s` Wasm operation. + pub fn i64_ge_s(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>=)) + } + + /// Execute `i32.ge_u` Wasm operation. + pub fn i32_ge_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>=)) + } + + /// Execute `i64.ge_u` Wasm operation. + pub fn i64_ge_u(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>=)) + } + + /// Execute `f32.ge` Wasm operation. + pub fn f32_ge(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>=)) + } + + /// Execute `f64.ge` Wasm operation. + pub fn f64_ge(self, rhs: Self) -> Self { + self.execute_binary::(rhs, op!(>=)) + } + + /// Execute `f32.abs` Wasm operation. + pub fn f32_abs(self) -> Self { + self.execute_unary(>::abs) + } + + /// Execute `f32.neg` Wasm operation. + pub fn f32_neg(self) -> Self { + self.execute_unary(::neg) + } + + /// Execute `f32.ceil` Wasm operation. + pub fn f32_ceil(self) -> Self { + self.execute_unary(>::ceil) + } + + /// Execute `f32.floor` Wasm operation. + pub fn f32_floor(self) -> Self { + self.execute_unary(>::floor) + } + + /// Execute `f32.trunc` Wasm operation. + pub fn f32_trunc(self) -> Self { + self.execute_unary(>::trunc) + } + + /// Execute `f32.nearest` Wasm operation. + pub fn f32_nearest(self) -> Self { + self.execute_unary(>::nearest) + } + + /// Execute `f32.sqrt` Wasm operation. + pub fn f32_sqrt(self) -> Self { + self.execute_unary(>::sqrt) + } + + /// Execute `f32.min` Wasm operation. + pub fn f32_min(self, other: Self) -> Self { + self.execute_binary(other, >::min) + } + + /// Execute `f32.max` Wasm operation. + pub fn f32_max(self, other: Self) -> Self { + self.execute_binary(other, >::max) + } + + /// Execute `f32.copysign` Wasm operation. + pub fn f32_copysign(self, other: Self) -> Self { + self.execute_binary(other, >::copysign) + } + + /// Execute `f64.abs` Wasm operation. + pub fn f64_abs(self) -> Self { + self.execute_unary(>::abs) + } + + /// Execute `f64.neg` Wasm operation. + pub fn f64_neg(self) -> Self { + self.execute_unary(::neg) + } + + /// Execute `f64.ceil` Wasm operation. + pub fn f64_ceil(self) -> Self { + self.execute_unary(>::ceil) + } + + /// Execute `f64.floor` Wasm operation. + pub fn f64_floor(self) -> Self { + self.execute_unary(>::floor) + } + + /// Execute `f64.trunc` Wasm operation. + pub fn f64_trunc(self) -> Self { + self.execute_unary(>::trunc) + } + + /// Execute `f64.nearest` Wasm operation. + pub fn f64_nearest(self) -> Self { + self.execute_unary(>::nearest) + } + + /// Execute `f64.sqrt` Wasm operation. + pub fn f64_sqrt(self) -> Self { + self.execute_unary(>::sqrt) + } + + /// Execute `f32.add` Wasm operation. + pub fn f32_add(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::add) + } + + /// Execute `f64.add` Wasm operation. + pub fn f64_add(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::add) + } + + /// Execute `f32.sub` Wasm operation. + pub fn f32_sub(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::sub) + } + + /// Execute `f64.sub` Wasm operation. + pub fn f64_sub(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::sub) + } + + /// Execute `f32.mul` Wasm operation. + pub fn f32_mul(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::mul) + } + + /// Execute `f64.mul` Wasm operation. + pub fn f64_mul(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::mul) + } + + /// Execute `f32.div` Wasm operation. + pub fn f32_div(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::div) + } + + /// Execute `f64.div` Wasm operation. + pub fn f64_div(self, rhs: Self) -> Self { + self.execute_binary(rhs, >::div) + } + + /// Execute `f64.min` Wasm operation. + pub fn f64_min(self, other: Self) -> Self { + self.execute_binary(other, >::min) + } + + /// Execute `f64.max` Wasm operation. + pub fn f64_max(self, other: Self) -> Self { + self.execute_binary(other, >::max) + } + + /// Execute `f64.copysign` Wasm operation. + pub fn f64_copysign(self, other: Self) -> Self { + self.execute_binary(other, >::copysign) + } + + /// Execute `i32.wrap_i64` Wasm operation. + pub fn i32_wrap_i64(self) -> Self { + self.execute_unary(>::wrap_into) + } + + /// Execute `i32.trunc_f32_s` Wasm operation. + /// + /// # Errors + /// + /// - If `self` is NaN (not a number). + /// - If `self` is positive or negative infinity. + /// - If the integer value of `self` is out of bounds of the target type. + /// + /// Read more about the failure cases in the [WebAssembly specification]. + /// + /// [WebAssembly specification]: + /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s + pub fn i32_trunc_f32_s(self) -> Result { + self.try_execute_unary(>::try_truncate_into) + } + + /// Execute `i32.trunc_f32_u` Wasm operation. + /// + /// # Errors + /// + /// - If `self` is NaN (not a number). + /// - If `self` is positive or negative infinity. + /// - If the integer value of `self` is out of bounds of the target type. + /// + /// Read more about the failure cases in the [WebAssembly specification]. + /// + /// [WebAssembly specification]: + /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s + pub fn i32_trunc_f32_u(self) -> Result { + self.try_execute_unary(>::try_truncate_into) + } + + /// Execute `i32.trunc_f64_s` Wasm operation. + /// + /// # Errors + /// + /// - If `self` is NaN (not a number). + /// - If `self` is positive or negative infinity. + /// - If the integer value of `self` is out of bounds of the target type. + /// + /// Read more about the failure cases in the [WebAssembly specification]. + /// + /// [WebAssembly specification]: + /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s + pub fn i32_trunc_f64_s(self) -> Result { + self.try_execute_unary(>::try_truncate_into) + } + + /// Execute `i32.trunc_f64_u` Wasm operation. + /// + /// # Errors + /// + /// - If `self` is NaN (not a number). + /// - If `self` is positive or negative infinity. + /// - If the integer value of `self` is out of bounds of the target type. + /// + /// Read more about the failure cases in the [WebAssembly specification]. + /// + /// [WebAssembly specification]: + /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s + pub fn i32_trunc_f64_u(self) -> Result { + self.try_execute_unary(>::try_truncate_into) + } + + /// Execute `i64.extend_i32_s` Wasm operation. + pub fn i64_extend_i32_s(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `i64.extend_i32_u` Wasm operation. + pub fn i64_extend_i32_u(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `i64.trunc_f32_s` Wasm operation. + /// + /// # Errors + /// + /// - If `self` is NaN (not a number). + /// - If `self` is positive or negative infinity. + /// - If the integer value of `self` is out of bounds of the target type. + /// + /// Read more about the failure cases in the [WebAssembly specification]. + /// + /// [WebAssembly specification]: + /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s + pub fn i64_trunc_f32_s(self) -> Result { + self.try_execute_unary(>::try_truncate_into) + } + + /// Execute `i64.trunc_f32_u` Wasm operation. + /// + /// # Errors + /// + /// - If `self` is NaN (not a number). + /// - If `self` is positive or negative infinity. + /// - If the integer value of `self` is out of bounds of the target type. + /// + /// Read more about the failure cases in the [WebAssembly specification]. + /// + /// [WebAssembly specification]: + /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s + pub fn i64_trunc_f32_u(self) -> Result { + self.try_execute_unary(>::try_truncate_into) + } + + /// Execute `i64.trunc_f64_s` Wasm operation. + /// + /// # Errors + /// + /// - If `self` is NaN (not a number). + /// - If `self` is positive or negative infinity. + /// - If the integer value of `self` is out of bounds of the target type. + /// + /// Read more about the failure cases in the [WebAssembly specification]. + /// + /// [WebAssembly specification]: + /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s + pub fn i64_trunc_f64_s(self) -> Result { + self.try_execute_unary(>::try_truncate_into) + } + + /// Execute `i64.trunc_f64_u` Wasm operation. + /// + /// # Errors + /// + /// - If `self` is NaN (not a number). + /// - If `self` is positive or negative infinity. + /// - If the integer value of `self` is out of bounds of the target type. + /// + /// Read more about the failure cases in the [WebAssembly specification]. + /// + /// [WebAssembly specification]: + /// https://webassembly.github.io/spec/core/exec/numerics.html#op-trunc-s + pub fn i64_trunc_f64_u(self) -> Result { + self.try_execute_unary(>::try_truncate_into) + } + + /// Execute `f32.convert_i32_s` Wasm operation. + pub fn f32_convert_i32_s(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `f32.convert_i32_u` Wasm operation. + pub fn f32_convert_i32_u(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `f32.convert_i64_s` Wasm operation. + pub fn f32_convert_i64_s(self) -> Self { + self.execute_unary(>::wrap_into) + } + + /// Execute `f32.convert_i64_u` Wasm operation. + pub fn f32_convert_i64_u(self) -> Self { + self.execute_unary(>::wrap_into) + } + + /// Execute `f32.demote_f64` Wasm operation. + pub fn f32_demote_f64(self) -> Self { + self.execute_unary(>::wrap_into) + } + + /// Execute `f64.convert_i32_s` Wasm operation. + pub fn f64_convert_i32_s(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `f64.convert_i32_u` Wasm operation. + pub fn f64_convert_i32_u(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `f64.convert_i64_s` Wasm operation. + pub fn f64_convert_i64_s(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `f64.convert_i64_u` Wasm operation. + pub fn f64_convert_i64_u(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `f64.promote_f32` Wasm operation. + pub fn f64_promote_f32(self) -> Self { + self.execute_unary(>::extend_into) + } + + /// Execute `i32.extend8_s` Wasm operation. + pub fn i32_extend8_s(self) -> Self { + self.execute_unary(>::sign_extend_from) + } + + /// Execute `i32.extend16_s` Wasm operation. + pub fn i32_extend16_s(self) -> Self { + self.execute_unary(>::sign_extend_from) + } + + /// Execute `i64.extend8_s` Wasm operation. + pub fn i64_extend8_s(self) -> Self { + self.execute_unary(>::sign_extend_from) + } + + /// Execute `i64.extend16_s` Wasm operation. + pub fn i64_extend16_s(self) -> Self { + self.execute_unary(>::sign_extend_from) + } + + /// Execute `i64.extend32_s` Wasm operation. + pub fn i64_extend32_s(self) -> Self { + self.execute_unary(>::sign_extend_from) + } + + /// Execute `i32.trunc_sat_f32_s` Wasm operation. + pub fn i32_trunc_sat_f32_s(self) -> Self { + self.execute_unary(>::truncate_saturate_into) + } + + /// Execute `i32.trunc_sat_f32_u` Wasm operation. + pub fn i32_trunc_sat_f32_u(self) -> Self { + self.execute_unary(>::truncate_saturate_into) + } + + /// Execute `i32.trunc_sat_f64_s` Wasm operation. + pub fn i32_trunc_sat_f64_s(self) -> Self { + self.execute_unary(>::truncate_saturate_into) + } + + /// Execute `i32.trunc_sat_f64_u` Wasm operation. + pub fn i32_trunc_sat_f64_u(self) -> Self { + self.execute_unary(>::truncate_saturate_into) + } + + /// Execute `i64.trunc_sat_f32_s` Wasm operation. + pub fn i64_trunc_sat_f32_s(self) -> Self { + self.execute_unary(>::truncate_saturate_into) + } + + /// Execute `i64.trunc_sat_f32_u` Wasm operation. + pub fn i64_trunc_sat_f32_u(self) -> Self { + self.execute_unary(>::truncate_saturate_into) + } + + /// Execute `i64.trunc_sat_f64_s` Wasm operation. + pub fn i64_trunc_sat_f64_s(self) -> Self { + self.execute_unary(>::truncate_saturate_into) + } + + /// Execute `i64.trunc_sat_f64_u` Wasm operation. + pub fn i64_trunc_sat_f64_u(self) -> Self { + self.execute_unary(>::truncate_saturate_into) + } +} + +/// Macro to help implement generic trait implementations for tuple types. +macro_rules! for_each_tuple { + ($mac:ident) => { + $mac!( 0 ); + $mac!( 1 T1); + $mac!( 2 T1 T2); + $mac!( 3 T1 T2 T3); + $mac!( 4 T1 T2 T3 T4); + $mac!( 5 T1 T2 T3 T4 T5); + $mac!( 6 T1 T2 T3 T4 T5 T6); + $mac!( 7 T1 T2 T3 T4 T5 T6 T7); + $mac!( 8 T1 T2 T3 T4 T5 T6 T7 T8); + $mac!( 9 T1 T2 T3 T4 T5 T6 T7 T8 T9); + $mac!(10 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10); + $mac!(11 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11); + $mac!(12 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12); + $mac!(13 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13); + $mac!(14 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14); + $mac!(15 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15); + $mac!(16 T1 T2 T3 T4 T5 T6 T7 T8 T9 T10 T11 T12 T13 T14 T15 T16); + } +} + +/// An error that may occur upon encoding or decoding slices of [`UntypedValue`]. +#[derive(Debug, Copy, Clone)] +pub enum UntypedError { + /// The [`UntypedValue`] slice length did not match `Self`. + InvalidLen, +} + +impl UntypedError { + /// Creates a new `InvalidLen` [`UntypedError`]. + #[cold] + pub fn invalid_len() -> Self { + Self::InvalidLen + } +} + +impl Display for UntypedError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + UntypedError::InvalidLen => { + write!(f, "mismatched length of the untyped slice",) + } + } + } +} + +impl UntypedValue { + /// Decodes the slice of [`UntypedValue`] as a value of type `T`. + /// + /// # Note + /// + /// `T` can either be a single type or a tuple of types depending + /// on the length of the `slice`. + /// + /// # Errors + /// + /// If the tuple length of `T` and the length of `slice` does not match. + pub fn decode_slice(slice: &[Self]) -> Result + where + T: DecodeUntypedSlice, + { + ::decode_untyped_slice(slice) + } + + pub fn decode_slice_i32( + slice: &[Self], + origin_params: &[ValueType], + ) -> Result + where + T: DecodeUntypedSlice, + { + ::decode_untyped_slice_i32(slice, origin_params) + } + + /// Encodes the slice of [`UntypedValue`] from the given value of type `T`. + /// + /// # Note + /// + /// `T` can either be a single type or a tuple of types depending + /// on the length of the `slice`. + /// + /// # Errors + /// + /// If the tuple length of `T` and the length of `slice` does not match. + pub fn encode_slice(slice: &mut [Self], input: T) -> Result<(), UntypedError> + where + T: EncodeUntypedSlice, + { + ::encode_untyped_slice(input, slice) + } + + pub fn encode_slice_i32( + slice: &mut [Self], + input: T, + origin_results: Vec, + ) -> Result<(), UntypedError> + where + T: EncodeUntypedSlice, + { + ::encode_untyped_slice_i32(input, slice, origin_results) + } +} + +/// Tuple types that allow to decode a slice of [`UntypedValue`]. +pub trait DecodeUntypedSlice: Sized { + /// Decodes the slice of [`UntypedValue`] as a value of type `Self`. + /// + /// # Note + /// + /// `Self` can either be a single type or a tuple of types depending + /// on the length of the `slice`. + /// + /// # Errors + /// + /// If the tuple length of `Self` and the length of `slice` does not match. + fn decode_untyped_slice(params: &[UntypedValue]) -> Result; + + fn decode_untyped_slice_i32( + params: &[UntypedValue], + origin_params: &[ValueType], + ) -> Result; +} + +impl DecodeUntypedSlice for T1 +where + T1: From, +{ + #[inline] + fn decode_untyped_slice(results: &[UntypedValue]) -> Result { + <(T1,) as DecodeUntypedSlice>::decode_untyped_slice(results).map(|t| t.0) + } + + #[inline] + fn decode_untyped_slice_i32( + results: &[UntypedValue], + origin_params: &[ValueType], + ) -> Result { + <(T1,) as DecodeUntypedSlice>::decode_untyped_slice_i32(results, origin_params).map(|t| t.0) + } +} + +macro_rules! impl_decode_untyped_slice { + ( $n:literal $( $tuple:ident )* ) => { + impl<$($tuple),*> DecodeUntypedSlice for ($($tuple,)*) + where + $( + $tuple: From + ),* + { + #[allow(non_snake_case)] + #[inline] + fn decode_untyped_slice(results: &[UntypedValue]) -> Result { + match results { + &[ $($tuple),* ] => Ok(( + $( + <$tuple as From>::from($tuple), + )* + )), + _ => Err(UntypedError::invalid_len()), + } + } + + #[allow(non_snake_case)] + #[inline] + #[allow(unused_variables, unused_mut, unused_assignments)] + fn decode_untyped_slice_i32(results: &[UntypedValue], origin_params: &[ValueType]) -> Result { + let mut i = 0; + match origin_params { + &[ $($tuple),* ] => Ok(( + $( + { + if $tuple == ValueType::I64 { + if i + 1 >= results.len() { + return Err(UntypedError::invalid_len()); + } + let high = results[i].as_u64(); + let low = results[i + 1].as_u64(); + i += 2; + + <$tuple as From>::from(UntypedValue::from((high << 32) | low)) + } else { + if i >= results.len() { + return Err(UntypedError::invalid_len()); + } + let value = results[i].clone(); + i += 1; + + <$tuple as From>::from(value) + } + }, + )* + + )), + _ => Err(UntypedError::invalid_len()), + } + } + } + }; +} +for_each_tuple!(impl_decode_untyped_slice); + +/// Tuple types that allow to encode a slice of [`UntypedValue`]. +pub trait EncodeUntypedSlice { + /// Encodes the slice of [`UntypedValue`] from the given value of type `Self`. + /// + /// # Note + /// + /// `Self` can either be a single type or a tuple of types depending + /// on the length of the `slice`. + /// + /// # Errors + /// + /// If the tuple length of `Self` and the length of `slice` does not match. + fn encode_untyped_slice(self, results: &mut [UntypedValue]) -> Result<(), UntypedError>; + + fn encode_untyped_slice_i32( + self, + results: &mut [UntypedValue], + origin_results: Vec, + ) -> Result<(), UntypedError>; +} + +impl EncodeUntypedSlice for T1 +where + T1: Into, +{ + #[inline] + fn encode_untyped_slice(self, results: &mut [UntypedValue]) -> Result<(), UntypedError> { + <(T1,) as EncodeUntypedSlice>::encode_untyped_slice((self,), results) + } + + #[inline] + fn encode_untyped_slice_i32( + self, + results: &mut [UntypedValue], + origin_results: Vec, + ) -> Result<(), UntypedError> { + <(T1,) as EncodeUntypedSlice>::encode_untyped_slice_i32((self,), results, origin_results) + } +} + +macro_rules! impl_encode_untyped_slice { + ( $n:literal $( $tuple:ident )* ) => { + paste! { + impl<$($tuple),*> EncodeUntypedSlice for ($($tuple,)*) + where + $( + $tuple: Into + ),* + { + #[allow(non_snake_case)] + #[inline] + fn encode_untyped_slice(self, results: &mut [UntypedValue]) -> Result<(), UntypedError> { + match results { + [ $( [< _results_ $tuple >] ,)* ] => { + let ( $( [< _self_ $tuple >] ,)* ) = self; + $( + *[< _results_ $tuple >] = <$tuple as Into>::into([< _self_ $tuple >]); + )* + Ok(()) + } + _ => Err(UntypedError::invalid_len()) + } + } + + #[allow(non_snake_case)] + #[inline] + #[allow(unused_variables)] + #[allow(unused_mut)] + fn encode_untyped_slice_i32(self, results: &mut [UntypedValue], origin_results: Vec) -> Result<(), UntypedError> { + let mut i = 0; + match origin_results.as_slice() { + [ $( [< _origin_results_ $tuple >] ,)* ] => { + let ( $( [< _self_ $tuple >] ,)* ) = self; + $( + let untyped = <$tuple as Into>::into([< _self_ $tuple >]); + if [< _origin_results_ $tuple >] == &ValueType::I64 { + let [low, high] = split_i64_to_i32(untyped.as_u64() as i64); + results[i] = UntypedValue::from(high); + i += 1; + results[i] = UntypedValue::from(low); + i += 1; + } else { + results[i] = untyped; + i += 1; + } + )* + if i != results.len() { + Err(UntypedError::invalid_len()) + } else { + Ok(()) + } + + } + _ => Err(UntypedError::invalid_len()) + } + } + } + } + }; +} +for_each_tuple!(impl_encode_untyped_slice); + +impl UntypedValue { + pub fn as_u16(self) -> u16 { + u16::from(self) + } + + pub fn as_u32(self) -> u32 { + u32::from(self) + } + + pub fn as_i32(self) -> i32 { + i32::from(self) + } + + pub fn as_u64(self) -> u64 { + u64::from(self) + } + + pub fn as_usize(self) -> usize { + self.as_u64() as usize + } +} diff --git a/src/types/utils.rs b/src/types/utils.rs new file mode 100644 index 000000000..746db15e6 --- /dev/null +++ b/src/types/utils.rs @@ -0,0 +1,216 @@ +use crate::RwasmError; +use bincode::{Decode, Encode}; + +/// A 32-bit encoded `f64` value. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] +pub struct F64Const32(u32); + +impl F64Const32 { + /// Creates an [`Instruction::F64Const32`] from the given `f64` value if possible. + /// + /// [`Instruction::F64Const32`]: [`super::Instruction::F64Const32`] + pub fn new(value: f64) -> Option { + let demoted = value as f32; + if f64::from(demoted).to_bits() != value.to_bits() { + return None; + } + Some(Self(demoted.to_bits())) + } + + /// Returns the 32-bit encoded `f64` value. + pub fn to_f64(self) -> f64 { + f64::from(f32::from_bits(self.0)) + } +} + +/// A function index. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct FuncIdx(u32); + +impl From for FuncIdx { + fn from(index: u16) -> Self { + Self(index as u32) + } +} +impl From for FuncIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl FuncIdx { + /// Returns the index value as `u32`. + pub fn to_u32(self) -> u32 { + self.0 + } +} + +/// A table index. +#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct TableIdx(u32); + +impl From for TableIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl TableIdx { + /// Returns the index value as `u32`. + pub fn to_u32(self) -> u32 { + self.0 + } +} + +/// An index of a unique function signature. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct SignatureIdx(u32); + +impl From for SignatureIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl SignatureIdx { + /// Returns the index value as `u32`. + pub fn to_u32(self) -> u32 { + self.0 + } +} + +/// A local variable depth access index. +/// +/// # Note +/// +/// The depth refers to the relative position of a local +/// variable on the value stack with respect to the height +/// of the value stack at the time of access. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct LocalDepth(u32); + +impl From for LocalDepth { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl LocalDepth { + /// Returns the depth as `usize` index. + pub fn to_usize(self) -> usize { + self.0 as usize + } +} + +/// A global variable index. +/// +/// # Note +/// +/// Refers to a global variable of a [`Store`]. +/// +/// [`Store`]: [`crate::Store`] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct GlobalIdx(u32); + +impl From for GlobalIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl GlobalIdx { + /// Returns the index value as `u32`. + pub fn to_u32(self) -> u32 { + self.0 + } +} + +/// A data segment index. +/// +/// # Note +/// +/// Refers to a data segment of a [`Store`]. +/// +/// [`Store`]: [`crate::Store`] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct DataSegmentIdx(u32); + +impl From for DataSegmentIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl DataSegmentIdx { + /// Returns the index value as `u32`. + pub fn to_u32(self) -> u32 { + self.0 + } +} + +/// An element segment index. +/// +/// # Note +/// +/// Refers to a data segment of a [`Store`]. +/// +/// [`Store`]: [`crate::Store`] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct ElementSegmentIdx(u32); + +impl From for ElementSegmentIdx { + fn from(index: u32) -> Self { + Self(index) + } +} + +impl ElementSegmentIdx { + /// Returns the index value as `u32`. + pub fn to_u32(self) -> u32 { + self.0 + } +} + +/// The number of branches of an [`Instruction::BrTable`]. +/// +/// [`Instruction::BrTable`]: [`super::Instruction::BrTable`] +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct BranchTableTargets(u32); + +impl TryFrom for BranchTableTargets { + type Error = RwasmError; + + fn try_from(index: usize) -> Result { + match u32::try_from(index) { + Ok(index) => Ok(Self(index)), + Err(_) => Err(RwasmError::BranchTableTargetsOutOfBounds), + } + } +} + +impl From for BranchTableTargets { + fn from(value: u32) -> Self { + Self(value) + } +} + +impl BranchTableTargets { + /// Returns the index value as `usize`. + pub fn to_usize(self) -> usize { + self.0 as usize + } +} + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord, Encode, Decode)] +#[repr(transparent)] +pub struct StackAlloc { + pub max_stack_height: u32, +} diff --git a/src/types/value.rs b/src/types/value.rs new file mode 100644 index 000000000..84a17e006 --- /dev/null +++ b/src/types/value.rs @@ -0,0 +1,1085 @@ +use crate::{ + types::{error::RwasmError, F32, F64}, + ExternRef, + FuncRef, + UntypedValue, +}; +use core::{f32, i32, i64, u32, u64}; + +/// Type of a value. +/// +/// See [`Value`] for details. +/// +/// [`Value`]: enum.Value.html +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ValueType { + /// 32-bit signed or unsigned integer. + I32, + /// 64-bit signed or unsigned integer. + I64, + /// 32-bit IEEE 754-2008 floating point number. + F32, + /// 64-bit IEEE 754-2008 floating point number. + F64, + /// A nullable function reference. + FuncRef, + /// A nullable external reference. + ExternRef, +} + +impl From for ValueType { + fn from(value: wasmparser::ValType) -> Self { + match value { + wasmparser::ValType::I32 => Self::I32, + wasmparser::ValType::I64 => Self::I64, + wasmparser::ValType::F32 => Self::F32, + wasmparser::ValType::F64 => Self::F64, + wasmparser::ValType::V128 => unreachable!("rwasm: not supported v128 type"), + wasmparser::ValType::FuncRef => Self::FuncRef, + wasmparser::ValType::ExternRef => Self::ExternRef, + } + } +} + +/// Convert one type to another by wrapping. +pub trait WrapInto { + /// Convert one type to another by wrapping. + fn wrap_into(self) -> T; +} + +/// Convert one type to another by rounding to the nearest integer towards zero. +/// +/// # Errors +/// +/// Traps when the input float cannot be represented by the target integer or +/// when the input float is NaN. +pub trait TryTruncateInto { + /// Convert one type to another by rounding to the nearest integer towards zero. + /// + /// # Errors + /// + /// - If the input float value is NaN (not a number). + /// - If the input float value cannot be represented using the truncated integer type. + fn try_truncate_into(self) -> Result; +} + +/// Convert one type to another by rounding to the nearest integer towards zero. +/// +/// # Note +/// +/// This has saturating semantics for when the integer cannot represent the float. +/// +/// Returns +/// +/// - `0` when the input is NaN. +/// - `int::MIN` when the input is -INF. +/// - `int::MAX` when the input is +INF. +pub trait TruncateSaturateInto { + /// Convert one type to another by rounding to the nearest integer towards zero. + fn truncate_saturate_into(self) -> T; +} + +/// Convert one type to another by extending with leading zeroes. +pub trait ExtendInto { + /// Convert one type to another by extending with leading zeroes. + fn extend_into(self) -> T; +} + +/// Sign-extends `Self` integer type from `T` integer type. +pub trait SignExtendFrom { + /// Convert one type to another by extending with leading zeroes. + fn sign_extend_from(self) -> Self; +} + +/// Reinterprets the bits of a value of one type as another type. +pub trait TransmuteInto { + /// Reinterprets the bits of a value of one type as another type. + fn transmute_into(self) -> T; +} + +/// Allows to efficiently load bytes from `memory` into a buffer. +pub trait LoadInto { + /// Loads bytes from `memory` into `self`. + /// + /// # Errors + /// + /// Traps if the `memory` access is out of bounds. + fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), RwasmError>; +} + +impl LoadInto for [u8; N] { + #[inline] + fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), RwasmError> { + let slice: &Self = memory + .get(address..) + .and_then(|slice| slice.get(..N)) + .and_then(|slice| slice.try_into().ok()) + .ok_or(RwasmError::MemoryOutOfBounds)?; + *self = *slice; + Ok(()) + } +} + +/// Allows to efficiently write bytes from a buffer into `memory`. +pub trait StoreFrom { + /// Writes bytes from `self` to `memory`. + /// + /// # Errors + /// + /// Traps if the `memory` access is out of bounds. + fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), RwasmError>; +} + +impl StoreFrom for [u8; N] { + #[inline] + fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), RwasmError> { + let slice: &mut Self = memory + .get_mut(address..) + .and_then(|slice| slice.get_mut(..N)) + .and_then(|slice| slice.try_into().ok()) + .ok_or(RwasmError::MemoryOutOfBounds)?; + *slice = *self; + Ok(()) + } +} + +/// Types that can be converted from and to little endian bytes. +pub trait LittleEndianConvert { + /// The little endian bytes representation. + type Bytes: Default + LoadInto + StoreFrom; + + /// Converts `self` into little endian bytes. + fn into_le_bytes(self) -> Self::Bytes; + + /// Converts little endian bytes into `Self`. + fn from_le_bytes(bytes: Self::Bytes) -> Self; +} + +macro_rules! impl_little_endian_convert_primitive { + ( $($primitive:ty),* $(,)? ) => { + $( + impl LittleEndianConvert for $primitive { + type Bytes = [::core::primitive::u8; ::core::mem::size_of::<$primitive>()]; + + #[inline] + fn into_le_bytes(self) -> Self::Bytes { + <$primitive>::to_le_bytes(self) + } + + #[inline] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + <$primitive>::from_le_bytes(bytes) + } + } + )* + }; +} +impl_little_endian_convert_primitive!(u8, u16, u32, u64, i8, i16, i32, i64, f32, f64); + +macro_rules! impl_little_endian_convert_float { + ( $( struct $float_ty:ident($uint_ty:ty); )* $(,)? ) => { + $( + impl LittleEndianConvert for $float_ty { + type Bytes = <$uint_ty as LittleEndianConvert>::Bytes; + + #[inline] + fn into_le_bytes(self) -> Self::Bytes { + <$uint_ty>::into_le_bytes(self.to_bits()) + } + + #[inline] + fn from_le_bytes(bytes: Self::Bytes) -> Self { + Self::from_bits(<$uint_ty>::from_le_bytes(bytes)) + } + } + )* + }; +} +impl_little_endian_convert_float!( + struct F32(u32); + struct F64(u64); +); + +/// Arithmetic operations. +pub trait ArithmeticOps: Copy { + /// Add two values. + fn add(self, other: T) -> T; + /// Subtract two values. + fn sub(self, other: T) -> T; + /// Multiply two values. + fn mul(self, other: T) -> T; +} + +/// Integer value. +pub trait Integer: ArithmeticOps { + /// Counts leading zeros in the bitwise representation of the value. + fn leading_zeros(self) -> T; + /// Counts trailing zeros in the bitwise representation of the value. + fn trailing_zeros(self) -> T; + /// Counts 1-bits in the bitwise representation of the value. + fn count_ones(self) -> T; + /// Get left bit rotation result. + fn rotl(self, other: T) -> T; + /// Get right bit rotation result. + fn rotr(self, other: T) -> T; + /// Divide two values. + /// + /// # Errors + /// + /// If `other` is equal to zero. + fn div(self, other: T) -> Result; + /// Get division remainder. + /// + /// # Errors + /// + /// If `other` is equal to zero. + fn rem(self, other: T) -> Result; +} + +/// Float-point value. +pub trait Float: ArithmeticOps { + /// Get absolute value. + fn abs(self) -> T; + /// Returns the largest integer less than or equal to a number. + fn floor(self) -> T; + /// Returns the smallest integer greater than or equal to a number. + fn ceil(self) -> T; + /// Returns the integer part of a number. + fn trunc(self) -> T; + /// Returns the nearest integer to a number. Round half-way cases away from 0.0. + fn round(self) -> T; + /// Returns the nearest integer to a number. Ties are round to even number. + fn nearest(self) -> T; + /// Takes the square root of a number. + fn sqrt(self) -> T; + /// Returns `true` if the sign of the number is positive. + fn is_sign_positive(self) -> bool; + /// Returns `true` if the sign of the number is negative. + fn is_sign_negative(self) -> bool; + /// Returns the division of the two numbers. + fn div(self, other: T) -> T; + /// Returns the minimum of the two numbers. + fn min(self, other: T) -> T; + /// Returns the maximum of the two numbers. + fn max(self, other: T) -> T; + /// Sets sign of this value to the sign of other value. + fn copysign(self, other: T) -> T; +} + +macro_rules! impl_wrap_into { + ($from:ident, $into:ident) => { + impl WrapInto<$into> for $from { + #[inline] + fn wrap_into(self) -> $into { + self as $into + } + } + }; + ($from:ident, $intermediate:ident, $into:ident) => { + impl WrapInto<$into> for $from { + #[inline] + fn wrap_into(self) -> $into { + $into::from(self as $intermediate) + } + } + }; +} + +impl_wrap_into!(i32, i8); +impl_wrap_into!(i32, i16); +impl_wrap_into!(i64, i8); +impl_wrap_into!(i64, i16); +impl_wrap_into!(i64, i32); +impl_wrap_into!(i64, f32, F32); +impl_wrap_into!(u64, f32, F32); + +// Casting to self +impl_wrap_into!(i32, i32); +impl_wrap_into!(i64, i64); +impl_wrap_into!(F32, F32); +impl_wrap_into!(F64, F64); + +impl WrapInto for F64 { + #[inline] + fn wrap_into(self) -> F32 { + (f64::from(self) as f32).into() + } +} + +macro_rules! impl_try_truncate_into { + (@primitive $from: ident, $into: ident, $to_primitive:path, $rmin:literal, $rmax:literal) => { + impl TryTruncateInto<$into, RwasmError> for $from { + #[inline] + fn try_truncate_into(self) -> Result<$into, RwasmError> { + if self.is_nan() { + return Err(RwasmError::BadConversionToInteger); + } + if self <= $rmin || self >= $rmax { + return Err(RwasmError::IntegerOverflow); + } + Ok(self as _) + } + } + + impl TruncateSaturateInto<$into> for $from { + #[inline] + fn truncate_saturate_into(self) -> $into { + if self.is_nan() { + return <$into as Default>::default(); + } + if self.is_infinite() && self.is_sign_positive() { + return <$into>::MAX; + } + if self.is_infinite() && self.is_sign_negative() { + return <$into>::MIN; + } + self as _ + } + } + }; + (@wrapped $from:ident, $intermediate:ident, $into:ident) => { + impl TryTruncateInto<$into, RwasmError> for $from { + #[inline] + fn try_truncate_into(self) -> Result<$into, RwasmError> { + $intermediate::from(self).try_truncate_into() + } + } + + impl TruncateSaturateInto<$into> for $from { + #[inline] + fn truncate_saturate_into(self) -> $into { + $intermediate::from(self).truncate_saturate_into() + } + } + }; +} + +impl_try_truncate_into!(@primitive f32, i32, num_traits::cast::ToPrimitive::to_i32, -2147483904.0_f32, 2147483648.0_f32); +impl_try_truncate_into!(@primitive f32, u32, num_traits::cast::ToPrimitive::to_u32, -1.0_f32, 4294967296.0_f32); +impl_try_truncate_into!(@primitive f64, i32, num_traits::cast::ToPrimitive::to_i32, -2147483649.0_f64, 2147483648.0_f64); +impl_try_truncate_into!(@primitive f64, u32, num_traits::cast::ToPrimitive::to_u32, -1.0_f64, 4294967296.0_f64); +impl_try_truncate_into!(@primitive f32, i64, num_traits::cast::ToPrimitive::to_i64, -9223373136366403584.0_f32, 9223372036854775808.0_f32); +impl_try_truncate_into!(@primitive f32, u64, num_traits::cast::ToPrimitive::to_u64, -1.0_f32, 18446744073709551616.0_f32); +impl_try_truncate_into!(@primitive f64, i64, num_traits::cast::ToPrimitive::to_i64, -9223372036854777856.0_f64, 9223372036854775808.0_f64); +impl_try_truncate_into!(@primitive f64, u64, num_traits::cast::ToPrimitive::to_u64, -1.0_f64, 18446744073709551616.0_f64); +impl_try_truncate_into!(@wrapped F32, f32, i32); +impl_try_truncate_into!(@wrapped F32, f32, i64); +impl_try_truncate_into!(@wrapped F64, f64, i32); +impl_try_truncate_into!(@wrapped F64, f64, i64); +impl_try_truncate_into!(@wrapped F32, f32, u32); +impl_try_truncate_into!(@wrapped F32, f32, u64); +impl_try_truncate_into!(@wrapped F64, f64, u32); +impl_try_truncate_into!(@wrapped F64, f64, u64); + +macro_rules! impl_extend_into { + ($from:ident, $into:ident) => { + impl ExtendInto<$into> for $from { + #[inline] + fn extend_into(self) -> $into { + self as $into + } + } + }; + ($from:ident, $intermediate:ident, $into:ident) => { + impl ExtendInto<$into> for $from { + #[inline] + fn extend_into(self) -> $into { + $into::from(self as $intermediate) + } + } + }; +} + +impl_extend_into!(i8, i32); +impl_extend_into!(u8, i32); +impl_extend_into!(i16, i32); +impl_extend_into!(u16, i32); +impl_extend_into!(i8, i64); +impl_extend_into!(u8, i64); +impl_extend_into!(i16, i64); +impl_extend_into!(u16, i64); +impl_extend_into!(i32, i64); +impl_extend_into!(u32, i64); +impl_extend_into!(u32, u64); + +impl_extend_into!(i32, f32, F32); +impl_extend_into!(i32, f64, F64); +impl_extend_into!(u32, f32, F32); +impl_extend_into!(u32, f64, F64); +impl_extend_into!(i64, f64, F64); +impl_extend_into!(u64, f64, F64); +impl_extend_into!(f32, f64, F64); + +// Casting to self +impl_extend_into!(i32, i32); +impl_extend_into!(i64, i64); +impl_extend_into!(F32, F32); +impl_extend_into!(F64, F64); + +impl ExtendInto for F32 { + #[inline] + fn extend_into(self) -> F64 { + F64::from(f64::from(f32::from(self))) + } +} + +macro_rules! impl_sign_extend_from { + ( $( impl SignExtendFrom<$from_type:ty> for $for_type:ty; )* ) => { + $( + impl SignExtendFrom<$from_type> for $for_type { + #[inline] + fn sign_extend_from(self) -> Self { + (self as $from_type) as Self + } + } + )* + }; +} +impl_sign_extend_from! { + impl SignExtendFrom for i32; + impl SignExtendFrom for i32; + impl SignExtendFrom for i64; + impl SignExtendFrom for i64; + impl SignExtendFrom for i64; +} + +macro_rules! impl_transmute_into_self { + ($type: ident) => { + impl TransmuteInto<$type> for $type { + #[inline] + fn transmute_into(self) -> $type { + self + } + } + }; +} + +impl_transmute_into_self!(i32); +impl_transmute_into_self!(i64); +impl_transmute_into_self!(f32); +impl_transmute_into_self!(f64); +impl_transmute_into_self!(F32); +impl_transmute_into_self!(F64); + +macro_rules! impl_transmute_into_as { + ($from: ident, $into: ident) => { + impl TransmuteInto<$into> for $from { + #[inline] + fn transmute_into(self) -> $into { + self as $into + } + } + }; +} + +impl_transmute_into_as!(i8, u8); +impl_transmute_into_as!(i32, u32); +impl_transmute_into_as!(i64, u64); + +macro_rules! impl_transmute_into_npf { + ($npf:ident, $float:ident, $signed:ident, $unsigned:ident) => { + impl TransmuteInto<$float> for $npf { + #[inline] + fn transmute_into(self) -> $float { + self.into() + } + } + + impl TransmuteInto<$npf> for $float { + #[inline] + fn transmute_into(self) -> $npf { + self.into() + } + } + + impl TransmuteInto<$signed> for $npf { + #[inline] + fn transmute_into(self) -> $signed { + self.to_bits() as _ + } + } + + impl TransmuteInto<$unsigned> for $npf { + #[inline] + fn transmute_into(self) -> $unsigned { + self.to_bits() + } + } + + impl TransmuteInto<$npf> for $signed { + #[inline] + fn transmute_into(self) -> $npf { + $npf::from_bits(self as _) + } + } + + impl TransmuteInto<$npf> for $unsigned { + #[inline] + fn transmute_into(self) -> $npf { + $npf::from_bits(self) + } + } + }; +} + +impl_transmute_into_npf!(F32, f32, i32, u32); +impl_transmute_into_npf!(F64, f64, i64, u64); + +impl TransmuteInto for f32 { + #[inline] + fn transmute_into(self) -> i32 { + self.to_bits() as i32 + } +} + +impl TransmuteInto for f64 { + #[inline] + fn transmute_into(self) -> i64 { + self.to_bits() as i64 + } +} + +impl TransmuteInto for i32 { + #[inline] + fn transmute_into(self) -> f32 { + f32::from_bits(self as u32) + } +} + +impl TransmuteInto for i64 { + #[inline] + fn transmute_into(self) -> f64 { + f64::from_bits(self as u64) + } +} + +impl TransmuteInto for u32 { + #[inline] + fn transmute_into(self) -> i32 { + self as _ + } +} + +impl TransmuteInto for u64 { + #[inline] + fn transmute_into(self) -> i64 { + self as _ + } +} + +macro_rules! impl_integer_arithmetic_ops { + ($type: ident) => { + impl ArithmeticOps<$type> for $type { + #[inline] + fn add(self, other: $type) -> $type { + self.wrapping_add(other) + } + #[inline] + fn sub(self, other: $type) -> $type { + self.wrapping_sub(other) + } + #[inline] + fn mul(self, other: $type) -> $type { + self.wrapping_mul(other) + } + } + }; +} + +impl_integer_arithmetic_ops!(i32); +impl_integer_arithmetic_ops!(u32); +impl_integer_arithmetic_ops!(i64); +impl_integer_arithmetic_ops!(u64); + +macro_rules! impl_float_arithmetic_ops { + ($type:ty) => { + impl ArithmeticOps for $type { + #[inline] + fn add(self, other: Self) -> Self { + self + other + } + #[inline] + fn sub(self, other: Self) -> Self { + self - other + } + #[inline] + fn mul(self, other: Self) -> Self { + self * other + } + } + }; +} + +impl_float_arithmetic_ops!(f32); +impl_float_arithmetic_ops!(f64); +impl_float_arithmetic_ops!(F32); +impl_float_arithmetic_ops!(F64); + +macro_rules! impl_integer { + ($type:ty) => { + impl Integer for $type { + #[inline] + fn leading_zeros(self) -> Self { + self.leading_zeros() as _ + } + #[inline] + fn trailing_zeros(self) -> Self { + self.trailing_zeros() as _ + } + #[inline] + fn count_ones(self) -> Self { + self.count_ones() as _ + } + #[inline] + fn rotl(self, other: Self) -> Self { + self.rotate_left(other as u32) + } + #[inline] + fn rotr(self, other: Self) -> Self { + self.rotate_right(other as u32) + } + #[inline] + fn div(self, other: Self) -> Result { + if other == 0 { + return Err(RwasmError::IntegerDivisionByZero); + } + match self.overflowing_div(other) { + (result, false) => Ok(result), + _ => Err(RwasmError::IntegerOverflow), + } + } + #[inline] + fn rem(self, other: Self) -> Result { + if other == 0 { + return Err(RwasmError::IntegerDivisionByZero); + } + Ok(self.wrapping_rem(other)) + } + } + }; +} + +impl_integer!(i32); +impl_integer!(u32); +impl_integer!(i64); +impl_integer!(u64); + +#[cfg(feature = "std")] +mod fmath { + pub use f32; + pub use f64; +} + +#[cfg(not(feature = "std"))] +mod fmath { + pub use super::libm_adapters::{f32, f64}; +} + +// We cannot call the math functions directly, because they are not all available in `core`. +// In no-std cases we instead rely on `libm`. +// These wrappers handle that delegation. +macro_rules! impl_float { + ($type:ident, $fXX:ident, $iXX:ident) => { + // In this particular instance we want to directly compare floating point numbers. + impl Float for $type { + #[inline] + fn abs(self) -> Self { + fmath::$fXX::abs(<$fXX>::from(self)).into() + } + #[inline] + fn floor(self) -> Self { + fmath::$fXX::floor(<$fXX>::from(self)).into() + } + #[inline] + fn ceil(self) -> Self { + fmath::$fXX::ceil(<$fXX>::from(self)).into() + } + #[inline] + fn trunc(self) -> Self { + fmath::$fXX::trunc(<$fXX>::from(self)).into() + } + #[inline] + fn round(self) -> Self { + fmath::$fXX::round(<$fXX>::from(self)).into() + } + #[inline] + fn nearest(self) -> Self { + let round = self.round(); + if fmath::$fXX::fract(<$fXX>::from(self)).abs() != 0.5 { + return round; + } + let rem = ::core::ops::Rem::rem(round, 2.0); + if rem == 1.0 { + self.floor() + } else if rem == -1.0 { + self.ceil() + } else { + round + } + } + #[inline] + fn sqrt(self) -> Self { + fmath::$fXX::sqrt(<$fXX>::from(self)).into() + } + #[inline] + fn is_sign_positive(self) -> bool { + <$fXX>::is_sign_positive(<$fXX>::from(self)).into() + } + #[inline] + fn is_sign_negative(self) -> bool { + <$fXX>::is_sign_negative(<$fXX>::from(self)).into() + } + #[inline] + fn div(self, other: Self) -> Self { + self / other + } + #[inline] + fn min(self, other: Self) -> Self { + // The implementation strictly adheres to the mandated behavior for the Wasm + // specification. Note: In other contexts this API is also known as: + // `nan_min`. + match (self.is_nan(), other.is_nan()) { + (true, false) => self, + (false, true) => other, + _ => { + // Case: Both values are NaN; OR both values are non-NaN. + if other.is_sign_negative() { + return other.min(self); + } + self.min(other) + } + } + } + #[inline] + fn max(self, other: Self) -> Self { + // The implementation strictly adheres to the mandated behavior for the Wasm + // specification. Note: In other contexts this API is also known as: + // `nan_max`. + match (self.is_nan(), other.is_nan()) { + (true, false) => self, + (false, true) => other, + _ => { + // Case: Both values are NaN; OR both values are non-NaN. + if other.is_sign_positive() { + return other.max(self); + } + self.max(other) + } + } + } + #[inline] + fn copysign(self, other: Self) -> Self { + use core::mem::size_of; + let sign_mask: $iXX = 1 << ((size_of::<$iXX>() << 3) - 1); + let self_int: $iXX = self.transmute_into(); + let other_int: $iXX = other.transmute_into(); + let is_self_sign_set = (self_int & sign_mask) != 0; + let is_other_sign_set = (other_int & sign_mask) != 0; + if is_self_sign_set == is_other_sign_set { + self + } else if is_other_sign_set { + (self_int | sign_mask).transmute_into() + } else { + (self_int & !sign_mask).transmute_into() + } + } + } + }; +} + +#[test] +fn wasm_float_min_regression_works() { + assert_eq!( + Float::min(F32::from(-0.0), F32::from(0.0)).to_bits(), + 0x8000_0000, + ); + assert_eq!( + Float::min(F32::from(0.0), F32::from(-0.0)).to_bits(), + 0x8000_0000, + ); +} + +#[test] +fn wasm_float_max_regression_works() { + assert_eq!( + Float::max(F32::from(-0.0), F32::from(0.0)).to_bits(), + 0x0000_0000, + ); + assert_eq!( + Float::max(F32::from(0.0), F32::from(-0.0)).to_bits(), + 0x0000_0000, + ); +} + +impl_float!(f32, f32, i32); +impl_float!(f64, f64, i64); +impl_float!(F32, f32, i32); +impl_float!(F64, f64, i64); + +#[test] +fn copysign_regression_works() { + // This test has been directly extracted from a WebAssembly Specification assertion. + use Float as _; + assert!(F32::from_bits(0xFFC00000).is_nan()); + assert_eq!( + F32::from_bits(0xFFC00000) + .copysign(F32::from_bits(0x0000_0000)) + .to_bits(), + F32::from_bits(0x7FC00000).to_bits() + ) +} + +#[cfg(not(feature = "std"))] +mod libm_adapters { + pub mod f32 { + #[inline] + pub fn abs(v: f32) -> f32 { + libm::fabsf(v) + } + + #[inline] + pub fn floor(v: f32) -> f32 { + libm::floorf(v) + } + + #[inline] + pub fn ceil(v: f32) -> f32 { + libm::ceilf(v) + } + + #[inline] + pub fn trunc(v: f32) -> f32 { + libm::truncf(v) + } + + #[inline] + pub fn round(v: f32) -> f32 { + libm::roundf(v) + } + + #[inline] + pub fn fract(v: f32) -> f32 { + v - trunc(v) + } + + #[inline] + pub fn sqrt(v: f32) -> f32 { + libm::sqrtf(v) + } + } + + pub mod f64 { + #[inline] + pub fn abs(v: f64) -> f64 { + libm::fabs(v) + } + + #[inline] + pub fn floor(v: f64) -> f64 { + libm::floor(v) + } + + #[inline] + pub fn ceil(v: f64) -> f64 { + libm::ceil(v) + } + + #[inline] + pub fn trunc(v: f64) -> f64 { + libm::trunc(v) + } + + #[inline] + pub fn round(v: f64) -> f64 { + libm::round(v) + } + + #[inline] + pub fn fract(v: f64) -> f64 { + v - trunc(v) + } + + #[inline] + pub fn sqrt(v: f64) -> f64 { + libm::sqrt(v) + } + } +} + +/// Untyped instances that allow to be typed. +pub trait WithType { + /// The typed output type. + type Output; + + /// Converts `self` to [`Self::Output`] using `ty`. + fn with_type(self, ty: ValueType) -> Self::Output; +} + +impl WithType for UntypedValue { + type Output = Value; + + fn with_type(self, ty: ValueType) -> Self::Output { + match ty { + ValueType::I32 => Value::I32(self.into()), + ValueType::I64 => Value::I64(self.into()), + ValueType::F32 => Value::F32(self.into()), + ValueType::F64 => Value::F64(self.into()), + ValueType::FuncRef => Value::FuncRef(self.into()), + ValueType::ExternRef => Value::ExternRef(self.into()), + } + } +} + +impl From for UntypedValue { + fn from(value: Value) -> Self { + match value { + Value::I32(value) => value.into(), + Value::I64(value) => value.into(), + Value::F32(value) => value.into(), + Value::F64(value) => value.into(), + Value::FuncRef(value) => value.into(), + Value::ExternRef(value) => value.into(), + } + } +} + +/// Runtime representation of a value. +/// +/// Wasm code manipulate values of the four basic value types: +/// integers and floating-point (IEEE 754-2008) data of 32 or 64 bit width each, respectively. +/// +/// There is no distinction between signed and unsigned integer types. Instead, integers are +/// interpreted by respective operations as either unsigned or signed in two’s complement +/// representation. +#[derive(Clone, Debug)] +pub enum Value { + /// Value of 32-bit signed or unsigned integer. + I32(i32), + /// Value of 64-bit signed or unsigned integer. + I64(i64), + /// Value of 32-bit IEEE 754-2008 floating point number. + F32(F32), + /// Value of 64-bit IEEE 754-2008 floating point number. + F64(F64), + /// A nullable [`Func`][`crate::Func`] reference, a.k.a. [`FuncRef`]. + FuncRef(FuncRef), + /// A nullable external object reference, a.k.a. [`ExternRef`]. + ExternRef(ExternRef), +} + +impl Value { + /// Creates new default value of given type. + #[inline] + pub fn default(value_type: ValueType) -> Self { + match value_type { + ValueType::I32 => Self::I32(0), + ValueType::I64 => Self::I64(0), + ValueType::F32 => Self::F32(0f32.into()), + ValueType::F64 => Self::F64(0f64.into()), + ValueType::FuncRef => Self::FuncRef(FuncRef::null()), + ValueType::ExternRef => Self::ExternRef(ExternRef::null()), + } + } + + /// Get variable type for this value. + #[inline] + pub fn ty(&self) -> ValueType { + match *self { + Self::I32(_) => ValueType::I32, + Self::I64(_) => ValueType::I64, + Self::F32(_) => ValueType::F32, + Self::F64(_) => ValueType::F64, + Self::FuncRef(_) => ValueType::FuncRef, + Self::ExternRef(_) => ValueType::ExternRef, + } + } + + /// Returns the underlying `i32` if the type matches otherwise returns `None`. + pub fn i32(&self) -> Option { + match self { + Self::I32(value) => Some(*value), + _ => None, + } + } + + /// Returns the underlying `i64` if the type matches otherwise returns `None`. + pub fn i64(&self) -> Option { + match self { + Self::I64(value) => Some(*value), + _ => None, + } + } + + /// Returns the underlying `f32` if the type matches otherwise returns `None`. + pub fn f32(&self) -> Option { + match self { + Self::F32(value) => Some(*value), + _ => None, + } + } + + /// Returns the underlying `f64` if the type matches otherwise returns `None`. + pub fn f64(&self) -> Option { + match self { + Self::F64(value) => Some(*value), + _ => None, + } + } + + /// Returns the underlying `funcref` if the type matches otherwise returns `None`. + pub fn funcref(&self) -> Option<&FuncRef> { + match self { + Self::FuncRef(value) => Some(value), + _ => None, + } + } + + /// Returns the underlying `externref` if the type matches otherwise returns `None`. + pub fn externref(&self) -> Option<&ExternRef> { + match self { + Self::ExternRef(value) => Some(value), + _ => None, + } + } +} + +impl From for Value { + #[inline] + fn from(val: i32) -> Self { + Self::I32(val) + } +} + +impl From for Value { + #[inline] + fn from(val: i64) -> Self { + Self::I64(val) + } +} + +impl From for Value { + #[inline] + fn from(val: F32) -> Self { + Self::F32(val) + } +} + +impl From for Value { + #[inline] + fn from(val: F64) -> Self { + Self::F64(val) + } +} + +impl From for Value { + #[inline] + fn from(val: FuncRef) -> Self { + Self::FuncRef(val) + } +} + +pub fn split_i64_to_i32(value: i64) -> [i32; 2] { + let lower = (value & 0xFFFF_FFFF) as i32; // Extract lower 32 bits + let upper = (value >> 32) as i32; // Extract upper 32 bits + [lower, upper] +} diff --git a/executor/src/config.rs b/src/vm/config.rs similarity index 100% rename from executor/src/config.rs rename to src/vm/config.rs diff --git a/executor/src/context.rs b/src/vm/context.rs similarity index 96% rename from executor/src/context.rs rename to src/vm/context.rs index 65f92c385..91d15aa1b 100644 --- a/executor/src/context.rs +++ b/src/vm/context.rs @@ -1,5 +1,7 @@ -use crate::{RwasmError, RwasmExecutor}; -use rwasm::core::UntypedValue; +use crate::{ + types::{RwasmError, UntypedValue}, + vm::executor::RwasmExecutor, +}; pub struct Caller<'a, T> { vm: &'a mut RwasmExecutor, diff --git a/src/vm/data_entity.rs b/src/vm/data_entity.rs new file mode 100644 index 000000000..7ae9d67ed --- /dev/null +++ b/src/vm/data_entity.rs @@ -0,0 +1,52 @@ +use alloc::sync::Arc; + +/// An instantiated [`DataSegmentEntity`]. +/// +/// # Note +/// +/// With the `bulk-memory` Wasm proposal it is possible to interact +/// with data segments at runtime. Therefore Wasm instances now have +/// a need to have an instantiated representation of data segments. +#[derive(Debug)] +pub struct DataSegmentEntity { + /// The underlying bytes of the instance data segment. + /// + /// # Note + /// + /// These bytes are just readable after instantiation. + /// Using Wasm `data.drop` simply replaces the instance + /// with an empty one. + bytes: Option>, +} + +impl DataSegmentEntity { + /// Create an empty [`DataSegmentEntity`] representing dropped data segments. + pub fn empty() -> Self { + Self { bytes: None } + } + + pub fn new(bytes: Arc<[u8]>) -> Self { + Self { bytes: Some(bytes) } + } + + /// Performs an emptiness check. + /// This function returns `true` only if the segment contains no items and not just an empty + /// array. + /// This check is crucial to determine if a segment has been dropped. + pub fn is_empty(&self) -> bool { + self.bytes.is_none() + } + + /// Returns the bytes of the [`DataSegmentEntity`]. + pub fn bytes(&self) -> &[u8] { + self.bytes + .as_ref() + .map(|bytes| &bytes[..]) + .unwrap_or_else(|| &[]) + } + + /// Drops the bytes of the [`DataSegmentEntity`]. + pub fn drop_bytes(&mut self) { + self.bytes = None; + } +} diff --git a/executor/src/executor.rs b/src/vm/executor.rs similarity index 56% rename from executor/src/executor.rs rename to src/vm/executor.rs index f05264bca..fb04edd04 100644 --- a/executor/src/executor.rs +++ b/src/vm/executor.rs @@ -1,57 +1,100 @@ use crate::{ - always_failing_syscall_handler, - config::ExecutorConfig, - instr_ptr::InstructionPtr, - memory::GlobalMemory, - module::{InstructionData, RwasmModule2}, - opcodes::run_the_loop, - types::RwasmError, - SyscallHandler, - FUNC_REF_OFFSET, - N_DEFAULT_STACK_SIZE, - N_MAX_STACK_SIZE, - N_MAX_TABLE_SIZE, - TABLE_ELEMENT_NULL, -}; -use hashbrown::HashMap; -use rwasm::{ - core::{TrapCode, UntypedValue, ValueType, N_MAX_MEMORY_PAGES}, - engine::{ - bytecode::{ - AddressOffset, - DataSegmentIdx, - ElementSegmentIdx, - GlobalIdx, - SignatureIdx, - TableIdx, - }, - code_map::{FuncHeader, InstructionsRef}, - stack::{ValueStack, ValueStackPtr}, + types::{ + AddressOffset, DropKeep, FuelCosts, - Tracer, + GlobalIdx, + OpcodeData, + Pages, + RwasmError, + RwasmModule, + SignatureIdx, + TableIdx, + UntypedValue, + FUNC_REF_OFFSET, + N_DEFAULT_STACK_SIZE, + N_MAX_DATA_SEGMENTS, + N_MAX_RECURSION_DEPTH, + N_MAX_STACK_SIZE, + N_MAX_TABLE_SIZE, }, - memory::DataSegmentEntity, - module::{ - ConstExpr, - DataSegment, - DataSegmentKind, - ElementSegment, - ElementSegmentItems, - ElementSegmentKind, + vm::{ + config::ExecutorConfig, + handler::{always_failing_syscall_handler, SyscallHandler}, + instr_ptr::InstructionPtr, + memory::GlobalMemory, + opcodes::run_the_loop, + table_entity::TableEntity, + tracer::Tracer, + value_stack::{ValueStack, ValueStackPtr}, }, - rwasm::N_MAX_RECURSION_DEPTH, - store::ResourceLimiterRef, - table::{ElementSegmentEntity, TableEntity}, - MemoryType, - TableType, - Value, }; -use std::sync::Arc; +use alloc::sync::Arc; +use bitvec::{bitvec, prelude::BitVec}; +use hashbrown::HashMap; +/// The `RwasmExecutor` struct represents the state and functionality required to execute +/// WebAssembly (WASM) instructions within an embedded WASM runtime environment. +/// It manages the +/// execution context, configuration, and other runtime parts necessary for the proper +/// execution and operation of WASM modules, particularly when leveraging the `rwasm` ecosystem. +/// +/// # Generic Parameters +/// - `T`: +/// Custom execution context type to enable user-defined functionality during WASM execution. +/// +/// # Fields +/// - `module`: A reference-counted pointer to the `RwasmModule` representing the compiled and +/// loaded WASM module. +/// - `config`: Configuration settings for the WASM executor, encapsulated in an `ExecutorConfig`. +/// - `consumed_fuel`: Tracks the total amount of fuel consumed, where fuel represents computational +/// resource usage. +/// - `refunded_fuel`: Tracks the amount of fuel refunded during execution, allowing optimizations +/// and reimbursements. +/// - `value_stack`: Stack for runtime values, used for computations and function calls in the WASM +/// runtime. +/// - `sp`: Pointer to the current position in the value stack. +/// - `global_memory`: Representation of the global memory accessible to the WASM module during +/// execution. +/// - `ip`: Instruction pointer used to track the next instruction to be executed. +/// - `context`: Custom execution context provided by the user, allowing external state to interact +/// with the executor. +/// - `tracer`: Optional field for an instance of `Tracer`, +/// used to trace or debug execution flow if +/// enabled. +/// - `fuel_costs`: Structure representing fuel consumption costs for various operations, enabling +/// fine-grained control of execution resources. +/// - `tables`: A map associating `TableIdx` (table index) to `TableEntity`, representing managed +/// tables in the WASM module. +/// - `call_stack`: A stack of instruction pointers, +/// used to manage nested function calls and return +/// points during execution. +/// - `last_signature`: Optionally stores the last used signature index, needed for validating +/// indirect function calls. +/// - `next_result`: Optionally stores the result of the next operation, either a valid result or an +/// error of type `RwasmError`. +/// - `stop_exec`: A boolean flag indicating whether the execution should halt prematurely. +/// - `syscall_handler`: A handler of type `SyscallHandler` to execute host function calls or +/// system calls invoked by the WASM module. +/// - `default_elements_segment`: A vector of untyped values representing the default elements +/// segment used in `rwasm`'s modified execution context. +/// - `global_variables`: A map of global variable indices (`GlobalIdx`) to untyped values, +/// representing global variables in the WASM runtime. +/// - `empty_elements_segments`: A bit vector indicating which element segments are considered +/// empty. +/// - `empty_data_segments`: A bit vector indicating which data segments are considered empty. +/// +/// # Usage +/// The `RwasmExecutor` is designed to be instantiated and used as the main driver for executing +/// WASM programs. +/// It maintains the state required for computation, controls the flow of execution, +/// and integrates user-defined functionality via the `T` generic execution context. +/// +/// Note: This struct is intended as part of an internal runtime and may not expose all fields to +/// public interfaces. pub struct RwasmExecutor { // function segments - pub(crate) module: Arc, + pub(crate) module: Arc, pub(crate) config: ExecutorConfig, // execution context information pub(crate) consumed_fuel: u64, @@ -64,10 +107,11 @@ pub struct RwasmExecutor { pub(crate) tracer: Option, pub(crate) fuel_costs: FuelCosts, // rwasm modified segments - pub(crate) global_variables: HashMap, pub(crate) tables: HashMap, - pub(crate) data_segments: HashMap, - pub(crate) elements: HashMap, + pub(crate) default_elements_segment: Vec, + pub(crate) global_variables: HashMap, + pub(crate) empty_elements_segments: BitVec, + pub(crate) empty_data_segments: BitVec, // list of nested calls return pointers pub(crate) call_stack: Vec, // the last used signature (needed for indirect calls type checks) @@ -84,45 +128,26 @@ impl RwasmExecutor { context: T, ) -> Result { Ok(Self::new( - Arc::new(RwasmModule2::new(rwasm_bytecode)), + Arc::new(RwasmModule::new(rwasm_bytecode)), config, context, )) } - pub fn new(module: Arc, config: ExecutorConfig, context: T) -> Self { - // create stack with sp + pub fn new(module: Arc, config: ExecutorConfig, context: T) -> Self { + // create a stack with sp let mut value_stack = ValueStack::new(N_DEFAULT_STACK_SIZE, N_MAX_STACK_SIZE); let sp = value_stack.stack_ptr(); - // assign sp to the position inside code section - let mut ip = InstructionPtr::new(module.code_section.as_ptr(), module.instr_data.as_ptr()); + // assign sp to the position inside a code section + let mut ip = InstructionPtr::new(module.code_section.instr.as_ptr()); ip.add(module.source_pc as usize); // create global memory - let mut resource_limiter_ref = ResourceLimiterRef::default(); - let global_memory = GlobalMemory::new( - MemoryType::new(0, Some(N_MAX_MEMORY_PAGES)).expect("rwasm: bad initial memory"), - &mut resource_limiter_ref, - ) - .expect("rwasm: bad initial memory"); - - // create the main element segment (index 0) from the module elements - let mut element_segments = HashMap::new(); - element_segments.insert( - ElementSegmentIdx::from(0u32), - ElementSegmentEntity::from(&ElementSegment { - kind: ElementSegmentKind::Passive, - ty: ValueType::I32, - items: ElementSegmentItems { - exprs: module - .element_section - .iter() - .map(|v| ConstExpr::from_const((*v + FUNC_REF_OFFSET).into())) - .collect(), - }, - }), - ); + let global_memory = GlobalMemory::new(Pages::default()); + + let dropped_elements = bitvec![0; N_MAX_TABLE_SIZE]; + let empty_data_segments = bitvec![0; N_MAX_DATA_SEGMENTS]; let tracer = if config.trace_enabled { Some(Tracer::default()) @@ -130,6 +155,13 @@ impl RwasmExecutor { None }; + let module_elements_section = module + .element_section + .iter() + .copied() + .map(|v| UntypedValue::from(v + FUNC_REF_OFFSET)) + .collect::>(); + Self { module, config, @@ -144,13 +176,14 @@ impl RwasmExecutor { fuel_costs: Default::default(), global_variables: Default::default(), tables: Default::default(), - data_segments: Default::default(), - elements: element_segments, call_stack: vec![], last_signature: None, next_result: None, stop_exec: false, syscall_handler: always_failing_syscall_handler, + default_elements_segment: module_elements_section, + empty_elements_segments: dropped_elements, + empty_data_segments, } } @@ -163,10 +196,7 @@ impl RwasmExecutor { } pub fn reset(&mut self, pc: Option) { - let mut ip = InstructionPtr::new( - self.module.code_section.as_ptr(), - self.module.instr_data.as_ptr(), - ); + let mut ip = InstructionPtr::new(self.module.code_section.instr.as_ptr()); ip.add(pc.unwrap_or(self.module.source_pc as usize)); self.ip = ip; self.consumed_fuel = 0; @@ -184,7 +214,7 @@ impl RwasmExecutor { let consumed_fuel = self.consumed_fuel.checked_add(fuel).unwrap_or(u64::MAX); if let Some(fuel_limit) = self.config.fuel_limit { if consumed_fuel > fuel_limit { - return Err(RwasmError::TrapCode(TrapCode::OutOfFuel)); + return Err(RwasmError::OutOfFuel); } } self.consumed_fuel = consumed_fuel; @@ -242,82 +272,11 @@ impl RwasmExecutor { } } - pub(crate) fn resolve_table(&mut self, table_idx: TableIdx) -> &mut TableEntity { - self.tables - .get_mut(&table_idx) - .expect("rwasm: missing table") - } - - pub(crate) fn resolve_table_or_create(&mut self, table_idx: TableIdx) -> &mut TableEntity { - self.tables - .entry(table_idx) - .or_insert_with(Self::empty_table) - } - - fn empty_table() -> TableEntity { - let mut dummy_resource_limiter = ResourceLimiterRef::default(); - TableEntity::new( - TableType::new(ValueType::I32, 0, Some(N_MAX_TABLE_SIZE as u32)), - Value::I32(TABLE_ELEMENT_NULL as i32), - &mut dummy_resource_limiter, - ) - .unwrap() - } - - fn empty_element_segment() -> ElementSegmentEntity { - ElementSegmentEntity::from(&ElementSegment { - kind: ElementSegmentKind::Passive, - ty: ValueType::I32, - items: ElementSegmentItems { exprs: [].into() }, - }) - } - - fn empty_data_segment() -> DataSegmentEntity { - DataSegmentEntity::from(&DataSegment { - kind: DataSegmentKind::Passive, - bytes: [0x1].into(), - }) - } - - pub(crate) fn resolve_data_or_create( - &mut self, - data_segment_idx: DataSegmentIdx, - ) -> &mut DataSegmentEntity { - self.data_segments - .entry(data_segment_idx) - .or_insert_with(Self::empty_data_segment) - } - - pub(crate) fn resolve_element_or_create( - &mut self, - element_idx: ElementSegmentIdx, - ) -> &mut ElementSegmentEntity { - self.elements - .entry(element_idx) - .or_insert_with(Self::empty_element_segment) - } - - pub(crate) fn resolve_table_with_element_or_create( - &mut self, - table_idx: TableIdx, - element_idx: ElementSegmentIdx, - ) -> (&mut TableEntity, &mut ElementSegmentEntity) { - let table_entity = self - .tables - .entry(table_idx) - .or_insert_with(Self::empty_table); - let element_entity = self - .elements - .entry(element_idx) - .or_insert_with(Self::empty_element_segment); - (table_entity, element_entity) - } - pub(crate) fn fetch_drop_keep(&self, offset: usize) -> DropKeep { let mut addr: InstructionPtr = self.ip; addr.add(offset); match addr.data() { - InstructionData::DropKeep(drop_keep) => *drop_keep, + OpcodeData::DropKeep(drop_keep) => *drop_keep, _ => unreachable!("rwasm: can't extract drop keep"), } } @@ -326,7 +285,7 @@ impl RwasmExecutor { let mut addr: InstructionPtr = self.ip; addr.add(offset); match addr.data() { - InstructionData::TableIdx(table_idx) => *table_idx, + OpcodeData::TableIdx(table_idx) => *table_idx, _ => unreachable!("rwasm: can't extract table index"), } } @@ -339,8 +298,8 @@ impl RwasmExecutor { memory: &[u8], address: UntypedValue, offset: u32, - ) -> Result, - ) -> Result<(), TrapCode> { + ) -> Result, + ) -> Result<(), RwasmError> { self.sp.try_eval_top(|address| { let memory = self.global_memory.data(); let value = load_extend(memory, address, offset.into_inner())?; @@ -359,9 +318,9 @@ impl RwasmExecutor { address: UntypedValue, offset: u32, value: UntypedValue, - ) -> Result<(), TrapCode>, + ) -> Result<(), RwasmError>, len: u32, - ) -> Result<(), TrapCode> { + ) -> Result<(), RwasmError> { let (address, value) = self.sp.pop2(); let memory = self.global_memory.data_mut(); store_wrap(memory, address, offset.into_inner(), value)?; @@ -394,8 +353,8 @@ impl RwasmExecutor { #[inline(always)] pub(crate) fn try_execute_unary( &mut self, - f: fn(UntypedValue) -> Result, - ) -> Result<(), TrapCode> { + f: fn(UntypedValue) -> Result, + ) -> Result<(), RwasmError> { self.sp.try_eval_top(f)?; self.ip.add(1); Ok(()) @@ -404,8 +363,8 @@ impl RwasmExecutor { #[inline(always)] pub(crate) fn try_execute_binary( &mut self, - f: fn(UntypedValue, UntypedValue) -> Result, - ) -> Result<(), TrapCode> { + f: fn(UntypedValue, UntypedValue) -> Result, + ) -> Result<(), RwasmError> { self.sp.try_eval_top2(f)?; self.ip.add(1); Ok(()) @@ -422,23 +381,18 @@ impl RwasmExecutor { self.value_stack.sync_stack_ptr(self.sp); if is_nested_call { if self.call_stack.len() > N_MAX_RECURSION_DEPTH { - return Err(RwasmError::TrapCode(TrapCode::StackOverflow)); + return Err(RwasmError::StackOverflow); } self.call_stack.push(self.ip); } let instr_ref = self .module - .func_segments + .func_section .get(func_idx as usize) .copied() .expect("rwasm: unknown internal function"); - let header = FuncHeader::new(InstructionsRef::uninit(), 0, 0); - self.value_stack.prepare_wasm_call(&header)?; self.sp = self.value_stack.stack_ptr(); - self.ip = InstructionPtr::new( - self.module.code_section.as_ptr(), - self.module.instr_data.as_ptr(), - ); + self.ip = InstructionPtr::new(self.module.code_section.instr.as_ptr()); self.ip.add(instr_ref as usize); Ok(()) } diff --git a/executor/src/handler.rs b/src/vm/handler.rs similarity index 94% rename from executor/src/handler.rs rename to src/vm/handler.rs index e76d9d61e..225a591ad 100644 --- a/executor/src/handler.rs +++ b/src/vm/handler.rs @@ -1,6 +1,7 @@ -use crate::{context::Caller, RwasmError}; -use rwasm::core::UntypedValue; -use tiny_keccak::Hasher; +use crate::{ + types::{RwasmError, UntypedValue}, + vm::context::Caller, +}; pub type SyscallHandler = fn(Caller, u32) -> Result<(), RwasmError>; @@ -24,7 +25,7 @@ struct SimpleCallHandler; impl SimpleCallHandler { fn fn_proc_exit(mut caller: Caller) -> Result<(), RwasmError> { let exit_code = caller.stack_pop(); - Err(RwasmError::ExecutionHalted(exit_code.as_i32())) + Err(RwasmError::ExecutionHalted(i32::from(exit_code))) } fn fn_get_state(mut caller: Caller) -> Result<(), RwasmError> { @@ -58,6 +59,7 @@ impl SimpleCallHandler { } fn fn_keccak256(mut caller: Caller) -> Result<(), RwasmError> { + use tiny_keccak::Hasher; let [data_offset, data_len, output32_offset] = caller.stack_pop_n(); let mut buffer = vec![0u8; data_len.as_usize()]; caller.memory_read(data_offset.as_usize(), &mut buffer)?; diff --git a/executor/src/instr_ptr.rs b/src/vm/instr_ptr.rs similarity index 76% rename from executor/src/instr_ptr.rs rename to src/vm/instr_ptr.rs index 6ad0b02ca..866501430 100644 --- a/executor/src/instr_ptr.rs +++ b/src/vm/instr_ptr.rs @@ -1,19 +1,17 @@ -use crate::module::{Instruction, InstructionData}; +use crate::types::{Opcode, OpcodeData}; /// The instruction pointer to the instruction of a function on the call stack. #[derive(Debug, Copy, Clone, PartialEq)] pub struct InstructionPtr { /// The pointer to the instruction. - pub(crate) ptr: *const Instruction, - pub(crate) src: *const Instruction, - /// Info about data - pub(crate) data: *const InstructionData, + pub(crate) ptr: *const (Opcode, OpcodeData), + pub(crate) src: *const (Opcode, OpcodeData), } /// It is safe to send an [`rwasm::engine::code_map::InstructionPtr`] to another thread. /// -/// The access to the pointed-to [`Instruction`] is read-only and -/// [`Instruction`] itself is [`Send`]. +/// The access to the pointed-to [`Opcode`] is read-only and +/// [`Opcode`] itself is [`Send`]. /// /// However, it is not safe to share an [`rwasm::engine::code_map::InstructionPtr`] between threads /// due to their [`rwasm::engine::code_map::InstructionPtr::offset`] method which relinks the @@ -23,17 +21,13 @@ unsafe impl Send for InstructionPtr {} impl InstructionPtr { /// Creates a new [`rwasm::engine::code_map::InstructionPtr`] for `instr`. #[inline] - pub fn new(ptr: *const Instruction, data: *const InstructionData) -> Self { - Self { - ptr, - src: ptr, - data, - } + pub fn new(ptr: *const (Opcode, OpcodeData)) -> Self { + Self { ptr, src: ptr } } #[inline(always)] pub fn pc(&self) -> u32 { - let size = size_of::() as u32; + let size = size_of::<(Opcode, OpcodeData)>() as u32; let diff = self.ptr as u32 - self.src as u32; diff / size } @@ -51,7 +45,6 @@ impl InstructionPtr { // Wasm validation and `wasmi` codegen to never run out // of valid bounds using this method. self.ptr = unsafe { self.ptr.offset(by) }; - self.data = unsafe { self.data.offset(by) }; } #[inline(always)] @@ -60,10 +53,9 @@ impl InstructionPtr { // Wasm validation and `wasmi` codegen to never run out // of valid bounds using this method. self.ptr = unsafe { self.ptr.add(delta) }; - self.data = unsafe { self.data.add(delta) }; } - /// Returns a shared reference to the currently pointed at [`Instruction`]. + /// Returns a shared reference to the currently pointed at [`Opcode`]. /// /// # Safety /// @@ -71,18 +63,18 @@ impl InstructionPtr { /// guaranteed that the [`rwasm::engine::code_map::InstructionPtr`] is validly pointing inside /// the boundaries of its associated compiled Wasm function. #[inline(always)] - pub fn get(&self) -> &Instruction { + pub fn get(&self) -> Opcode { // SAFETY: Within Wasm bytecode execution we are guaranteed by // Wasm validation and `wasmi` codegen to never run out // of valid bounds using this method. - unsafe { &*self.ptr } + unsafe { &*self.ptr }.0 } #[inline(always)] - pub fn data(&self) -> &InstructionData { + pub fn data(&self) -> &OpcodeData { // SAFETY: Within Wasm bytecode execution we are guaranteed by // Wasm validation and `wasmi` codegen to never run out // of valid bounds using this method. - unsafe { &*self.data } + &unsafe { &*self.ptr }.1 } } diff --git a/executor/src/instruction_table.rs b/src/vm/instruction_table.rs similarity index 99% rename from executor/src/instruction_table.rs rename to src/vm/instruction_table.rs index 30d3dd92b..160b03658 100644 --- a/executor/src/instruction_table.rs +++ b/src/vm/instruction_table.rs @@ -1,4 +1,4 @@ -use crate::RwasmExecutor; +use crate::vm::executor::RwasmExecutor; pub type VisitInstruction = fn(&mut RwasmExecutor); @@ -6,7 +6,7 @@ pub type InstructionTable = [VisitInstruction; 0x100]; #[inline] pub const fn make_instruction_table() -> InstructionTable { - use crate::{module::Instruction::*, opcodes::*}; + use crate::{types::Opcode::*, vm::opcodes::*}; const { let mut tables: InstructionTable = [visit_unreachable_wrapped; 0x100]; tables[Unreachable as usize] = visit_unreachable_wrapped; diff --git a/src/vm/memory.rs b/src/vm/memory.rs new file mode 100644 index 000000000..783b253c2 --- /dev/null +++ b/src/vm/memory.rs @@ -0,0 +1,106 @@ +use crate::types::{Pages, RwasmError, N_MAX_MEMORY_PAGES}; +use bytes::BytesMut; + +pub struct GlobalMemory { + pub shared_memory: BytesMut, + pub current_pages: Pages, +} + +const MEMORY_MAX_PAGES: Pages = Pages::new_unchecked(N_MAX_MEMORY_PAGES); + +impl GlobalMemory { + pub fn new(initial_pages: Pages) -> Self { + let initial_len = initial_pages + .to_bytes() + .expect("rwasm: not supported target pointer width"); + let maximum_len = MEMORY_MAX_PAGES + .to_bytes() + .expect("rwasm: not supported target pointer width"); + if initial_len > maximum_len { + unreachable!("rwasm: initial memory size is greater than the maximum"); + } + let mut shared_memory = BytesMut::with_capacity(maximum_len); + shared_memory.resize(initial_len, 0); + Self { + shared_memory, + current_pages: initial_pages, + } + } + + /// Returns the number of pages in use by the linear memory. + pub fn current_pages(&self) -> Pages { + self.current_pages + } + + /// Grows the linear memory by the given number of new pages. + /// + /// Returns the number of pages before the operation upon success. + /// + /// # Errors + /// + /// If the linear memory grows beyond its maximum limit after + /// the growth operation. + pub fn grow(&mut self, additional: Pages) -> Result { + let current_pages = self.current_pages(); + if additional == Pages::from(0) { + return Ok(current_pages); + } + let desired_pages = current_pages + .checked_add(additional) + .ok_or(RwasmError::GrowthOperationLimited)?; + if desired_pages > MEMORY_MAX_PAGES { + return Err(RwasmError::GrowthOperationLimited); + } + // At this point, it is okay to grow the underlying virtual memory + // by the given number of additional pages. + let new_size = desired_pages + .to_bytes() + .expect("rwasm: not supported target pointer width"); + assert!(new_size >= self.shared_memory.len()); + self.shared_memory.resize(new_size, 0); + self.current_pages = desired_pages; + Ok(current_pages) + } + + /// Returns a shared slice to the bytes underlying to the byte buffer. + pub fn data(&self) -> &[u8] { + self.shared_memory.as_ref() + } + + /// Returns an exclusive slice to the bytes underlying to the byte buffer. + pub fn data_mut(&mut self) -> &mut [u8] { + self.shared_memory.as_mut() + } + + /// Reads `n` bytes from `memory[offset..offset+n]` into `buffer` + /// where `n` is the length of `buffer`. + /// + /// # Errors + /// + /// If this operation accesses out of bounds linear memory. + pub fn read(&self, offset: usize, buffer: &mut [u8]) -> Result<(), RwasmError> { + let len_buffer = buffer.len(); + let slice = self + .data() + .get(offset..(offset + len_buffer)) + .ok_or(RwasmError::MemoryOutOfBounds)?; + buffer.copy_from_slice(slice); + Ok(()) + } + + /// Writes `n` bytes to `memory[offset..offset+n]` from `buffer` + /// where `n` if the length of `buffer`. + /// + /// # Errors + /// + /// If this operation accesses out of bounds linear memory. + pub fn write(&mut self, offset: usize, buffer: &[u8]) -> Result<(), RwasmError> { + let len_buffer = buffer.len(); + let slice = self + .data_mut() + .get_mut(offset..(offset + len_buffer)) + .ok_or(RwasmError::MemoryOutOfBounds)?; + slice.copy_from_slice(buffer); + Ok(()) + } +} diff --git a/src/vm/mod.rs b/src/vm/mod.rs new file mode 100644 index 000000000..06068756c --- /dev/null +++ b/src/vm/mod.rs @@ -0,0 +1,24 @@ +mod config; +mod context; +mod data_entity; +mod executor; +mod handler; +mod instr_ptr; +mod instruction_table; +mod memory; +mod opcodes; +mod table_entity; +mod tracer; +mod value_stack; + +pub use config::*; +pub use context::*; +pub use data_entity::*; +pub use executor::*; +pub use handler::*; +pub use instr_ptr::*; +pub use instruction_table::*; +pub use memory::*; +pub use table_entity::*; +pub use tracer::*; +pub use value_stack::*; diff --git a/executor/src/opcodes.rs b/src/vm/opcodes.rs similarity index 83% rename from executor/src/opcodes.rs rename to src/vm/opcodes.rs index 167a12e15..86d4c7c4d 100644 --- a/executor/src/opcodes.rs +++ b/src/vm/opcodes.rs @@ -1,30 +1,22 @@ use crate::{ - executor::RwasmExecutor, - impl_visit_binary, - impl_visit_fallible_binary, - impl_visit_fallible_unary, - impl_visit_load, - impl_visit_store, - impl_visit_unary, - instr_ptr::InstructionPtr, - module::{Instruction, InstructionData}, - Caller, - RwasmError, - FUNC_REF_OFFSET, - TABLE_ELEMENT_NULL, -}; -use core::cmp; -use rwasm::{ - core::{Pages, TrapCode, UntypedValue}, - engine::{ - bytecode::ElementSegmentIdx, - code_map::{FuncHeader, InstructionsRef}, - executor::EntityGrowError, + types::{ + Opcode, + OpcodeData, + Pages, + RwasmError, + UntypedValue, + FUNC_REF_NULL, + FUNC_REF_OFFSET, + N_MAX_RECURSION_DEPTH, + }, + vm::{ + context::Caller, + executor::RwasmExecutor, + instr_ptr::InstructionPtr, + table_entity::TableEntity, }, - rwasm::N_MAX_RECURSION_DEPTH, - store::ResourceLimiterRef, - table::{ElementSegmentEntity, TableEntity}, }; +use core::cmp; pub(crate) fn run_the_loop(vm: &mut RwasmExecutor) -> Result { let floats_enabled = vm.config.floats_enabled; @@ -37,7 +29,7 @@ pub(crate) fn run_the_loop(vm: &mut RwasmExecutor) -> Result(vm: &mut RwasmExecutor) -> Result>() ); } - use Instruction::*; + use Opcode::*; match instr { Unreachable => visit_unreachable_wrapped(vm), LocalGet => visit_local_get(vm), @@ -336,13 +328,13 @@ wrap_function_result!(visit_stack_alloc); #[inline(always)] pub(crate) fn visit_unreachable(_vm: &mut RwasmExecutor) -> Result<(), RwasmError> { - Err(RwasmError::TrapCode(TrapCode::UnreachableCodeReached)) + Err(RwasmError::UnreachableCodeReached) } #[inline(always)] pub(crate) fn visit_local_get(vm: &mut RwasmExecutor) { let local_depth = match vm.ip.data() { - InstructionData::LocalDepth(local_depth) => local_depth, + OpcodeData::LocalDepth(local_depth) => local_depth, _ => unreachable!("rwasm: missing instr data"), }; let value = vm.sp.nth_back(local_depth.to_usize()); @@ -353,7 +345,7 @@ pub(crate) fn visit_local_get(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_local_set(vm: &mut RwasmExecutor) { let local_depth = match vm.ip.data() { - InstructionData::LocalDepth(local_depth) => local_depth, + OpcodeData::LocalDepth(local_depth) => local_depth, _ => unreachable!("rwasm: missing instr data"), }; let new_value = vm.sp.pop(); @@ -364,7 +356,7 @@ pub(crate) fn visit_local_set(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_local_tee(vm: &mut RwasmExecutor) { let local_depth = match vm.ip.data() { - InstructionData::LocalDepth(local_depth) => local_depth, + OpcodeData::LocalDepth(local_depth) => local_depth, _ => unreachable!("rwasm: missing instr data"), }; let new_value = vm.sp.last(); @@ -375,7 +367,7 @@ pub(crate) fn visit_local_tee(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_br(vm: &mut RwasmExecutor) { let branch_offset = match vm.ip.data() { - InstructionData::BranchOffset(branch_offset) => branch_offset, + OpcodeData::BranchOffset(branch_offset) => branch_offset, _ => unreachable!("rwasm: missing instr data"), }; vm.ip.offset(branch_offset.to_i32() as isize) @@ -384,7 +376,7 @@ pub(crate) fn visit_br(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_br_if(vm: &mut RwasmExecutor) { let branch_offset = match vm.ip.data() { - InstructionData::BranchOffset(branch_offset) => branch_offset, + OpcodeData::BranchOffset(branch_offset) => branch_offset, _ => unreachable!("rwasm: missing instr data"), }; let condition = vm.sp.pop_as(); @@ -398,7 +390,7 @@ pub(crate) fn visit_br_if(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_br_if_nez(vm: &mut RwasmExecutor) { let branch_offset = match vm.ip.data() { - InstructionData::BranchOffset(branch_offset) => branch_offset, + OpcodeData::BranchOffset(branch_offset) => branch_offset, _ => unreachable!("rwasm: missing instr data"), }; let condition = vm.sp.pop_as(); @@ -412,7 +404,7 @@ pub(crate) fn visit_br_if_nez(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_br_adjust(vm: &mut RwasmExecutor) { let branch_offset = match vm.ip.data() { - InstructionData::BranchOffset(branch_offset) => branch_offset, + OpcodeData::BranchOffset(branch_offset) => branch_offset, _ => unreachable!("rwasm: missing instr data"), }; let drop_keep = vm.fetch_drop_keep(1); @@ -423,7 +415,7 @@ pub(crate) fn visit_br_adjust(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_br_adjust_if_nez(vm: &mut RwasmExecutor) { let branch_offset = match vm.ip.data() { - InstructionData::BranchOffset(branch_offset) => branch_offset, + OpcodeData::BranchOffset(branch_offset) => branch_offset, _ => unreachable!("rwasm: missing instr data"), }; let condition = vm.sp.pop_as(); @@ -439,7 +431,7 @@ pub(crate) fn visit_br_adjust_if_nez(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_br_table(vm: &mut RwasmExecutor) { let targets = match vm.ip.data() { - InstructionData::BranchTableTargets(targets) => targets, + OpcodeData::BranchTableTargets(targets) => targets, _ => unreachable!("rwasm: missing instr data"), }; let index: u32 = vm.sp.pop_as(); @@ -451,7 +443,7 @@ pub(crate) fn visit_br_table(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_consume_fuel(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let block_fuel = match vm.ip.data() { - InstructionData::BlockFuel(block_fuel) => block_fuel, + OpcodeData::BlockFuel(block_fuel) => block_fuel, _ => unreachable!("rwasm: missing instr data"), }; if vm.config.fuel_enabled { @@ -464,7 +456,7 @@ pub(crate) fn visit_consume_fuel(vm: &mut RwasmExecutor) -> Result<(), Rwa #[inline(always)] pub(crate) fn visit_return(vm: &mut RwasmExecutor) { let drop_keep = match vm.ip.data() { - InstructionData::DropKeep(drop_keep) => drop_keep, + OpcodeData::DropKeep(drop_keep) => drop_keep, _ => unreachable!("rwasm: missing instr data"), }; vm.sp.drop_keep(*drop_keep); @@ -483,7 +475,7 @@ pub(crate) fn visit_return(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_return_if_nez(vm: &mut RwasmExecutor) { let drop_keep = match vm.ip.data() { - InstructionData::DropKeep(drop_keep) => drop_keep, + OpcodeData::DropKeep(drop_keep) => drop_keep, _ => unreachable!("rwasm: missing instr data"), }; let condition = vm.sp.pop_as(); @@ -507,7 +499,7 @@ pub(crate) fn visit_return_if_nez(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_return_call_internal(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let func_idx = match vm.ip.data() { - InstructionData::CompiledFunc(func_idx) => *func_idx, + OpcodeData::CompiledFunc(func_idx) => *func_idx, _ => unreachable!("rwasm: missing instr data"), }; let drop_keep = vm.fetch_drop_keep(1); @@ -516,17 +508,12 @@ pub(crate) fn visit_return_call_internal(vm: &mut RwasmExecutor) -> Result vm.value_stack.sync_stack_ptr(vm.sp); let instr_ref = vm .module - .func_segments - .get(func_idx.to_u32() as usize) + .func_section + .get(func_idx as usize) .copied() .expect("rwasm: unknown internal function"); - let header = FuncHeader::new(InstructionsRef::uninit(), 0, 0); - vm.value_stack.prepare_wasm_call(&header)?; vm.sp = vm.value_stack.stack_ptr(); - vm.ip = InstructionPtr::new( - vm.module.code_section.as_ptr(), - vm.module.instr_data.as_ptr(), - ); + vm.ip = InstructionPtr::new(vm.module.code_section.instr.as_ptr()); vm.ip.add(instr_ref as usize); Ok(()) } @@ -534,7 +521,7 @@ pub(crate) fn visit_return_call_internal(vm: &mut RwasmExecutor) -> Result #[inline(always)] pub(crate) fn visit_return_call(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let func_idx = match vm.ip.data() { - InstructionData::CompiledFunc(func_idx) => *func_idx, + OpcodeData::CompiledFunc(func_idx) => *func_idx, _ => unreachable!("rwasm: missing instr data"), }; let drop_keep = vm.fetch_drop_keep(1); @@ -543,14 +530,14 @@ pub(crate) fn visit_return_call(vm: &mut RwasmExecutor) -> Result<(), Rwas // external call can cause interruption, // that is why it's important to increase IP before doing the call vm.ip.add(2); - (vm.syscall_handler)(Caller::new(vm), func_idx.to_u32())?; + (vm.syscall_handler)(Caller::new(vm), func_idx)?; Ok(()) } #[inline(always)] pub(crate) fn visit_return_call_indirect(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let signature_idx = match vm.ip.data() { - InstructionData::SignatureIdx(value) => *value, + OpcodeData::SignatureIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let drop_keep = vm.fetch_drop_keep(1); @@ -562,13 +549,12 @@ pub(crate) fn visit_return_call_indirect(vm: &mut RwasmExecutor) -> Result .tables .get(&table) .expect("rwasm: unresolved table index") - .get(func_index) - .and_then(|v| v.i32()) - .ok_or(TrapCode::TableOutOfBounds)? + .get_untyped(func_index) + .ok_or(RwasmError::TableOutOfBounds)? .try_into() .unwrap(); if func_idx == 0 { - return Err(TrapCode::IndirectCallToNull.into()); + return Err(RwasmError::IndirectCallToNull.into()); } let func_idx = func_idx - FUNC_REF_OFFSET; vm.execute_call_internal(false, 3, func_idx) @@ -577,28 +563,23 @@ pub(crate) fn visit_return_call_indirect(vm: &mut RwasmExecutor) -> Result #[inline(always)] pub(crate) fn visit_call_internal(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let func_idx = match vm.ip.data() { - InstructionData::CompiledFunc(value) => *value, + OpcodeData::CompiledFunc(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; vm.ip.add(1); vm.value_stack.sync_stack_ptr(vm.sp); if vm.call_stack.len() > N_MAX_RECURSION_DEPTH { - return Err(RwasmError::TrapCode(TrapCode::StackOverflow)); + return Err(RwasmError::StackOverflow); } vm.call_stack.push(vm.ip); let instr_ref = vm .module - .func_segments - .get(func_idx.to_u32() as usize) + .func_section + .get(func_idx as usize) .copied() .expect("rwasm: unknown internal function"); - let header = FuncHeader::new(InstructionsRef::uninit(), 0, 0); - vm.value_stack.prepare_wasm_call(&header)?; vm.sp = vm.value_stack.stack_ptr(); - vm.ip = InstructionPtr::new( - vm.module.code_section.as_ptr(), - vm.module.instr_data.as_ptr(), - ); + vm.ip = InstructionPtr::new(vm.module.code_section.instr.as_ptr()); vm.ip.add(instr_ref as usize); Ok(()) } @@ -606,7 +587,7 @@ pub(crate) fn visit_call_internal(vm: &mut RwasmExecutor) -> Result<(), Rw #[inline(always)] pub(crate) fn visit_call(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let func_idx = match vm.ip.data() { - InstructionData::FuncIdx(value) => *value, + OpcodeData::FuncIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; vm.value_stack.sync_stack_ptr(vm.sp); @@ -619,44 +600,39 @@ pub(crate) fn visit_call(vm: &mut RwasmExecutor) -> Result<(), RwasmError> #[inline(always)] pub(crate) fn visit_call_indirect(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let signature_idx = match vm.ip.data() { - InstructionData::SignatureIdx(value) => *value, + OpcodeData::SignatureIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; // resolve func index let table = vm.fetch_table_index(1); let func_index: u32 = vm.sp.pop_as(); vm.last_signature = Some(signature_idx); - let func_idx: u32 = vm + let func_idx = vm .tables .get(&table) .expect("rwasm: unresolved table index") - .get(func_index) - .and_then(|v| v.i32().map(|v| v as u32)) - .ok_or(TrapCode::TableOutOfBounds)?; - if func_idx == TABLE_ELEMENT_NULL { - return Err(TrapCode::IndirectCallToNull.into()); + .get_untyped(func_index) + .map(|v| v.as_u32()) + .ok_or(RwasmError::TableOutOfBounds)?; + if func_idx == FUNC_REF_NULL { + return Err(RwasmError::IndirectCallToNull); } let func_idx = func_idx - FUNC_REF_OFFSET; // call func vm.ip.add(2); vm.value_stack.sync_stack_ptr(vm.sp); if vm.call_stack.len() > N_MAX_RECURSION_DEPTH { - return Err(RwasmError::TrapCode(TrapCode::StackOverflow)); + return Err(RwasmError::StackOverflow); } vm.call_stack.push(vm.ip); let instr_ref = vm .module - .func_segments + .func_section .get(func_idx as usize) .copied() .expect("rwasm: unknown internal function"); - let header = FuncHeader::new(InstructionsRef::uninit(), 0, 0); - vm.value_stack.prepare_wasm_call(&header)?; vm.sp = vm.value_stack.stack_ptr(); - vm.ip = InstructionPtr::new( - vm.module.code_section.as_ptr(), - vm.module.instr_data.as_ptr(), - ); + vm.ip = InstructionPtr::new(vm.module.code_section.instr.as_ptr()); vm.ip.add(instr_ref as usize); Ok(()) } @@ -664,12 +640,12 @@ pub(crate) fn visit_call_indirect(vm: &mut RwasmExecutor) -> Result<(), Rw #[inline(always)] pub(crate) fn visit_signature_check(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let signature_idx = match vm.ip.data() { - InstructionData::SignatureIdx(value) => *value, + OpcodeData::SignatureIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; if let Some(actual_signature) = vm.last_signature.take() { if actual_signature != signature_idx { - return Err(TrapCode::BadSignature).map_err(Into::into); + return Err(RwasmError::BadSignature); } } vm.ip.add(1); @@ -698,7 +674,7 @@ pub(crate) fn visit_select(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_global_get(vm: &mut RwasmExecutor) { let global_idx = match vm.ip.data() { - InstructionData::GlobalIdx(value) => *value, + OpcodeData::GlobalIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let global_value = vm @@ -713,7 +689,7 @@ pub(crate) fn visit_global_get(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_global_set(vm: &mut RwasmExecutor) { let global_idx = match vm.ip.data() { - InstructionData::GlobalIdx(value) => *value, + OpcodeData::GlobalIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let new_value = vm.sp.pop(); @@ -721,6 +697,22 @@ pub(crate) fn visit_global_set(vm: &mut RwasmExecutor) { vm.ip.add(1); } +macro_rules! impl_visit_load { + ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { + $( + #[inline(always)] + pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { + let offset = match vm.ip.data() { + OpcodeData::AddressOffset(value) => *value, + _ => unreachable!("rwasm: missing instr data"), + }; + vm.execute_load_extend(offset, UntypedValue::$untyped_ident) + } + wrap_function_result!($visit_ident); + )* + } +} + impl_visit_load! { fn visit_i32_load(i32_load); fn visit_i64_load(i64_load); @@ -740,6 +732,22 @@ impl_visit_load! { fn visit_i64_load_i32_u(i64_load32_u); } +macro_rules! impl_visit_store { + ( $( fn $visit_ident:ident($untyped_ident:ident, $type_size:literal); )* ) => { + $( + #[inline(always)] + pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { + let offset = match vm.ip.data() { + OpcodeData::AddressOffset(value) => *value, + _ => unreachable!("rwasm: missing instr data"), + }; + vm.execute_store_wrap(offset, UntypedValue::$untyped_ident, $type_size) + } + wrap_function_result!($visit_ident); + )* + } +} + impl_visit_store! { fn visit_i32_store(i32_store, 4); fn visit_i64_store(i64_store, 8); @@ -763,7 +771,6 @@ pub(crate) fn visit_memory_size(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_memory_grow(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { - let mut limiter = ResourceLimiterRef::default(); let delta: u32 = vm.sp.pop_as(); let delta = match Pages::new(delta) { Some(delta) => delta, @@ -779,7 +786,7 @@ pub(crate) fn visit_memory_grow(vm: &mut RwasmExecutor) -> Result<(), Rwas } let new_pages = vm .global_memory - .grow(delta, &mut limiter) + .grow(delta) .map(u32::from) .unwrap_or(u32::MAX); vm.sp.push_as(new_pages); @@ -801,7 +808,7 @@ pub(crate) fn visit_memory_fill(vm: &mut RwasmExecutor) -> Result<(), Rwas .data_mut() .get_mut(offset..) .and_then(|memory| memory.get_mut(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; + .ok_or(RwasmError::MemoryOutOfBounds)?; memory.fill(byte); if let Some(tracer) = vm.tracer.as_mut() { tracer.memory_change(offset as u32, n as u32, memory); @@ -823,10 +830,10 @@ pub(crate) fn visit_memory_copy(vm: &mut RwasmExecutor) -> Result<(), Rwas let data = vm.global_memory.data_mut(); data.get(src_offset..) .and_then(|memory| memory.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; + .ok_or(RwasmError::MemoryOutOfBounds)?; data.get(dst_offset..) .and_then(|memory| memory.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; + .ok_or(RwasmError::MemoryOutOfBounds)?; data.copy_within(src_offset..src_offset.wrapping_add(n), dst_offset); if let Some(tracer) = vm.tracer.as_mut() { tracer.memory_change( @@ -842,10 +849,17 @@ pub(crate) fn visit_memory_copy(vm: &mut RwasmExecutor) -> Result<(), Rwas #[inline(always)] pub(crate) fn visit_memory_init(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let data_segment_idx = match vm.ip.data() { - InstructionData::DataSegmentIdx(value) => *value, + OpcodeData::DataSegmentIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; - let is_empty_data_segment = vm.resolve_data_or_create(data_segment_idx).is_empty(); + + let is_empty_data_segment = vm + .empty_data_segments + .get(data_segment_idx.to_u32() as usize) + .as_deref() + .copied() + .unwrap_or(false); + let (d, s, n) = vm.sp.pop3(); let n = i32::from(n) as usize; let src_offset = i32::from(s) as usize; @@ -858,7 +872,7 @@ pub(crate) fn visit_memory_init(vm: &mut RwasmExecutor) -> Result<(), Rwas .data_mut() .get_mut(dst_offset..) .and_then(|memory| memory.get_mut(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; + .ok_or(RwasmError::MemoryOutOfBounds)?; let mut memory_section = vm.module.memory_section.as_slice(); if is_empty_data_segment { memory_section = &[]; @@ -866,7 +880,7 @@ pub(crate) fn visit_memory_init(vm: &mut RwasmExecutor) -> Result<(), Rwas let data = memory_section .get(src_offset..) .and_then(|data| data.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; + .ok_or(RwasmError::MemoryOutOfBounds)?; memory.copy_from_slice(data); if let Some(tracer) = vm.tracer.as_mut() { tracer.global_memory(dst_offset as u32, n as u32, memory); @@ -878,18 +892,18 @@ pub(crate) fn visit_memory_init(vm: &mut RwasmExecutor) -> Result<(), Rwas #[inline(always)] pub(crate) fn visit_data_drop(vm: &mut RwasmExecutor) { let data_segment_idx = match vm.ip.data() { - InstructionData::DataSegmentIdx(value) => *value, + OpcodeData::DataSegmentIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; - let data_segment = vm.resolve_data_or_create(data_segment_idx); - data_segment.drop_bytes(); + vm.empty_data_segments + .set(data_segment_idx.to_u32() as usize, true); vm.ip.add(1); } #[inline(always)] pub(crate) fn visit_table_size(vm: &mut RwasmExecutor) { let table_idx = match vm.ip.data() { - InstructionData::TableIdx(value) => *value, + OpcodeData::TableIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let table_size = vm @@ -903,9 +917,8 @@ pub(crate) fn visit_table_size(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_table_grow(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { - let mut limiter = ResourceLimiterRef::default(); let table_idx = match vm.ip.data() { - InstructionData::TableIdx(value) => *value, + OpcodeData::TableIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let (init, delta) = vm.sp.pop2(); @@ -913,15 +926,11 @@ pub(crate) fn visit_table_grow(vm: &mut RwasmExecutor) -> Result<(), Rwasm if vm.config.fuel_enabled { vm.try_consume_fuel(vm.fuel_costs.fuel_for_elements(delta as u64))?; } - let table = vm.resolve_table_or_create(table_idx); - let result = match table.grow_untyped(delta, init, &mut limiter) { - Ok(result) => result, - Err(EntityGrowError::TrapCode(trap_code)) => return Err(RwasmError::TrapCode(trap_code)), - Err(EntityGrowError::InvalidGrow) => u32::MAX, - }; + let table = vm.tables.entry(table_idx).or_insert_with(TableEntity::new); + let result = table.grow_untyped(delta, init); vm.sp.push_as(result); if let Some(tracer) = vm.tracer.as_mut() { - tracer.table_size_change(table_idx.to_u32(), init.as_u32(), delta); + tracer.table_size_change(table_idx.to_u32(), init.into(), delta); } vm.ip.add(1); Ok(()) @@ -930,15 +939,17 @@ pub(crate) fn visit_table_grow(vm: &mut RwasmExecutor) -> Result<(), Rwasm #[inline(always)] pub(crate) fn visit_table_fill(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let table_idx = match vm.ip.data() { - InstructionData::TableIdx(value) => *value, + OpcodeData::TableIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let (i, val, n) = vm.sp.pop3(); if vm.config.fuel_enabled { - vm.try_consume_fuel(vm.fuel_costs.fuel_for_elements(n.as_u64()))?; + vm.try_consume_fuel(vm.fuel_costs.fuel_for_elements(n.into()))?; } - vm.resolve_table(table_idx) - .fill_untyped(i.as_u32(), val, n.as_u32())?; + vm.tables + .get_mut(&table_idx) + .expect("rwasm: missing table") + .fill_untyped(i.into(), val, n.into())?; vm.ip.add(1); Ok(()) } @@ -946,14 +957,16 @@ pub(crate) fn visit_table_fill(vm: &mut RwasmExecutor) -> Result<(), Rwasm #[inline(always)] pub(crate) fn visit_table_get(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let table_idx = match vm.ip.data() { - InstructionData::TableIdx(value) => *value, + OpcodeData::TableIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let index = vm.sp.pop(); let value = vm - .resolve_table(table_idx) - .get_untyped(index.as_u32()) - .ok_or(TrapCode::TableOutOfBounds)?; + .tables + .get_mut(&table_idx) + .expect("rwasm: missing table") + .get_untyped(index.into()) + .ok_or(RwasmError::TableOutOfBounds)?; vm.sp.push(value); vm.ip.add(1); Ok(()) @@ -962,15 +975,17 @@ pub(crate) fn visit_table_get(vm: &mut RwasmExecutor) -> Result<(), RwasmE #[inline(always)] pub(crate) fn visit_table_set(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let table_idx = match vm.ip.data() { - InstructionData::TableIdx(value) => *value, + OpcodeData::TableIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let (index, value) = vm.sp.pop2(); - vm.resolve_table(table_idx) - .set_untyped(index.as_u32(), value) - .map_err(|_| TrapCode::TableOutOfBounds)?; + vm.tables + .get_mut(&table_idx) + .expect("rwasm: missing table") + .set_untyped(index.into(), value) + .map_err(|_| RwasmError::TableOutOfBounds)?; if let Some(tracer) = vm.tracer.as_mut() { - tracer.table_change(table_idx.to_u32(), index.as_u32(), value); + tracer.table_change(table_idx.to_u32(), index.into(), value); } vm.ip.add(1); Ok(()) @@ -979,7 +994,7 @@ pub(crate) fn visit_table_set(vm: &mut RwasmExecutor) -> Result<(), RwasmE #[inline(always)] pub(crate) fn visit_table_copy(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let dst_table_idx = match vm.ip.data() { - InstructionData::TableIdx(value) => *value, + OpcodeData::TableIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let src_table_idx = vm.fetch_table_index(1); @@ -988,7 +1003,7 @@ pub(crate) fn visit_table_copy(vm: &mut RwasmExecutor) -> Result<(), Rwasm let src_index = u32::from(s); let dst_index = u32::from(d); if vm.config.fuel_enabled { - vm.try_consume_fuel(vm.fuel_costs.fuel_for_elements(n.as_u64()))?; + vm.try_consume_fuel(vm.fuel_costs.fuel_for_elements(len as u64))?; } // Query both tables and check if they are the same: if src_table_idx != dst_table_idx { @@ -1011,10 +1026,11 @@ pub(crate) fn visit_table_copy(vm: &mut RwasmExecutor) -> Result<(), Rwasm #[inline(always)] pub(crate) fn visit_table_init(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let element_segment_idx = match vm.ip.data() { - InstructionData::ElementSegmentIdx(value) => *value, + OpcodeData::ElementSegmentIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; let table_idx = vm.fetch_table_index(1); + let (d, s, n) = vm.sp.pop3(); let len = u32::from(n); let src_index = u32::from(s); @@ -1033,15 +1049,20 @@ pub(crate) fn visit_table_init(vm: &mut RwasmExecutor) -> Result<(), Rwasm // to perform an emptiness check. // Therefore, in `element_segment_idx`, we store the original index, // which is always > 0. - let is_empty_segment = vm.resolve_element_or_create(element_segment_idx).is_empty(); + let is_empty_segment = vm + .empty_elements_segments + .get(element_segment_idx.to_u32() as usize) + .as_deref() + .copied() + .unwrap_or(false); - let (table, mut element) = - vm.resolve_table_with_element_or_create(table_idx, ElementSegmentIdx::from(0)); - let mut empty_element_segment = ElementSegmentEntity::empty(element.ty()); + let mut module_elements_section = &vm.default_elements_segment[..]; if is_empty_segment { - element = &mut empty_element_segment; + module_elements_section = &[]; } - table.init_untyped(dst_index, element, src_index, len)?; + let table = vm.tables.get_mut(&table_idx).expect("rwasm: missing table"); + table.init_untyped(dst_index, module_elements_section, src_index, len)?; + vm.ip.add(2); Ok(()) } @@ -1049,18 +1070,18 @@ pub(crate) fn visit_table_init(vm: &mut RwasmExecutor) -> Result<(), Rwasm #[inline(always)] pub(crate) fn visit_element_drop(vm: &mut RwasmExecutor) { let element_segment_idx = match vm.ip.data() { - InstructionData::ElementSegmentIdx(value) => *value, + OpcodeData::ElementSegmentIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; - let element_segment = vm.resolve_element_or_create(element_segment_idx); - element_segment.drop_items(); + vm.empty_elements_segments + .set(element_segment_idx.to_u32() as usize, true); vm.ip.add(1); } #[inline(always)] pub(crate) fn visit_ref_func(vm: &mut RwasmExecutor) { let func_idx = match vm.ip.data() { - InstructionData::FuncIdx(value) => *value, + OpcodeData::FuncIdx(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; vm.sp.push_as(func_idx.to_u32() + FUNC_REF_OFFSET); @@ -1070,13 +1091,24 @@ pub(crate) fn visit_ref_func(vm: &mut RwasmExecutor) { #[inline(always)] pub(crate) fn visit_i32_i64_const(vm: &mut RwasmExecutor) { let untyped_value = match vm.ip.data() { - InstructionData::UntypedValue(value) => *value, + OpcodeData::UntypedValue(value) => *value, _ => unreachable!("rwasm: missing instr data"), }; vm.sp.push(untyped_value); vm.ip.add(1); } +macro_rules! impl_visit_unary { + ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { + $( + #[inline(always)] + pub(crate) fn $visit_ident(exec: &mut RwasmExecutor) { + exec.execute_unary(UntypedValue::$untyped_ident) + } + )* + } +} + impl_visit_unary! { fn visit_i32_eqz(i32_eqz); fn visit_i64_eqz(i64_eqz); @@ -1136,6 +1168,18 @@ impl_visit_unary! { fn visit_i64_trunc_sat_f64_u(i64_trunc_sat_f64_u); } +macro_rules! impl_visit_fallible_unary { + ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { + $( + #[inline(always)] + pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { + vm.try_execute_unary(UntypedValue::$untyped_ident) + } + wrap_function_result!($visit_ident); + )* + } +} + impl_visit_fallible_unary! { fn visit_i32_trunc_f32_s(i32_trunc_f32_s); fn visit_i32_trunc_f32_u(i32_trunc_f32_u); @@ -1148,6 +1192,17 @@ impl_visit_fallible_unary! { fn visit_i64_trunc_f64_u(i64_trunc_f64_u); } +macro_rules! impl_visit_binary { + ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { + $( + #[inline(always)] + pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) { + vm.execute_binary(UntypedValue::$untyped_ident) + } + )* + } +} + impl_visit_binary! { fn visit_i32_eq(i32_eq); fn visit_i32_ne(i32_ne); @@ -1226,6 +1281,18 @@ impl_visit_binary! { fn visit_f64_copysign(f64_copysign); } +macro_rules! impl_visit_fallible_binary { + ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { + $( + #[inline(always)] + pub(crate) fn $visit_ident(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { + vm.try_execute_binary(UntypedValue::$untyped_ident) + } + wrap_function_result!($visit_ident); + )* + } +} + impl_visit_fallible_binary! { fn visit_i32_div_s(i32_div_s); fn visit_i32_div_u(i32_div_u); @@ -1241,7 +1308,7 @@ impl_visit_fallible_binary! { #[inline(always)] pub(crate) fn visit_stack_alloc(vm: &mut RwasmExecutor) -> Result<(), RwasmError> { let max_stack_height = match vm.ip.data() { - InstructionData::StackAlloc { max_stack_height } => *max_stack_height, + OpcodeData::StackAlloc(value) => value.max_stack_height, _ => unreachable!("rwasm: missing instr data"), }; vm.value_stack.reserve(max_stack_height as usize)?; diff --git a/src/vm/rw_op.rs b/src/vm/rw_op.rs new file mode 100644 index 000000000..60877b1e6 --- /dev/null +++ b/src/vm/rw_op.rs @@ -0,0 +1,360 @@ +use crate::Opcode; +use alloc::vec::Vec; + +#[derive(Debug, Copy, Clone)] +pub enum RwOp { + StackWrite(u32), + StackRead(u32), + GlobalWrite(u32), + GlobalRead(u32), + MemoryWrite { + offset: u32, + length: u32, + signed: bool, + }, + MemoryRead { + offset: u32, + length: u32, + signed: bool, + }, + MemorySizeWrite, + MemorySizeRead, + TableSizeRead(u32), + TableSizeWrite(u32), + TableElemRead(u32), + TableElemWrite(u32), + DataWrite(u32), + DataRead(u32), +} + +impl Opcode { + pub fn get_rw_count(&self) -> usize { + let mut rw_count = 0; + for rw_op in self.get_rw_ops() { + match rw_op { + RwOp::MemoryWrite { length, .. } => rw_count += length as usize, + RwOp::MemoryRead { length, .. } => rw_count += length as usize, + _ => rw_count += 1, + } + } + rw_count + } + + pub fn get_rw_ops(&self) -> Vec { + let mut stack_ops = Vec::new(); + match *self { + Opcode::LocalGet(local_depth) => { + stack_ops.push(RwOp::StackRead(local_depth.to_usize() as u32)); + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::LocalSet(local_depth) => { + stack_ops.push(RwOp::StackRead(0)); + // local depth can't be zero otherwise this op is useless + if local_depth.to_usize() > 0 { + stack_ops.push(RwOp::StackWrite(local_depth.to_usize() as u32 - 1)); + } else { + stack_ops.push(RwOp::StackWrite(0)); + } + } + Opcode::LocalTee(local_depth) => { + stack_ops.push(RwOp::StackRead(0)); + // local depth can't be zero otherwise this op is useless + if local_depth.to_usize() > 0 { + stack_ops.push(RwOp::StackWrite(local_depth.to_usize() as u32 - 1)); + } else { + stack_ops.push(RwOp::StackWrite(0)); + } + } + Opcode::Br(_) => {} + Opcode::BrIfEqz(_) | Opcode::BrIfNez(_) => { + stack_ops.push(RwOp::StackRead(0)); + } + Opcode::BrAdjust(_) => {} + Opcode::BrAdjustIfNez(_) | Opcode::BrTable(_) => { + stack_ops.push(RwOp::StackRead(0)); + } + Opcode::Unreachable | Opcode::ConsumeFuel(_) | Opcode::Return(_) => {} + Opcode::ReturnIfNez(_) => { + stack_ops.push(RwOp::StackRead(0)); + } + Opcode::ReturnCallInternal(_) | Opcode::ReturnCall(_) => {} + Opcode::ReturnCallIndirect(_) => { + stack_ops.push(RwOp::StackRead(0)); + } + Opcode::CallInternal(_) => {} + Opcode::Call(_) => {} + Opcode::CallIndirect(_) => { + stack_ops.push(RwOp::StackRead(0)); + } + Opcode::Drop => { + stack_ops.push(RwOp::StackRead(0)); + } + Opcode::Select => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::GlobalGet(val) => { + stack_ops.push(RwOp::GlobalRead(val.to_u32())); + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::GlobalSet(val) => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::GlobalWrite(val.to_u32())); + } + Opcode::I32Load(val) + | Opcode::I64Load(val) + | Opcode::F32Load(val) + | Opcode::F64Load(val) + | Opcode::I32Load8S(val) + | Opcode::I32Load8U(val) + | Opcode::I32Load16S(val) + | Opcode::I32Load16U(val) + | Opcode::I64Load8S(val) + | Opcode::I64Load8U(val) + | Opcode::I64Load16S(val) + | Opcode::I64Load16U(val) + | Opcode::I64Load32S(val) + | Opcode::I64Load32U(val) => { + let (_, commit_byte_len, signed) = Self::load_instr_meta(self); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::MemoryRead { + offset: val.into_inner(), + length: commit_byte_len as u32, + signed, + }); + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::I32Store(val) + | Opcode::I64Store(val) + | Opcode::F32Store(val) + | Opcode::F64Store(val) + | Opcode::I32Store8(val) + | Opcode::I32Store16(val) + | Opcode::I64Store8(val) + | Opcode::I64Store16(val) + | Opcode::I64Store32(val) => { + let length = Self::store_instr_meta(self); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::MemoryWrite { + offset: val.into_inner(), + length: length as u32, + signed: false, + }); + } + Opcode::MemorySize => { + stack_ops.push(RwOp::MemorySizeRead); + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::MemoryGrow => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackWrite(0)); + stack_ops.push(RwOp::MemorySizeWrite); + } + Opcode::MemoryFill | Opcode::MemoryCopy => { + // unreachable!("not implemented here") + } + Opcode::MemoryInit(_) => {} + Opcode::DataDrop(_) => {} + + Opcode::TableSize(table_idx) => { + stack_ops.push(RwOp::TableSizeRead(table_idx.to_u32())); + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::TableGrow(table_idx) => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::TableSizeWrite(table_idx.to_u32())); + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::TableFill(table_idx) => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::TableSizeRead(table_idx.to_u32())); + } + Opcode::TableGet(_) => { + panic!("custom function is used"); + } + Opcode::TableSet(table_idx) => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::TableElemWrite(table_idx.to_u32())); + stack_ops.push(RwOp::TableSizeRead(table_idx.to_u32())); + } + Opcode::TableCopy(_) => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + } + Opcode::TableInit(_) => {} + + Opcode::ElemDrop(_) => {} + Opcode::RefFunc(_) => { + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::ConstRef(_) => stack_ops.push(RwOp::StackWrite(0)), + + Opcode::I32Eqz + | Opcode::I32Eq + | Opcode::I64Eqz + | Opcode::I64Eq + | Opcode::I32Ne + | Opcode::I64Ne => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackWrite(0)); + } + Opcode::I32LtS + | Opcode::I32LtU + | Opcode::I32GtS + | Opcode::I32GtU + | Opcode::I32LeS + | Opcode::I32LeU + | Opcode::I32GeS + | Opcode::I32GeU + | Opcode::I64LtS + | Opcode::I64LtU + | Opcode::I64GtS + | Opcode::I64GtU + | Opcode::I64LeS + | Opcode::I64LeU + | Opcode::I64GeS + | Opcode::I64GeU + | Opcode::F32Eq + | Opcode::F32Lt + | Opcode::F32Gt + | Opcode::F32Le + | Opcode::F32Ge + | Opcode::F32Ne + | Opcode::F64Eq + | Opcode::F64Ne + | Opcode::F64Lt + | Opcode::F64Gt + | Opcode::F64Le + | Opcode::F64Ge => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackWrite(0)); + } + + Opcode::I32Clz + | Opcode::I64Clz + | Opcode::I32Ctz + | Opcode::I64Ctz + | Opcode::I32Popcnt + | Opcode::I64Popcnt => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackWrite(0)); + } + + Opcode::I32Add + | Opcode::I32Sub + | Opcode::I32Mul + | Opcode::I32DivS + | Opcode::I32DivU + | Opcode::I32RemS + | Opcode::I32RemU + | Opcode::I32And + | Opcode::I32Or + | Opcode::I32Xor + | Opcode::I32Shl + | Opcode::I32ShrS + | Opcode::I32ShrU + | Opcode::I32Rotl + | Opcode::I32Rotr + | Opcode::I64Add + | Opcode::I64Sub + | Opcode::I64Mul + | Opcode::I64DivS + | Opcode::I64DivU + | Opcode::I64RemS + | Opcode::I64RemU + | Opcode::I64And + | Opcode::I64Or + | Opcode::I64Xor + | Opcode::I64Shl + | Opcode::I64ShrS + | Opcode::I64ShrU + | Opcode::I64Rotl + | Opcode::I64Rotr + | Opcode::F32Add + | Opcode::F32Sub + | Opcode::F32Mul + | Opcode::F32Div + | Opcode::F32Min + | Opcode::F32Max + | Opcode::F32Copysign + | Opcode::F64Add + | Opcode::F64Sub + | Opcode::F64Mul + | Opcode::F64Div + | Opcode::F64Min + | Opcode::F64Max + | Opcode::F64Copysign => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackWrite(0)); + } + + Opcode::I32WrapI64 + | Opcode::I32TruncF32S + | Opcode::I32TruncF32U + | Opcode::I32TruncF64S + | Opcode::I32TruncF64U + | Opcode::I64ExtendI32S + | Opcode::I64ExtendI32U + | Opcode::I64TruncF32S + | Opcode::I64TruncF32U + | Opcode::I64TruncF64S + | Opcode::I64TruncF64U + | Opcode::F32ConvertI32S + | Opcode::F32ConvertI32U + | Opcode::F32ConvertI64S + | Opcode::F32ConvertI64U + | Opcode::F32DemoteF64 + | Opcode::F64ConvertI32S + | Opcode::F64ConvertI32U + | Opcode::F64ConvertI64S + | Opcode::F64ConvertI64U + | Opcode::F64PromoteF32 + | Opcode::I32Extend8S + | Opcode::I32Extend16S + | Opcode::I64Extend8S + | Opcode::I64Extend16S + | Opcode::I64Extend32S + | Opcode::I32TruncSatF32S + | Opcode::I32TruncSatF32U + | Opcode::I32TruncSatF64S + | Opcode::I32TruncSatF64U + | Opcode::I64TruncSatF32S + | Opcode::I64TruncSatF32U + | Opcode::I64TruncSatF64S + | Opcode::I64TruncSatF64U => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackWrite(0)); + } + + Opcode::F32Sqrt => { + stack_ops.push(RwOp::StackRead(0)); + stack_ops.push(RwOp::StackWrite(0)); + } + + _ => unreachable!("not supported rws for opcode: {:?}", self), + } + stack_ops + } + + pub fn get_stack_diff(&self) -> i32 { + let mut stack_diff = 0; + for rw_op in self.get_rw_ops() { + match rw_op { + RwOp::StackWrite(_) => stack_diff += 1, + RwOp::StackRead(_) => stack_diff -= 1, + _ => {} + } + } + stack_diff + } +} diff --git a/src/vm/table_entity.rs b/src/vm/table_entity.rs new file mode 100644 index 000000000..91d160bf3 --- /dev/null +++ b/src/vm/table_entity.rs @@ -0,0 +1,238 @@ +use crate::types::{RwasmError, UntypedValue, N_MAX_TABLE_SIZE}; + +/// A Wasm table entity. +#[derive(Debug)] +pub struct TableEntity { + pub(crate) elements: Vec, +} + +impl TableEntity { + /// Creates a new table entity with the given resizable limits. + /// + /// # Errors + /// + /// If `init` does not match the [`TableType`] element type. + pub fn new() -> Self { + let elements = Vec::with_capacity(N_MAX_TABLE_SIZE); + Self { elements } + } + + /// Returns the current size of the [`Table`]. + pub fn size(&self) -> u32 { + self.elements.len() as u32 + } + + /// Grows the table by the given number of elements. + /// + /// Returns the old size of the [`Table`] upon success. + /// + /// # Note + /// + /// This is an internal API that exists for efficiency purposes. + /// + /// The newly added elements are initialized to the `init` [`Value`]. + /// + /// # Errors + /// + /// If the table is grown beyond its maximum limits. + pub fn grow_untyped(&mut self, delta: u32, init: UntypedValue) -> u32 { + let current = self.size(); + let Some(desired) = current.checked_add(delta) else { + return u32::MAX; + }; + if desired as usize > self.elements.capacity() { + return u32::MAX; + } + self.elements.resize(desired as usize, init); + current + } + + /// Returns the untyped [`Table`] element value at `index`. + /// + /// Returns `None` if `index` is out of bounds. + /// + /// # Note + /// + /// This is a more efficient version of [`Table::get`] for + /// internal use only. + pub fn get_untyped(&self, index: u32) -> Option { + self.elements.get(index as usize).copied() + } + + /// Returns the [`UntypedValue`] of the [`Table`] at `index`. + /// + /// # Errors + /// + /// If `index` is out of bounds. + pub fn set_untyped(&mut self, index: u32, value: UntypedValue) -> Result<(), RwasmError> { + let untyped = self + .elements + .get_mut(index as usize) + .ok_or(RwasmError::TableOutOfBounds)?; + *untyped = value; + Ok(()) + } + + /// Initializes a segment of the `elements` table with values from a specified `element` array. + /// + /// # Parameters + /// + /// - `&mut self`: A mutable reference to the current instance, allowing modification of the + /// `elements` table. + /// - `dst_index`: The starting index in the `elements` table where the initialization will + /// begin. + /// - `element`: A slice of `UntypedValue` containing the values to copy into the `elements` + /// table. + /// - `src_index`: The starting index in the `element` slice from which to copy values. + /// - `len`: The number of values to copy from `element` to `elements`. + /// + /// # Returns + /// + /// - `Ok(())` if the segment was successfully initialized. + /// - `Err(RwasmError::TableOutOfBounds)` if the source or destination ranges are out of bounds. + /// + /// # Behavior + /// + /// 1. Convert `dst_index`, `src_index`, and `len` to `usize` for indexing. + /// 2. Performs bounds-checking for both the `elements` table and the `element` slice: + /// - Ensures enough elements are available starting from `dst_index` and `src_index` for the + /// specified `len`. + /// - If the bounds are exceeded, it returns `Err(RwasmError::TableOutOfBounds)`. + /// 3. If `len` is 0, the method returns early with `Ok(())` after performing the necessary + /// bounds check. + /// 4. Copies the specified `len` values from `element[src_index..]` into + /// `elements[dst_index..]`. + /// + /// # Notes + /// + /// - The bound check is always performed, even if `len` is 0, to adhere to WebAssembly + /// specifications. + /// - This function works with untyped values (`UntypedValue`), allowing generic initialization + /// of table segments. + pub fn init_untyped( + &mut self, + dst_index: u32, + element: &[UntypedValue], + src_index: u32, + len: u32, + ) -> Result<(), RwasmError> { + // Convert parameters to indices. + let dst_index = dst_index as usize; + let src_index = src_index as usize; + let len = len as usize; + // Perform bound check before anything else. + let dst_items = self + .elements + .get_mut(dst_index..) + .and_then(|items| items.get_mut(..len)) + .ok_or(RwasmError::TableOutOfBounds)?; + let src_items = element + .get(src_index..) + .and_then(|items| items.get(..len)) + .ok_or(RwasmError::TableOutOfBounds)?; + if len == 0 { + // Bail out early if nothing needs to be initialized. + // The Wasm spec demands to still perform the bound check + // so we cannot bail out earlier. + return Ok(()); + } + // Perform the actual table initialization. + dst_items.iter_mut().zip(src_items).for_each(|(dst, src)| { + *dst = *src; + }); + Ok(()) + } + + /// Copy `len` elements from `src_table[src_index..]` into + /// `dst_table[dst_index..]`. + /// + /// # Errors + /// + /// Returns an error if the range is out of bounds of either the source or + /// destination tables. + pub fn copy( + dst_table: &mut Self, + dst_index: u32, + src_table: &Self, + src_index: u32, + len: u32, + ) -> Result<(), RwasmError> { + // Turn parameters into proper slice indices. + let src_index = src_index as usize; + let dst_index = dst_index as usize; + let len = len as usize; + // Perform bound check before anything else. + let dst_items = dst_table + .elements + .get_mut(dst_index..) + .and_then(|items| items.get_mut(..len)) + .ok_or(RwasmError::TableOutOfBounds)?; + let src_items = src_table + .elements + .get(src_index..) + .and_then(|items| items.get(..len)) + .ok_or(RwasmError::TableOutOfBounds)?; + // Finally, copy elements in-place for the table. + dst_items.copy_from_slice(src_items); + Ok(()) + } + + /// Copy `len` elements from `self[src_index..]` into `self[dst_index..]`. + /// + /// # Errors + /// + /// Returns an error if the range is out of bounds of the table. + pub fn copy_within( + &mut self, + dst_index: u32, + src_index: u32, + len: u32, + ) -> Result<(), RwasmError> { + // These accesses just perform the bound checks required by the Wasm spec. + let max_offset = core::cmp::max(dst_index, src_index); + max_offset + .checked_add(len) + .filter(|&offset| offset <= self.size()) + .ok_or(RwasmError::TableOutOfBounds)?; + // Turn parameters into proper indices. + let src_index = src_index as usize; + let dst_index = dst_index as usize; + let len = len as usize; + // Finally, copy elements in-place for the table. + self.elements + .copy_within(src_index..src_index.wrapping_add(len), dst_index); + Ok(()) + } + + /// Fill `table[dst..(dst + len)]` with the given value. + /// + /// # Note + /// + /// This is an API for internal use only and exists for efficiency reasons. + /// + /// # Errors + /// + /// - If the region to be filled is out of bounds for the [`Table`]. + /// + /// # Panics + /// + /// If `ctx` does not own `dst_table` or `src_table`. + /// + /// [`Store`]: [`crate::Store`] + pub fn fill_untyped( + &mut self, + dst: u32, + val: UntypedValue, + len: u32, + ) -> Result<(), RwasmError> { + let dst_index = dst as usize; + let len = len as usize; + let dst = self + .elements + .get_mut(dst_index..) + .and_then(|elements| elements.get_mut(..len)) + .ok_or(RwasmError::TableOutOfBounds)?; + dst.fill(val); + Ok(()) + } +} diff --git a/src/vm/tracer.rs b/src/vm/tracer.rs new file mode 100644 index 000000000..aaf0ff0f9 --- /dev/null +++ b/src/vm/tracer.rs @@ -0,0 +1,186 @@ +use crate::types::{Opcode, OpcodeMeta, TableIdx, UntypedValue}; +use alloc::{collections::BTreeMap, string::String, vec::Vec}; +use core::{ + fmt::{Debug, Formatter}, + mem::take, +}; + +#[derive(Debug, Clone)] +pub struct TracerMemoryState { + pub offset: u32, + pub len: u32, + pub data: Vec, +} + +#[derive(Debug, Clone)] +pub struct TraceTableState { + pub table_idx: u32, + pub elem_idx: u32, + pub func_ref: UntypedValue, +} + +#[derive(Debug, Clone)] +pub struct TraceTableSizeState { + pub table_idx: u32, + pub init: u32, + pub delta: u32, +} + +#[derive(Debug, Clone)] +pub struct TracerInstrState { + pub program_counter: u32, + pub opcode: Opcode, + pub memory_changes: Vec, + pub table_changes: Vec, + pub table_size_changes: Vec, + pub stack: Vec, + pub next_table_idx: Option, + pub source_pc: u32, + pub code: u8, + pub memory_size: u32, + pub index: usize, + pub consumed_fuel: u64, + pub call_id: u32, +} + +#[derive(Default, Debug, Clone)] +pub struct TracerFunctionMeta { + pub fn_index: u32, + pub max_stack_height: u32, + pub num_locals: u32, + pub fn_name: String, +} + +#[derive(Default, Clone)] +pub struct TracerGlobalVariable { + pub index: u32, + pub value: u64, +} + +#[derive(Default, Clone)] +pub struct Tracer { + pub global_memory: Vec, + pub logs: Vec, + pub memory_changes: Vec, + pub table_changes: Vec, + pub table_size_changes: Vec, + pub fns_meta: Vec, + pub global_variables: Vec, + pub extern_names: BTreeMap, + pub nested_calls: u32, +} + +impl Debug for Tracer { + fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { + write!( + f, + "global_memory: {:?}; logs: {:?}; memory_changes: {:?}; fns_meta: {:?}", + self.global_memory, self.logs, self.memory_changes, self.fns_meta + ) + } +} + +impl Tracer { + pub fn merge_nested_call(&mut self, tracer: &Tracer) { + self.nested_calls += 1; + for mut log in tracer.logs.iter().cloned() { + log.call_id = self.nested_calls; + self.logs.push(log); + } + } + + pub fn global_memory(&mut self, offset: u32, len: u32, memory: &[u8]) { + self.global_memory.push(TracerMemoryState { + offset, + len, + data: Vec::from(memory), + }); + } + + pub fn get_last_pc(&self) -> Option { + self.logs.last().map(|opcode| opcode.source_pc) + } + + pub fn pre_opcode_state( + &mut self, + program_counter: u32, + opcode: Opcode, + stack: Vec, + meta: &OpcodeMeta, + memory_size: u32, + consumed_fuel: u64, + ) { + let memory_changes = take(&mut self.memory_changes); + let table_changes = take(&mut self.table_changes); + let table_size_changes = take(&mut self.table_size_changes); + let opcode_state = TracerInstrState { + program_counter, + opcode, + memory_changes, + table_changes, + table_size_changes, + stack, + next_table_idx: None, + source_pc: meta.pos as u32, + code: meta.opcode, + memory_size, + index: meta.index, + consumed_fuel, + call_id: 0, + }; + self.logs.push(opcode_state.clone()); + } + + pub fn remember_next_table(&mut self, table_idx: TableIdx) { + self.logs.last_mut().map(|v| { + v.next_table_idx = Some(table_idx); + }); + } + + pub fn function_call( + &mut self, + fn_index: u32, + max_stack_height: usize, + num_locals: usize, + fn_name: String, + ) { + let resolved_name = self.extern_names.get(&fn_index).unwrap_or(&fn_name); + self.fns_meta.push(TracerFunctionMeta { + fn_index, + max_stack_height: max_stack_height as u32, + num_locals: num_locals as u32, + fn_name: resolved_name.clone(), + }) + } + + pub fn global_variable(&mut self, value: UntypedValue, index: u32) { + self.global_variables.push(TracerGlobalVariable { + value: value.to_bits(), + index, + }) + } + + pub fn memory_change(&mut self, offset: u32, len: u32, memory: &[u8]) { + self.memory_changes.push(TracerMemoryState { + offset, + len, + data: Vec::from(memory), + }); + } + + pub fn table_change(&mut self, table_idx: u32, elem_idx: u32, func_ref: UntypedValue) { + self.table_changes.push(TraceTableState { + table_idx, + elem_idx, + func_ref, + }); + } + + pub fn table_size_change(&mut self, table_idx: u32, init: u32, delta: u32) { + self.table_size_changes.push(TraceTableSizeState { + table_idx, + init, + delta, + }); + } +} diff --git a/src/vm/value_stack.rs b/src/vm/value_stack.rs new file mode 100644 index 000000000..052894e78 --- /dev/null +++ b/src/vm/value_stack.rs @@ -0,0 +1,652 @@ +use crate::types::{ + DropKeep, + RwasmError, + UntypedValue, + DEFAULT_MAX_VALUE_STACK_HEIGHT, + DEFAULT_MIN_VALUE_STACK_HEIGHT, +}; +use core::fmt::Debug; + +/// The value stack used to execute Wasm bytecode. +/// +/// # Note +/// +/// The [`ValueStack`] implementation heavily relies on the prior +/// validation of the executed Wasm bytecode for correct execution. +#[derive(Clone)] +pub struct ValueStack { + /// All currently live stack entries. + entries: Vec, + /// Index of the first free place in the stack. + stack_ptr: usize, + /// The maximum value stack height. + /// + /// # Note + /// + /// Extending the value stack beyond this limit during execution + /// will cause a stack overflow trap. + maximum_len: usize, +} + +impl Debug for ValueStack { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_struct("ValueStack") + .field("stack_ptr", &self.stack_ptr) + .field("entries", &&self.entries[..self.stack_ptr]) + .finish() + } +} + +impl PartialEq for ValueStack { + fn eq(&self, other: &Self) -> bool { + self.stack_ptr == other.stack_ptr + && self.entries[..self.stack_ptr] == other.entries[..other.stack_ptr] + } +} + +impl Eq for ValueStack {} + +impl Default for ValueStack { + fn default() -> Self { + let register_len = size_of::(); + Self::new( + DEFAULT_MIN_VALUE_STACK_HEIGHT / register_len, + DEFAULT_MAX_VALUE_STACK_HEIGHT / register_len, + ) + } +} + +impl Extend for ValueStack { + fn extend(&mut self, iter: I) + where + I: IntoIterator, + { + for item in iter { + self.push(item) + } + } +} + +impl ValueStack { + /// Creates an empty [`ValueStack`] that does not allocate heap memor. + /// + /// # Note + /// + /// This is required for resumable functions in order to replace their + /// proper stack with a cheap dummy one. + pub fn empty() -> Self { + Self { + entries: Vec::new(), + stack_ptr: 0, + maximum_len: 0, + } + } + + /// Returns the current [`ValueStackPtr`] of `self`. + /// + /// The returned [`ValueStackPtr`] points to the top most value on the [`ValueStack`]. + #[inline] + pub fn stack_ptr(&mut self) -> ValueStackPtr { + self.base_ptr().into_add(self.stack_ptr) + } + + pub fn stack_len(&mut self, sp: ValueStackPtr) -> usize { + let base = self.base_ptr(); + sp.offset_from(base) as usize + } + + pub fn has_stack_overflowed(&mut self, sp: ValueStackPtr) -> bool { + self.stack_len(sp) > self.maximum_len + } + + pub fn dump_stack(&mut self, sp: ValueStackPtr) -> Vec { + let size = self.stack_len(sp); + self.entries[0..size.min(self.entries.len())].to_vec() + } + + /// Returns the base [`ValueStackPtr`] of `self`. + /// + /// The returned [`ValueStackPtr`] points to the first value on the [`ValueStack`]. + #[inline] + fn base_ptr(&mut self) -> ValueStackPtr { + ValueStackPtr::new(self.entries.as_mut_ptr(), self.entries.len()) + } + + /// Synchronizes [`ValueStack`] with the new [`ValueStackPtr`]. + #[inline] + pub fn sync_stack_ptr(&mut self, new_sp: ValueStackPtr) { + let offset = new_sp.offset_from(self.base_ptr()); + self.stack_ptr = offset as usize; + } + + /// Returns `true` if the [`ValueStack`] is empty. + pub fn is_empty(&self) -> bool { + self.entries.capacity() == 0 + } + + /// Creates a new empty [`ValueStack`]. + /// + /// # Panics + /// + /// - If the `initial_len` is zero. + /// - If the `initial_len` is greater than `maximum_len`. + pub fn new(initial_len: usize, maximum_len: usize) -> Self { + assert!( + initial_len > 0, + "cannot initialize the value stack with zero length", + ); + assert!( + initial_len <= maximum_len, + "initial value stack length is greater than maximum value stack length", + ); + let entries = vec![UntypedValue::default(); initial_len]; + Self { + entries, + stack_ptr: 0, + maximum_len, + } + } + + /// Returns the [`UntypedValue`] at the given `index`. + /// + /// # Note + /// + /// This is an optimized convenience method that only asserts + /// that the index is within bounds in `debug` mode. + /// + /// # Safety + /// + /// This is safe since all wasmi bytecode has been validated + /// during translation and therefore cannot result in out of + /// bounds accesses. + /// + /// # Panics (Debug) + /// + /// If the `index` is out of bounds. + #[inline] + fn get_release_unchecked_mut(&mut self, index: usize) -> &mut UntypedValue { + debug_assert!(index < self.capacity()); + // Safety: This is safe since all wasmi bytecode has been validated + // during translation and therefore cannot result in out of + // bounds accesses. + unsafe { self.entries.get_unchecked_mut(index) } + } + + /// Extends the value stack by the `additional` amount of zeros. + /// + /// # Errors + /// + /// If the value stack cannot fit `additional` stack values. + pub fn extend_zeros(&mut self, additional: usize) { + let cells = self + .entries + .get_mut(self.stack_ptr..) + .and_then(|slice| slice.get_mut(..additional)) + .unwrap_or_else(|| panic!("did not reserve enough value stack space")); + cells.fill(UntypedValue::default()); + self.stack_ptr += additional; + } + + /// Prepares the [`ValueStack`] for execution of the given Wasm function. + pub fn prepare_wasm_call( + &mut self, + max_stack_height: usize, + len_locals: usize, + ) -> Result<(), RwasmError> { + self.reserve(max_stack_height)?; + self.extend_zeros(len_locals); + Ok(()) + } + + /// Drops the last value on the [`ValueStack`]. + #[inline] + pub fn drop(&mut self, depth: usize) { + self.stack_ptr -= depth; + } + + /// Pushes the [`UntypedValue`] to the end of the [`ValueStack`]. + /// + /// # Note + /// + /// - This operation heavily relies on the prior validation of the executed WebAssembly bytecode + /// for correctness. + /// - Especially the stack-depth analysis during compilation with a manual stack extension + /// before function call prevents this procedure from panicking. + #[inline] + pub fn push(&mut self, entry: UntypedValue) { + *self.get_release_unchecked_mut(self.stack_ptr) = entry; + self.stack_ptr += 1; + } + + /// Returns the capacity of the [`ValueStack`]. + fn capacity(&self) -> usize { + self.entries.len() + } + + /// Returns the current length of the [`ValueStack`]. + fn len(&self) -> usize { + self.stack_ptr + } + + /// Reserves enough space for `additional` entries in the [`ValueStack`]. + /// + /// # Note + /// + /// This allows to efficiently operate on the [`ValueStack`] through + /// [`ValueStackPtr`] which requires external resource management. + /// + /// Before executing a function the interpreter calls this function + /// to guarantee that enough space on the [`ValueStack`] exists for + /// correct execution to occur. + /// For this to be working we need a stack-depth analysis during Wasm + /// compilation so that we are aware of all stack-depths for every + /// functions. + pub fn reserve(&mut self, additional: usize) -> Result<(), RwasmError> { + let new_len = self + .len() + .checked_add(additional) + .filter(|&new_len| new_len <= self.maximum_len) + .ok_or_else(|| RwasmError::StackOverflow)?; + if new_len > self.capacity() { + // Note: By extending the new length, we effectively double + // the current value stack length and add the additional flat amount + // on top. This avoids too many frequent reallocations. + self.entries + .extend(core::iter::repeat(UntypedValue::default()).take(new_len)); + } + Ok(()) + } + + /// Drains the remaining value stack. + /// + /// # Note + /// + /// This API is mostly used when writing results back to the + /// caller after function execution has finished. + #[inline] + pub fn drain(&mut self) -> &[UntypedValue] { + let len = self.stack_ptr; + self.stack_ptr = 0; + &self.entries[0..len] + } + + /// Returns an exclusive slice to the last `depth` entries in the value stack. + #[inline] + pub fn peek_as_slice_mut(&mut self, depth: usize) -> &mut [UntypedValue] { + let start = self.stack_ptr - depth; + let end = self.stack_ptr; + &mut self.entries[start..end] + } + + /// Clears the [`ValueStack`] entirely. + /// + /// # Note + /// + /// This is required since sometimes execution can halt in the middle of + /// function execution which leaves the [`ValueStack`] in an unspecified + /// state. Therefore the [`ValueStack`] is required to be reset before + /// function execution happens. + pub fn reset(&mut self) { + self.stack_ptr = 0; + } +} + +/// A pointer on the [`ValueStack`]. +/// +/// Allows for efficient mutable access to the values of the [`ValueStack`]. +/// +/// [`ValueStack`]: super::ValueStack +#[derive(Debug, Copy, Clone)] +// #[repr(transparent)] +pub struct ValueStackPtr { + src: *mut UntypedValue, + ptr: *mut UntypedValue, + // len: usize, +} + +impl From<*mut UntypedValue> for ValueStackPtr { + #[inline] + fn from(ptr: *mut UntypedValue) -> Self { + Self { + src: ptr, + ptr, + // len: usize::MAX, + } + } +} + +impl ValueStackPtr { + pub fn new(ptr: *mut UntypedValue, _len: usize) -> ValueStackPtr { + // Self { src: ptr, ptr, len } + Self { ptr, src: ptr } + } + + /// Calculates the distance between two [`ValueStackPtr] in units of [`UntypedValue`]. + #[inline] + pub fn offset_from(self, other: Self) -> isize { + // SAFETY: Within Wasm bytecode execution we are guaranteed by + // Wasm validation and `wasmi` codegen to never run out + // of valid bounds using this method. + unsafe { self.ptr.offset_from(other.ptr) } + } + + /// Returns the [`UntypedValue`] at the current stack pointer. + #[must_use] + #[inline] + fn get(self) -> UntypedValue { + // SAFETY: Within Wasm bytecode execution we are guaranteed by + // Wasm validation and `wasmi` codegen to never run out + // of valid bounds using this method. + unsafe { *self.ptr } + } + + /// Writes `value` to the cell pointed at by [`ValueStackPtr`]. + #[inline] + fn set(self, value: UntypedValue) { + // SAFETY: Within Wasm bytecode execution we are guaranteed by + // Wasm validation and `wasmi` codegen to never run out + // of valid bounds using this method. + *unsafe { &mut *self.ptr } = value; + } + + /// Returns a [`ValueStackPtr`] with a pointer value increased by `delta`. + /// + /// # Note + /// + /// The amount of `delta` is in number of bytes per [`UntypedValue`]. + #[must_use] + #[inline] + pub fn into_add(mut self, delta: usize) -> Self { + self.inc_by(delta); + self + } + + /// Returns a [`ValueStackPtr`] with a pointer value decreased by `delta`. + /// + /// # Note + /// + /// The amount of `delta` is in number of bytes per [`UntypedValue`]. + #[must_use] + #[inline] + pub fn into_sub(mut self, delta: usize) -> Self { + self.dec_by(delta); + self + } + + /// Returns the last [`UntypedValue`] on the [`ValueStack`]. + /// + /// # Note + /// + /// This has the same effect as [`ValueStackPtr::nth_back`]`(1)`. + /// + /// [`ValueStack`]: super::ValueStack + #[inline] + #[must_use] + pub fn last(self) -> UntypedValue { + self.nth_back(1) + } + + /// Peeks the entry at the given depth from the last entry. + /// + /// # Note + /// + /// Given a `depth` of 1 has the same effect as [`ValueStackPtr::last`]. + /// + /// A `depth` of 0 is invalid and undefined. + #[inline] + #[must_use] + pub fn nth_back(self, depth: usize) -> UntypedValue { + self.into_sub(depth).get() + } + + /// Writes `value` to the n-th [`UntypedValue`] from the back. + /// + /// # Note + /// + /// Given a `depth` of 1 has the same effect as mutating [`ValueStackPtr::last`]. + /// + /// A `depth` of 0 is invalid and undefined. + #[inline] + pub fn set_nth_back(self, depth: usize, value: UntypedValue) { + self.into_sub(depth).set(value) + } + + /// Bumps the [`ValueStackPtr`] of `self` by one. + #[inline] + fn inc_by(&mut self, delta: usize) { + // SAFETY: Within Wasm bytecode execution we are guaranteed by + // Wasm validation and `wasmi` codegen to never run out + // of valid bounds using this method. + self.ptr = unsafe { self.ptr.add(delta) }; + // let diff = self.ptr as isize - self.src as isize; + // if diff < 0 { + // unreachable!("STACK UNDERFLOW") + // } else if diff > self.len as isize { + // unreachable!("STACK OVERFLOW") + // } + } + + /// Decreases the [`ValueStackPtr`] of `self` by one. + #[inline] + fn dec_by(&mut self, delta: usize) { + // SAFETY: Within Wasm bytecode execution we are guaranteed by + // Wasm validation and `wasmi` codegen to never run out + // of valid bounds using this method. + self.ptr = unsafe { self.ptr.sub(delta) }; + if self.ptr < self.src { + unreachable!("STACK UNDERFLOW") + } + } + + /// Pushes the `T` to the end of the [`ValueStack`]. + /// + /// # Note + /// + /// - This operation heavily relies on the prior validation of the executed WebAssembly bytecode + /// for correctness. + /// - Especially the stack-depth analysis during compilation with a manual stack extension + /// before function call prevents this procedure from panicking. + /// + /// [`ValueStack`]: super::ValueStack + #[inline] + pub fn push_as(&mut self, value: T) + where + T: Into, + { + self.push(value.into()) + } + + /// Pushes the [`UntypedValue`] to the end of the [`ValueStack`]. + /// + /// # Note + /// + /// - This operation heavily relies on the prior validation of the executed WebAssembly bytecode + /// for correctness. + /// - Especially the stack-depth analysis during compilation with a manual stack extension + /// before function call prevents this procedure from panicking. + /// + /// [`ValueStack`]: super::ValueStack + #[inline] + pub fn push(&mut self, value: UntypedValue) { + self.set(value); + self.inc_by(1); + } + + /// Drops the last [`UntypedValue`] from the [`ValueStack`]. + /// + /// # Note + /// + /// This operation heavily relies on the prior validation of + /// the executed WebAssembly bytecode for correctness. + /// + /// [`ValueStack`]: super::ValueStack + #[inline] + pub fn drop(&mut self) { + self.dec_by(1); + } + + /// Pops the last [`UntypedValue`] from the [`ValueStack`] as `T`. + /// + /// [`ValueStack`]: super::ValueStack + #[inline] + pub fn pop_as(&mut self) -> T + where + T: From, + { + T::from(self.pop()) + } + + /// Pops the last [`UntypedValue`] from the [`ValueStack`]. + /// + /// # Note + /// + /// This operation heavily relies on the prior validation of + /// the executed WebAssembly bytecode for correctness. + /// + /// [`ValueStack`]: super::ValueStack + #[inline] + pub fn pop(&mut self) -> UntypedValue { + self.dec_by(1); + self.get() + } + + /// Pops the last pair of [`UntypedValue`] from the [`ValueStack`]. + /// + /// # Note + /// + /// This operation heavily relies on the prior validation of + /// the executed WebAssembly bytecode for correctness. + /// + /// [`ValueStack`]: super::ValueStack + #[inline] + pub fn pop2(&mut self) -> (UntypedValue, UntypedValue) { + let rhs = self.pop(); + let lhs = self.pop(); + (lhs, rhs) + } + + /// Pops the last triple of [`UntypedValue`] from the [`ValueStack`]. + /// + /// # Note + /// + /// This operation heavily relies on the prior validation of + /// the executed WebAssembly bytecode for correctness. + /// + /// [`ValueStack`]: super::ValueStack + #[inline] + pub fn pop3(&mut self) -> (UntypedValue, UntypedValue, UntypedValue) { + let (snd, trd) = self.pop2(); + let fst = self.pop(); + (fst, snd, trd) + } + + /// Evaluates the given closure `f` for the top most stack value. + #[inline] + pub fn eval_top(&mut self, f: F) + where + F: FnOnce(UntypedValue) -> UntypedValue, + { + let last = self.into_sub(1); + last.set(f(last.get())) + } + + /// Evaluates the given closure `f` for the 2 top most stack values. + #[inline] + pub fn eval_top2(&mut self, f: F) + where + F: FnOnce(UntypedValue, UntypedValue) -> UntypedValue, + { + let rhs = self.pop(); + let last = self.into_sub(1); + let lhs = last.get(); + last.set(f(lhs, rhs)); + } + + /// Evaluates the given closure `f` for the 3 top most stack values. + #[inline] + pub fn eval_top3(&mut self, f: F) + where + F: FnOnce(UntypedValue, UntypedValue, UntypedValue) -> UntypedValue, + { + let (e2, e3) = self.pop2(); + let last = self.into_sub(1); + let e1 = last.get(); + last.set(f(e1, e2, e3)); + } + + /// Evaluates the given fallible closure `f` for the top most stack value. + /// + /// # Errors + /// + /// If the closure execution fails. + #[inline] + pub fn try_eval_top(&mut self, f: F) -> Result<(), RwasmError> + where + F: FnOnce(UntypedValue) -> Result, + { + let last = self.into_sub(1); + last.set(f(last.get())?); + Ok(()) + } + + /// Evaluates the given fallible closure `f` for the 2 top most stack values. + /// + /// # Errors + /// + /// If the closure execution fails. + #[inline] + pub fn try_eval_top2(&mut self, f: F) -> Result<(), RwasmError> + where + F: FnOnce(UntypedValue, UntypedValue) -> Result, + { + let rhs = self.pop(); + let last = self.into_sub(1); + let lhs = last.get(); + last.set(f(lhs, rhs)?); + Ok(()) + } + + /// Drops some amount of entries and keeps some amount of them at the new top. + /// + /// # Note + /// + /// For an amount of entries to keep `k` and an amount of entries to drop `d` + /// this has the following effect on stack `s` and stack pointer `sp`. + /// + /// 1) Copy `k` elements from indices starting at `sp - k` to `sp - k - d`. + /// 2) Adjust stack pointer: `sp -= d` + /// + /// After this operation the value stack will have `d` fewer entries and the + /// top `k` entries are the top `k` entries before this operation. + /// + /// Note that `k + d` cannot be greater than the stack length. + pub fn drop_keep(&mut self, drop_keep: DropKeep) { + fn drop_keep_impl(this: ValueStackPtr, drop_keep: DropKeep) { + let keep = drop_keep.keep; + if keep == 0 { + // Case: no values need to be kept. + return; + } + let keep = keep as usize; + let src = this.into_sub(keep); + let dst = this.into_sub(keep + drop_keep.drop as usize); + if keep == 1 { + // Case: only one value needs to be kept. + dst.set(src.get()); + return; + } + // Case: many values need to be kept and moved on the stack. + for i in 0..keep { + dst.into_add(i).set(src.into_add(i).get()); + } + } + + let drop = drop_keep.drop; + if drop == 0 { + // Nothing to do in this case. + return; + } + drop_keep_impl(*self, drop_keep); + self.dec_by(drop as usize); + } +} From 3fa8734f2077d89a92c1e1f6dcafe2fdd27d7a8b Mon Sep 17 00:00:00 2001 From: Dmitry Savonin Date: Thu, 8 May 2025 16:08:51 +0400 Subject: [PATCH 002/141] fix: re-export legacy crate --- Cargo.lock | 81 +++++++++++++++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 2 +- src/lib.rs | 4 +++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 3f23a1286..5c2538be5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -52,6 +52,12 @@ dependencies = [ "wyz", ] +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "1.10.1" @@ -70,6 +76,12 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + [[package]] name = "downcast-rs" version = "2.0.1" @@ -118,6 +130,12 @@ dependencies = [ "rustc-std-workspace-alloc", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "indexmap" version = "2.9.0" @@ -140,6 +158,12 @@ version = "0.2.172" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d750af042f7ef4f724306de029d18836c26c1765a54a6a3f094cbd23a7267ffa" +[[package]] +name = "libm" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de" + [[package]] name = "memchr" version = "2.7.2" @@ -265,6 +289,12 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f9d441c3b2ebf55cebf796bfdc265d67fa09db17b7bb6bd4be75c509e1e8fec3" +[[package]] +name = "rustversion" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eded382c5f5f786b989652c49544c4877d9f015cc22e145a5ea8ea66c2921cd2" + [[package]] name = "rwasm" version = "0.0.0" @@ -272,16 +302,34 @@ dependencies = [ "bincode", "bitvec", "bytes", - "downcast-rs", + "downcast-rs 2.0.1", "hashbrown", "num-traits", "num_enum", "paste", "rand", + "rwasm-legacy", "tiny-keccak", "wasmparser-nostd", ] +[[package]] +name = "rwasm-legacy" +version = "0.30.0" +dependencies = [ + "byteorder", + "downcast-rs 1.2.1", + "hashbrown", + "libm", + "num-traits", + "paste", + "smallvec", + "spin", + "strum", + "strum_macros", + "wasmparser-nostd", +] + [[package]] name = "serde" version = "1.0.210" @@ -302,6 +350,37 @@ dependencies = [ "syn", ] +[[package]] +name = "smallvec" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9" + +[[package]] +name = "spin" +version = "0.9.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" + +[[package]] +name = "strum" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f64def088c51c9510a8579e3c5d67c65349dcf755e5479ad3d010aa6454e2c32" + +[[package]] +name = "strum_macros" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c77a8c5abcaf0f9ce05d62342b7d298c346515365c36b673df4ebe3ced01fde8" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn", +] + [[package]] name = "syn" version = "2.0.60" diff --git a/Cargo.toml b/Cargo.toml index bdaf7ca0c..680f2ea6e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -23,7 +23,7 @@ num-traits = { version = "0.2", default-features = false } bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } # an rwasm legacy dependency -#rwasm-legacy = { path = "./legacy" } +rwasm-legacy = { path = "./legacy" } [dev-dependencies] rand = "0.9.1" diff --git a/src/lib.rs b/src/lib.rs index 668ffd324..888fc62f3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -10,3 +10,7 @@ extern crate core; pub use types::*; pub use vm::*; + +pub mod legacy { + pub use rwasm_legacy::*; +} From c4560aa5edd12793fbec5d97bab5a89d3eda00e9 Mon Sep 17 00:00:00 2001 From: Dmitry Savonin Date: Thu, 8 May 2025 16:54:04 +0400 Subject: [PATCH 003/141] fix: fix no_std compilation issues --- Cargo.lock | 1 + Cargo.toml | 6 ++++-- src/types/error.rs | 1 + src/types/import_linker.rs | 3 +-- src/types/import_name.rs | 2 ++ src/types/instruction.rs | 6 +++--- src/types/instruction_set.rs | 2 +- src/types/module.rs | 1 + src/types/untyped_value.rs | 2 +- src/vm/context.rs | 1 + src/vm/executor.rs | 2 +- src/vm/handler.rs | 1 + src/vm/table_entity.rs | 1 + src/vm/value_stack.rs | 1 + 14 files changed, 20 insertions(+), 10 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5c2538be5..aad49b3e2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -304,6 +304,7 @@ dependencies = [ "bytes", "downcast-rs 2.0.1", "hashbrown", + "libm", "num-traits", "num_enum", "paste", diff --git a/Cargo.toml b/Cargo.toml index 680f2ea6e..bb9c722ae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,15 +15,16 @@ wasmparser = { version = "0.100.1", package = "wasmparser-nostd", default-featur hashbrown = { version = "0.15.2", features = ["alloc"] } tiny-keccak = { version = "2.0.2", features = ["keccak"] } paste = { version = "1.0", default-features = false } -num_enum = { version = "0.7.3" } +num_enum = { version = "0.7.3", default-features = false } bytes = { version = "1.10.1", default-features = false } downcast-rs = { version = "2.0.1", default-features = false, features = ["sync"] } bincode = { version = "2.0.1", default-features = false, features = ["alloc", "derive"] } num-traits = { version = "0.2", default-features = false } bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } +libm = "0.2.1" # an rwasm legacy dependency -rwasm-legacy = { path = "./legacy" } +rwasm-legacy = { path = "./legacy", default-features = false } [dev-dependencies] rand = "0.9.1" @@ -36,5 +37,6 @@ std = [ "bincode/std", "num-traits/std", "bitvec/std", + "rwasm-legacy/std", ] debug-print = [] diff --git a/src/types/error.rs b/src/types/error.rs index 2055d1585..c3b97b755 100644 --- a/src/types/error.rs +++ b/src/types/error.rs @@ -1,4 +1,5 @@ use crate::types::HostError; +use alloc::boxed::Box; use core::fmt::Formatter; #[derive(Debug)] diff --git a/src/types/import_linker.rs b/src/types/import_linker.rs index 052b7658b..5f27c3cb4 100644 --- a/src/types/import_linker.rs +++ b/src/types/import_linker.rs @@ -1,7 +1,6 @@ use crate::{ImportName, ValueType}; -use core::ops::Deref; +use core::ops::{Deref, DerefMut}; use hashbrown::HashMap; -use std::ops::DerefMut; #[derive(Debug, Default, Clone)] pub struct ImportLinker { diff --git a/src/types/import_name.rs b/src/types/import_name.rs index 516d3af06..2323c7460 100644 --- a/src/types/import_name.rs +++ b/src/types/import_name.rs @@ -1,3 +1,5 @@ +use alloc::boxed::Box; + /// The name or namespace of an imported item. #[derive(Debug, Clone, PartialOrd, PartialEq, Ord, Eq, Hash)] pub struct ImportName { diff --git a/src/types/instruction.rs b/src/types/instruction.rs index 4f8efa823..93d71aa32 100644 --- a/src/types/instruction.rs +++ b/src/types/instruction.rs @@ -17,7 +17,8 @@ use crate::{ }, StackAlloc, }; -use core::fmt::Formatter; +use alloc::{format, vec::Vec}; +use core::fmt::{Display, Formatter}; #[derive( Debug, @@ -234,8 +235,7 @@ pub enum Opcode { StackAlloc = 0xc6, } -#[cfg(feature = "std")] -impl core::fmt::Display for Opcode { +impl Display for Opcode { fn fmt(&self, f: &mut Formatter<'_>) -> core::fmt::Result { let name = format!("{:?}", self); let name: Vec<_> = name.split('(').collect(); diff --git a/src/types/instruction_set.rs b/src/types/instruction_set.rs index 384836e5d..b474aa2f1 100644 --- a/src/types/instruction_set.rs +++ b/src/types/instruction_set.rs @@ -17,7 +17,7 @@ use crate::types::{ TableIdx, UntypedValue, }; -use alloc::vec::Vec; +use alloc::{vec, vec::Vec}; use bincode::{ de::Decoder, enc::Encoder, diff --git a/src/types/module.rs b/src/types/module.rs index 4d5278051..b16675930 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -1,4 +1,5 @@ use crate::types::InstructionSet; +use alloc::{vec, vec::Vec}; use bincode::{ de::Decoder, enc::Encoder, diff --git a/src/types/untyped_value.rs b/src/types/untyped_value.rs index 92c3d3f91..46892bcda 100644 --- a/src/types/untyped_value.rs +++ b/src/types/untyped_value.rs @@ -16,7 +16,7 @@ use crate::{ F32, F64, }; -use alloc::vec::Vec; +use alloc::{format, vec::Vec}; use bincode::{Decode, Encode}; use core::{ fmt::{self, Display, Formatter}, diff --git a/src/vm/context.rs b/src/vm/context.rs index 91d15aa1b..347eb1fca 100644 --- a/src/vm/context.rs +++ b/src/vm/context.rs @@ -2,6 +2,7 @@ use crate::{ types::{RwasmError, UntypedValue}, vm::executor::RwasmExecutor, }; +use alloc::{vec, vec::Vec}; pub struct Caller<'a, T> { vm: &'a mut RwasmExecutor, diff --git a/src/vm/executor.rs b/src/vm/executor.rs index fb04edd04..229d3edbc 100644 --- a/src/vm/executor.rs +++ b/src/vm/executor.rs @@ -29,7 +29,7 @@ use crate::{ value_stack::{ValueStack, ValueStackPtr}, }, }; -use alloc::sync::Arc; +use alloc::{sync::Arc, vec, vec::Vec}; use bitvec::{bitvec, prelude::BitVec}; use hashbrown::HashMap; diff --git a/src/vm/handler.rs b/src/vm/handler.rs index 225a591ad..997ab4530 100644 --- a/src/vm/handler.rs +++ b/src/vm/handler.rs @@ -2,6 +2,7 @@ use crate::{ types::{RwasmError, UntypedValue}, vm::context::Caller, }; +use alloc::{vec, vec::Vec}; pub type SyscallHandler = fn(Caller, u32) -> Result<(), RwasmError>; diff --git a/src/vm/table_entity.rs b/src/vm/table_entity.rs index 91d160bf3..52f74a678 100644 --- a/src/vm/table_entity.rs +++ b/src/vm/table_entity.rs @@ -1,4 +1,5 @@ use crate::types::{RwasmError, UntypedValue, N_MAX_TABLE_SIZE}; +use alloc::vec::Vec; /// A Wasm table entity. #[derive(Debug)] diff --git a/src/vm/value_stack.rs b/src/vm/value_stack.rs index 052894e78..8c4ca6185 100644 --- a/src/vm/value_stack.rs +++ b/src/vm/value_stack.rs @@ -5,6 +5,7 @@ use crate::types::{ DEFAULT_MAX_VALUE_STACK_HEIGHT, DEFAULT_MIN_VALUE_STACK_HEIGHT, }; +use alloc::{vec, vec::Vec}; use core::fmt::Debug; /// The value stack used to execute Wasm bytecode. From 7a3a37f8a947737d8c9445f880521557457f15b8 Mon Sep 17 00:00:00 2001 From: easy Date: Mon, 12 May 2025 23:29:49 +1000 Subject: [PATCH 004/141] fix: fix e2e test for 32 bit mode --- e2e/src/context.rs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/e2e/src/context.rs b/e2e/src/context.rs index 8ef4a44f6..be81cc70a 100644 --- a/e2e/src/context.rs +++ b/e2e/src/context.rs @@ -28,6 +28,7 @@ use rwasm_legacy::{ module::ImportName, }; use std::{cell::RefCell, collections::HashMap, hash::Hash, rc::Rc, sync::Arc}; +use rwasm_legacy::engine::RwasmConfig; use wast::token::{Id, Span}; type TestingRwasmExecutor = RwasmExecutor; @@ -205,6 +206,18 @@ impl TestContext<'_> { // extract all exports first to calculate rwasm config let rwasm_config = { + if ENABLE_32_BIT_TRANSLATOR { + config.rwasm_config(RwasmConfig{ + state_router: None, + entrypoint_name: None, + import_linker: None, + wrap_import_functions: false, + translate_drop_keep: false, + allow_malformed_entrypoint_func_type: false, + use_32bit_mode: true, + builtins_consume_fuel: false, + }); + } let engine = rwasm_legacy::Engine::new(&config); let wasm_module = rwasm_legacy::Module::new(&engine, &wasm[..])?; let mut states = Vec::<(String, u32)>::new(); From b3fb5b5222bc846724b882a3b8fbc25470fb7c4b Mon Sep 17 00:00:00 2001 From: Stanislav <105381376+bfdays@users.noreply.github.com> Date: Tue, 13 May 2025 18:55:39 +0700 Subject: [PATCH 005/141] feat(svm): increas initial global memory 2 times (#11) Co-authored-by: Stas Stepanov --- src/vm/memory.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/vm/memory.rs b/src/vm/memory.rs index 783b253c2..dcdc8b0ad 100644 --- a/src/vm/memory.rs +++ b/src/vm/memory.rs @@ -6,7 +6,7 @@ pub struct GlobalMemory { pub current_pages: Pages, } -const MEMORY_MAX_PAGES: Pages = Pages::new_unchecked(N_MAX_MEMORY_PAGES); +const MEMORY_MAX_PAGES: Pages = Pages::new_unchecked(N_MAX_MEMORY_PAGES * 2); impl GlobalMemory { pub fn new(initial_pages: Pages) -> Self { From fb33c350dead7f6ce1601088365efaa58d03d9f5 Mon Sep 17 00:00:00 2001 From: Stanislav <105381376+bfdays@users.noreply.github.com> Date: Tue, 13 May 2025 19:54:17 +0700 Subject: [PATCH 006/141] Feat(svm): put increased N_MAX_MEMORY_PAGES under the feature (#12) * feat(svm): increas initial global memory 2 times * feat(svm): put N_MAX_MEMORY_PAGES under the feature * feat(svm): added comment --------- Co-authored-by: Stas Stepanov --- Cargo.toml | 1 + src/types/mod.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index bb9c722ae..b09faa16b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,4 +39,5 @@ std = [ "bitvec/std", "rwasm-legacy/std", ] +more-max-pages = [] debug-print = [] diff --git a/src/types/mod.rs b/src/types/mod.rs index 770334292..9dcac6517 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -39,7 +39,11 @@ pub const N_BYTES_PER_MEMORY_PAGE: u32 = 65536; /// We have a hard limit for max possible memory used /// that is equal to ~64mB +#[cfg(not(feature = "more-max-pages"))] pub const N_MAX_MEMORY_PAGES: u32 = 1024; +/// Increased value needed for SVM for now +#[cfg(feature = "more-max-pages")] +pub const N_MAX_MEMORY_PAGES: u32 = 2048; /// To optimize a proving process, we have to limit the max /// number of pages, tables, etc. We found 1024 is enough. From 3f47f741efa93de71db452594c8f00dd800c0872 Mon Sep 17 00:00:00 2001 From: Dmitry Savonin Date: Wed, 14 May 2025 15:52:41 +0400 Subject: [PATCH 007/141] fix: enable tracer without metadata --- src/vm/opcodes.rs | 54 ++++++++++++++++------------------------------- 1 file changed, 18 insertions(+), 36 deletions(-) diff --git a/src/vm/opcodes.rs b/src/vm/opcodes.rs index 86d4c7c4d..1ccde4f3b 100644 --- a/src/vm/opcodes.rs +++ b/src/vm/opcodes.rs @@ -15,6 +15,7 @@ use crate::{ instr_ptr::InstructionPtr, table_entity::TableEntity, }, + OpcodeMeta, }; use core::cmp; @@ -46,6 +47,23 @@ pub(crate) fn run_the_loop(vm: &mut RwasmExecutor) -> Result>() ); } + if vm.tracer.is_some() { + let memory_size: u32 = vm.global_memory.current_pages().into(); + let consumed_fuel = vm.fuel_consumed(); + let stack = vm.value_stack.dump_stack(vm.sp); + vm.tracer.as_mut().unwrap().pre_opcode_state( + vm.ip.pc(), + instr, + stack, + &OpcodeMeta { + index: 0, + pos: 0, + opcode: 0, + }, + memory_size, + consumed_fuel, + ); + } use Opcode::*; match instr { Unreachable => visit_unreachable_wrapped(vm), @@ -290,42 +308,6 @@ wrap_function_result!(visit_table_copy); wrap_function_result!(visit_table_init); wrap_function_result!(visit_stack_alloc); -// #[cfg(feature = "debug-print")] -// { -// let stack = self -// .store -// .value_stack -// .dump_stack(exec.sp) -// .iter() -// .rev() -// .take(10) -// .map(|v| v.as_u64()) -// .collect::>(); -// println!( -// "{:02}: {:?}, stack={:?} ({})", -// exec.ip.pc(), -// instr, -// stack, -// exec.value_stack.stack_len(exec.sp) -// ); -// } -// -// #[cfg(feature = "tracer")] -// if exec.tracer.is_some() { -// use rwasm::engine::bytecode::InstrMeta; -// let memory_size: u32 = exec.global_memory.current_pages().into(); -// let consumed_fuel = exec.fuel_consumed(); -// let stack = exec.value_stack.dump_stack(exec.sp); -// exec.tracer.as_mut().unwrap().pre_opcode_state( -// exec.ip.pc(), -// instr, -// stack, -// &InstrMeta::new(0, 0, 0), -// memory_size, -// consumed_fuel, -// ); -// } - #[inline(always)] pub(crate) fn visit_unreachable(_vm: &mut RwasmExecutor) -> Result<(), RwasmError> { Err(RwasmError::UnreachableCodeReached) From 523708aee52d2cfbb75b0694980d792eceb21736 Mon Sep 17 00:00:00 2001 From: Dmitry Savonin Date: Wed, 14 May 2025 23:59:40 +0400 Subject: [PATCH 008/141] feat: add snippets for i64 ops for add/sub/div/mul --- snippets/Cargo.toml | 24 ++ snippets/Makefile | 5 + snippets/extractor.rs | 44 ++++ snippets/lib.rs | 329 ++++++++++++++++++++++++ snippets/lib.wasm | Bin 0 -> 6972 bytes snippets/lib.wat | 582 ++++++++++++++++++++++++++++++++++++++++++ snippets/test.rs | 63 +++++ 7 files changed, 1047 insertions(+) create mode 100644 snippets/Cargo.toml create mode 100644 snippets/Makefile create mode 100644 snippets/extractor.rs create mode 100644 snippets/lib.rs create mode 100755 snippets/lib.wasm create mode 100644 snippets/lib.wat create mode 100644 snippets/test.rs diff --git a/snippets/Cargo.toml b/snippets/Cargo.toml new file mode 100644 index 000000000..1fc329c70 --- /dev/null +++ b/snippets/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "snippets" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] +path = "lib.rs" + +[dev-dependencies] +rand = "0.9.1" + +[profile.release] +panic = "abort" +lto = true +opt-level = 3 +strip = false +debug = false +debug-assertions = false +rpath = false +codegen-units = 1 + +[dependencies] +walrus = "0.23.3" diff --git a/snippets/Makefile b/snippets/Makefile new file mode 100644 index 000000000..79931995e --- /dev/null +++ b/snippets/Makefile @@ -0,0 +1,5 @@ +.PHONY: lib.wasm +lib.wasm: Cargo.toml lib.rs + cargo b --release --target=wasm32-unknown-unknown --no-default-features + cp ./target/wasm32-unknown-unknown/release/snippets.wasm lib.wasm + wasm2wat lib.wasm > lib.wat \ No newline at end of file diff --git a/snippets/extractor.rs b/snippets/extractor.rs new file mode 100644 index 000000000..16e683d0f --- /dev/null +++ b/snippets/extractor.rs @@ -0,0 +1,44 @@ +use walrus::ir::{BinaryOp, Binop, Const, Instr, InstrSeq, UnaryOp, Unop, Value}; + +fn extract_wasm_snippet(wasm_binary: &[u8]) { + let module = walrus::Module::from_buffer(wasm_binary).unwrap(); + for func in module.funcs.iter() { + let Some(func_name) = func.name.clone() else { + continue; + }; + let kind = func.kind.unwrap_local(); + assert_eq!(kind.args.len(), 4); + let block = kind.block(kind.entry_block()); + println!("func: {}", func_name); + test_ending_opcodes(&block); + println!() + } +} + +#[rustfmt::skip] +fn test_ending_opcodes(seq: &InstrSeq) { + let fake_block = seq + .iter() + .rev() + .take(6) + .map(|v| v.0.clone()) + .rev() + .collect::>(); + for instr in &fake_block { + println!(" - {:?}", instr); + } + assert_eq!(fake_block.len(), 6); + // make sure the fake block we injected matches a>>32|b + assert!(matches!(fake_block[0],Instr::Unop(Unop {op: UnaryOp::I64ExtendUI32}))); + assert!(matches!(fake_block[1], Instr::Const(Const{value: Value::I64(32)}))); + assert!(matches!(fake_block[2],Instr::Binop(Binop {op: BinaryOp::I64Shl}))); + assert!(matches!(fake_block[3],Instr::LocalGet(..))); + assert!(matches!(fake_block[4],Instr::Unop(Unop {op: UnaryOp::I64ExtendUI32}))); + assert!(matches!(fake_block[5],Instr::Binop(Binop {op: BinaryOp::I64Or}))); +} + +#[test] +fn test_extract_i64_add() { + let wasm_binary = include_bytes!("./lib.wasm"); + extract_wasm_snippet(wasm_binary); +} diff --git a/snippets/lib.rs b/snippets/lib.rs new file mode 100644 index 000000000..a68f23c30 --- /dev/null +++ b/snippets/lib.rs @@ -0,0 +1,329 @@ +#![allow(clippy::needless_range_loop)] + +mod extractor; +#[cfg(test)] +mod test; + +/// Stack layout (little-endian limbs) +/// before: …, a_lo, a_hi, b_lo, b_hi +/// after: …, p_lo, p_hi // 64-bit product a · b (mod 2⁶⁴) +#[inline(always)] +pub(crate) fn karatsuba_mul64_impl(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> (u32, u32) { + #[inline(always)] + fn add32(a: u32, b: u32) -> (u32, u32) { + let (sum, carry) = a.overflowing_add(b); + (sum, carry as u32) + } + + #[inline(always)] + fn add64(lo: &mut u32, hi: &mut u32, add_lo: u32, add_hi: u32) { + let (s, c1) = (*lo).overflowing_add(add_lo); + let (h, _) = (*hi).overflowing_add(add_hi + c1 as u32); + *lo = s; + *hi = h; + } + + #[inline(always)] + fn sub64(lo: &mut u32, hi: &mut u32, sub_lo: u32, sub_hi: u32) { + let (s, b1) = (*lo).overflowing_sub(sub_lo); + let (h, _) = (*hi).overflowing_sub(sub_hi + b1 as u32); + *lo = s; + *hi = h; + } + + /// 32 × 32 → 64 without leaving `u32` + #[inline(always)] + fn mul32(x: u32, y: u32) -> (u32, u32) { + let x0 = x & 0xFFFF; + let x1 = x >> 16; + let y0 = y & 0xFFFF; + let y1 = y >> 16; + + let t = x0 * y0; // 16×16 => ≤32 bits + let s1 = x0 * y1; + let s2 = x1 * y0; + let v = x1 * y1; + + // cross = s1 + s2 (up to 33 bits) + let (cross_lo, carry_cross) = add32(s1, s2); + + // low = t + ((cross & 0xFFFF) << 16) + let (low, carry_low) = add32(t, (cross_lo & 0xFFFF) << 16); + + // high = v + (cross >> 16) + carry_low + let cross_hi = (cross_lo >> 16) + (carry_cross << 16); + let (tmp, carry_hi1) = add32(v, cross_hi); + let high = tmp + carry_low + carry_hi1; // cannot overflow 32 bits + + (low, high) + } + + // ---- Karatsuba partial products -------------------------------------- + let (z0_lo, z0_hi) = mul32(a_lo, b_lo); + let (z2_lo, z2_hi) = mul32(a_hi, b_hi); + + // sums (33-bit each) + let (sa_lo, ca) = add32(a_lo, a_hi); // sa = sa_hi · 2³² + sa_lo, sa_hi = ca + let (sb_lo, cb) = add32(b_lo, b_hi); + + // z1 = (sa * sb) − z0 − z2 (low 64 bits only) + let (mut z1_lo, mut z1_hi) = mul32(sa_lo, sb_lo); + if ca != 0 { + add64(&mut z1_lo, &mut z1_hi, 0, sb_lo); // + sb_lo << 32 + } + if cb != 0 { + add64(&mut z1_lo, &mut z1_hi, 0, sa_lo); // + sa_lo << 32 + } + sub64(&mut z1_lo, &mut z1_hi, z0_lo, z0_hi); + sub64(&mut z1_lo, &mut z1_hi, z2_lo, z2_hi); + + // ---- assemble low-64-bit result -------------------------------------- + // p = z0 + (z1 << 32) (z2 << 64 drops in mod-2⁶⁴ arithmetic) + let mut res_lo = z0_lo; + let mut res_hi = z0_hi; + add64(&mut res_lo, &mut res_hi, 0, z1_lo); // add z1 << 32 + + // ---- push result ------------------------------------------------------ + (res_lo, res_hi) +} + +#[no_mangle] +pub fn karatsuba_mul64_stack(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { + let (res_lo, res_hi) = karatsuba_mul64_impl(a_lo, a_hi, b_lo, b_hi); + (res_hi as u64) << 32 | res_lo as u64 +} + +#[inline(always)] +pub(crate) fn add64_impl(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> (u32, u32) { + // low part + let sum_lo = a_lo.wrapping_add(b_lo); + // compute carry without branches + let carry = (sum_lo < a_lo) as u32; + // high part + carry + let sum_hi = a_hi.wrapping_add(b_hi).wrapping_add(carry); + // push result + (sum_lo, sum_hi) +} + +#[no_mangle] +pub fn add64_stack(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { + let (res_lo, res_hi) = add64_impl(a_lo, a_hi, b_lo, b_hi); + (res_hi as u64) << 32 | res_lo as u64 +} + +#[inline(always)] +pub(crate) fn div64u_impl( + n_lo: u32, + n_hi: u32, + d_lo: u32, + d_hi: u32, +) -> (u32 /* q_lo */, u32 /* q_hi */) { + // ---------- simple corner cases --------------------------------------- + if d_hi == 0 && d_lo == 0 { + core::hint::black_box(()); // force a trap later; UB if ignored + } + if (n_hi < d_hi) || (n_hi == d_hi && n_lo < d_lo) { + return (0, 0); // quotient = 0 (remainder is n, caller doesn’t need it) + } + + // ---------- fast path: divisor fits in one limb ----------------------- + if d_hi == 0 { + // divide (n_hi<<32 | n_lo) by 32-bit d_lo using two 32-bit loops + #[inline(always)] + fn div_mod_64_by_32(hi: u32, lo: u32, d: u32) -> (u32, u32) { + let mut q = 0u32; + let mut r = 0u32; + for i in (0..64).rev() { + // shift remainder left, bring next dividend bit + r <<= 1; + r |= if i >= 32 { + (hi >> (i - 32)) & 1 + } else { + (lo >> i) & 1 + }; + if r >= d { + r -= d; + if i >= 32 { + q |= 1 << (i - 32); + } else { + q |= 1 << i; + } + } + } + (q, r) + } + + // high half first, then low half with carry remainder + let (q_hi, rem) = div_mod_64_by_32(0, n_hi, d_lo); + let (q_lo, _) = div_mod_64_by_32(rem, n_lo, d_lo); + return (q_lo, q_hi); + } + + // ---------- general 64-bit ÷ 64-bit long division --------------------- + let mut n_hi = n_hi; + let mut n_lo = n_lo; + let mut r_hi = 0u32; + let mut r_lo = 0u32; + let mut q_hi = 0u32; + let mut q_lo = 0u32; + + for _ in 0..64 { + // left-shift (r_hi,r_lo,n_hi,n_lo) by 1 + let carry_n_hi = (n_hi >> 31) & 1; + let carry_n_lo = (n_lo >> 31) & 1; + n_hi = (n_hi << 1) | carry_n_lo; + n_lo <<= 1; + let carry_r_lo = (r_lo >> 31) & 1; + r_hi = (r_hi << 1) | carry_r_lo; + r_lo = (r_lo << 1) | carry_n_hi; + + // compare remainder with divisor + let ge = (r_hi > d_hi) || (r_hi == d_hi && r_lo >= d_lo); + if ge { + // r -= d + let (new_lo, borrow) = r_lo.overflowing_sub(d_lo); + r_lo = new_lo; + r_hi = r_hi.wrapping_sub(d_hi + borrow as u32); + } + + // shift quotient left and add current bit + let carry_q = (q_lo >> 31) & 1; + q_lo = (q_lo << 1) | ge as u32; + q_hi = (q_hi << 1) | carry_q; + } + + (q_lo, q_hi) +} + +#[no_mangle] +pub fn div64u_stack(n_lo: u32, n_hi: u32, d_lo: u32, d_hi: u32) -> u64 { + let (q_lo, q_hi) = div64u_impl(n_lo, n_hi, d_lo, d_hi); + ((q_hi as u64) << 32) | q_lo as u64 +} + +/// ------------------------------------------------------------------------- +/// helpers +/// ------------------------------------------------------------------------- + +/// Two’s-complement negate of a 64-bit value held in little-endian limbs. +#[inline(always)] +fn neg64(lo: u32, hi: u32) -> (u32, u32) { + let lo_n = (!lo).wrapping_add(1); + let hi_n = (!hi).wrapping_add((lo_n == 0) as u32); + (lo_n, hi_n) +} + +/// Unsigned 64-bit division in pure 32-bit arithmetic (from previous reply). +#[inline(always)] +fn div64_impl(n_lo: u32, n_hi: u32, d_lo: u32, d_hi: u32) -> (u32 /* q_lo */, u32 /* q_hi */) { + /* … same code as before … */ + #![allow(unused_mut, clippy::needless_range_loop)] + // --- fast corner cases and 32-bit divisor path elided for brevity --- + let mut n_hi = n_hi; + let mut n_lo = n_lo; + let mut r_hi = 0u32; + let mut r_lo = 0u32; + let mut q_hi = 0u32; + let mut q_lo = 0u32; + + for _ in 0..64 { + let carry_n_hi = n_hi >> 31; + let carry_n_lo = n_lo >> 31; + n_hi = (n_hi << 1) | carry_n_lo; + n_lo <<= 1; + let carry_r_lo = r_lo >> 31; + r_hi = (r_hi << 1) | carry_r_lo; + r_lo = (r_lo << 1) | carry_n_hi; + + let ge = (r_hi > d_hi) || (r_hi == d_hi && r_lo >= d_lo); + if ge { + let (new_lo, borrow) = r_lo.overflowing_sub(d_lo); + r_lo = new_lo; + r_hi = r_hi.wrapping_sub(d_hi + borrow as u32); + } + + let carry_q = q_lo >> 31; + q_lo = (q_lo << 1) | ge as u32; + q_hi = (q_hi << 1) | carry_q; + } + (q_lo, q_hi) +} + +/// ------------------------------------------------------------------------- +/// signed division implementation (two-limb in / two-limb out) +/// ------------------------------------------------------------------------- +#[inline(always)] +pub(crate) fn div64s_impl( + n_lo: u32, + n_hi: u32, + d_lo: u32, + d_hi: u32, +) -> (u32 /* q_lo */, u32 /* q_hi */) { + // 1. Extract signs + let n_neg = (n_hi >> 31) != 0; + let d_neg = (d_hi >> 31) != 0; + + // 2. Absolute values + let (n_lo, n_hi) = if n_neg { + neg64(n_lo, n_hi) + } else { + (n_lo, n_hi) + }; + let (d_lo, d_hi) = if d_neg { + neg64(d_lo, d_hi) + } else { + (d_lo, d_hi) + }; + + // 3. Unsigned divide + let (mut q_lo, mut q_hi) = div64_impl(n_lo, n_hi, d_lo, d_hi); + + // 4. Apply sign to quotient (truncate toward zero) + if n_neg ^ d_neg { + let (lo, hi) = neg64(q_lo, q_hi); + q_lo = lo; + q_hi = hi; + } + (q_lo, q_hi) +} + +/// ------------------------------------------------------------------------- +/// public wrapper: packed i64 result +/// ------------------------------------------------------------------------- +#[no_mangle] +pub fn div64s_stack(n_lo: u32, n_hi: u32, d_lo: u32, d_hi: u32) -> i64 { + let (q_lo, q_hi) = div64s_impl(n_lo, n_hi, d_lo, d_hi); + let bits = ((q_hi as u64) << 32) | q_lo as u64; + bits as i64 +} + +/// ------------------------------------------------------------------------- +/// 64-bit subtraction in pure 32-bit arithmetic +/// (two’s-complement works for both signed and unsigned values) +/// ------------------------------------------------------------------------- + +#[inline(always)] +pub(crate) fn sub64_impl( + a_lo: u32, + a_hi: u32, + b_lo: u32, + b_hi: u32, +) -> (u32 /* res_lo */, u32 /* res_hi */) { + // low 32-bit difference + let diff_lo = a_lo.wrapping_sub(b_lo); + + // detect borrow without branches + let borrow = (a_lo < b_lo) as u32; + + // high 32-bit difference minus borrow + let diff_hi = a_hi.wrapping_sub(b_hi).wrapping_sub(borrow); + + (diff_lo, diff_hi) +} + +#[no_mangle] +pub fn sub64_stack(a_lo: u32, a_hi: u32, b_lo: u32, b_hi: u32) -> u64 { + let (res_lo, res_hi) = sub64_impl(a_lo, a_hi, b_lo, b_hi); + // pack the two limbs back into a single i64 + ((res_hi as u64) << 32) | res_lo as u64 +} diff --git a/snippets/lib.wasm b/snippets/lib.wasm new file mode 100755 index 0000000000000000000000000000000000000000..66682f93e826bcae2cc17fca139a2747280a52ba GIT binary patch literal 6972 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 M4K_wA;&bu;0?9F0{{R30 literal 0 HcmV?d00001 diff --git a/snippets/lib.wat b/snippets/lib.wat new file mode 100644 index 000000000..fc8053642 --- /dev/null +++ b/snippets/lib.wat @@ -0,0 +1,582 @@ +(module $snippets.wasm + (type (;0;) (func (param i32 i32 i32 i32) (result i64))) + (func $karatsuba_mul64_stack (type 0) (param i32 i32 i32 i32) (result i64) + (local i32 i32 i32) + local.get 3 + local.get 2 + i32.add + local.get 1 + local.get 0 + i32.add + i32.mul + local.get 3 + local.get 1 + i32.mul + local.get 2 + i32.const 65535 + i32.and + local.tee 1 + local.get 0 + i32.const 65535 + i32.and + local.tee 3 + i32.mul + local.tee 4 + local.get 2 + i32.const 16 + i32.shr_u + local.tee 5 + local.get 3 + i32.mul + local.tee 3 + local.get 1 + local.get 0 + i32.const 16 + i32.shr_u + local.tee 6 + i32.mul + i32.add + local.tee 0 + i32.const 16 + i32.shl + i32.add + local.tee 2 + i32.add + i32.sub + local.get 2 + local.get 4 + i32.lt_u + i32.add + local.get 5 + local.get 6 + i32.mul + local.tee 1 + local.get 0 + i32.const 16 + i32.shr_u + local.get 0 + local.get 3 + i32.lt_u + i32.const 16 + i32.shl + i32.or + i32.add + local.tee 0 + i32.add + local.get 0 + local.get 1 + i32.lt_u + i32.add + i64.extend_i32_u + i64.const 32 + i64.shl + local.get 2 + i64.extend_i32_u + i64.or) + (func $add64_stack (type 0) (param i32 i32 i32 i32) (result i64) + local.get 3 + local.get 1 + i32.add + local.get 2 + local.get 0 + i32.add + local.tee 1 + local.get 2 + i32.lt_u + i32.add + i64.extend_i32_u + i64.const 32 + i64.shl + local.get 1 + i64.extend_i32_u + i64.or) + (func $div64u_stack (type 0) (param i32 i32 i32 i32) (result i64) + (local i32 i32 i32 i32 i32 i32 i32) + block ;; label = @1 + block ;; label = @2 + local.get 3 + br_if 0 (;@2;) + block ;; label = @3 + local.get 2 + br_if 0 (;@3;) + end + block ;; label = @3 + local.get 1 + br_if 0 (;@3;) + i32.const 0 + local.set 4 + i32.const 0 + local.set 5 + local.get 0 + local.get 2 + i32.lt_u + br_if 2 (;@1;) + end + i32.const 0 + local.set 4 + i32.const 63 + local.set 6 + i32.const 0 + local.set 7 + loop ;; label = @3 + local.get 7 + i32.const 1 + i32.shl + local.set 7 + block ;; label = @4 + block ;; label = @5 + local.get 6 + i32.const 31 + i32.gt_u + br_if 0 (;@5;) + local.get 1 + local.get 6 + i32.shr_u + i32.const 1 + i32.and + local.get 7 + i32.or + local.tee 7 + local.get 2 + i32.lt_u + br_if 1 (;@4;) + local.get 7 + local.get 2 + i32.sub + local.set 7 + local.get 4 + i32.const 1 + local.get 6 + i32.shl + i32.or + local.set 4 + br 1 (;@4;) + end + local.get 7 + local.get 2 + i32.lt_u + br_if 0 (;@4;) + local.get 7 + local.get 2 + i32.sub + local.set 7 + i32.const 1 + local.get 6 + i32.shl + local.get 4 + i32.or + local.set 4 + end + local.get 6 + i32.const -1 + i32.add + local.tee 6 + i32.const -1 + i32.ne + br_if 0 (;@3;) + end + i32.const 0 + local.set 5 + i32.const 63 + local.set 6 + i32.const 0 + local.set 1 + loop ;; label = @3 + local.get 1 + i32.const 1 + i32.shl + local.set 1 + block ;; label = @4 + block ;; label = @5 + local.get 6 + i32.const 31 + i32.gt_u + br_if 0 (;@5;) + local.get 0 + local.get 6 + i32.shr_u + i32.const 1 + i32.and + local.get 1 + i32.or + local.tee 1 + local.get 2 + i32.lt_u + br_if 1 (;@4;) + local.get 1 + local.get 2 + i32.sub + local.set 1 + local.get 5 + i32.const 1 + local.get 6 + i32.shl + i32.or + local.set 5 + br 1 (;@4;) + end + local.get 7 + local.get 6 + i32.shr_u + i32.const 1 + i32.and + local.get 1 + i32.or + local.tee 1 + local.get 2 + i32.lt_u + br_if 0 (;@4;) + local.get 1 + local.get 2 + i32.sub + local.set 1 + i32.const 1 + local.get 6 + i32.const 31 + i32.and + i32.shl + local.get 5 + i32.or + local.set 5 + end + local.get 6 + i32.const -1 + i32.add + local.tee 6 + i32.const -1 + i32.ne + br_if 0 (;@3;) + br 2 (;@1;) + end + end + i32.const 0 + local.set 4 + block ;; label = @2 + local.get 1 + local.get 3 + i32.ge_u + br_if 0 (;@2;) + i32.const 0 + local.set 5 + br 1 (;@1;) + end + block ;; label = @2 + local.get 0 + local.get 2 + i32.ge_u + br_if 0 (;@2;) + i32.const 0 + local.set 5 + local.get 1 + local.get 3 + i32.eq + br_if 1 (;@1;) + end + i32.const 0 + local.set 7 + i32.const 64 + local.set 8 + i32.const 0 + local.set 9 + i32.const 0 + local.set 4 + i32.const 0 + local.set 5 + loop ;; label = @2 + local.get 9 + i32.const 1 + i32.shl + local.get 1 + i32.const 31 + i32.shr_u + i32.or + local.set 6 + block ;; label = @3 + block ;; label = @4 + local.get 7 + i32.const 1 + i32.shl + local.get 9 + i32.const 31 + i32.shr_u + i32.or + local.tee 7 + local.get 3 + i32.gt_u + br_if 0 (;@4;) + i32.const 0 + local.set 10 + block ;; label = @5 + local.get 7 + local.get 3 + i32.ne + br_if 0 (;@5;) + local.get 6 + local.get 2 + i32.ge_u + br_if 1 (;@4;) + end + local.get 6 + local.set 9 + br 1 (;@3;) + end + local.get 7 + local.get 3 + i32.sub + local.get 6 + local.get 2 + i32.lt_u + i32.sub + local.set 7 + local.get 6 + local.get 2 + i32.sub + local.set 9 + i32.const 1 + local.set 10 + end + local.get 1 + i32.const 1 + i32.shl + local.get 0 + i32.const 31 + i32.shr_u + i32.or + local.set 1 + local.get 0 + i32.const 1 + i32.shl + local.set 0 + local.get 4 + i32.const 1 + i32.shl + local.get 5 + i32.const 31 + i32.shr_u + i32.or + local.set 4 + local.get 10 + local.get 5 + i32.const 1 + i32.shl + i32.or + local.set 5 + local.get 8 + i32.const -1 + i32.add + local.tee 8 + br_if 0 (;@2;) + end + end + local.get 4 + i64.extend_i32_u + i64.const 32 + i64.shl + local.get 5 + i64.extend_i32_u + i64.or) + (func $div64s_stack (type 0) (param i32 i32 i32 i32) (result i64) + (local i32 i32 i32 i32 i32 i32 i32 i32 i32) + block ;; label = @1 + block ;; label = @2 + local.get 1 + i32.const -1 + i32.le_s + br_if 0 (;@2;) + local.get 1 + local.set 4 + local.get 0 + local.set 5 + br 1 (;@1;) + end + i32.const 0 + local.get 0 + i32.sub + local.set 5 + local.get 0 + i32.eqz + local.get 1 + i32.const -1 + i32.xor + i32.add + local.set 4 + end + block ;; label = @1 + block ;; label = @2 + local.get 3 + i32.const -1 + i32.le_s + br_if 0 (;@2;) + local.get 3 + local.set 6 + local.get 2 + local.set 7 + br 1 (;@1;) + end + i32.const 0 + local.get 2 + i32.sub + local.set 7 + local.get 2 + i32.eqz + local.get 3 + i32.const -1 + i32.xor + i32.add + local.set 6 + end + i32.const 0 + local.set 2 + i32.const 64 + local.set 8 + i32.const 0 + local.set 9 + i32.const 0 + local.set 10 + i32.const 0 + local.set 11 + loop ;; label = @1 + local.get 9 + i32.const 1 + i32.shl + local.get 4 + i32.const 31 + i32.shr_u + i32.or + local.set 0 + block ;; label = @2 + block ;; label = @3 + local.get 2 + i32.const 1 + i32.shl + local.get 9 + i32.const 31 + i32.shr_u + i32.or + local.tee 2 + local.get 6 + i32.gt_u + br_if 0 (;@3;) + i32.const 0 + local.set 12 + block ;; label = @4 + local.get 2 + local.get 6 + i32.ne + br_if 0 (;@4;) + local.get 0 + local.get 7 + i32.ge_u + br_if 1 (;@3;) + end + local.get 0 + local.set 9 + br 1 (;@2;) + end + local.get 2 + local.get 6 + i32.sub + local.get 0 + local.get 7 + i32.lt_u + i32.sub + local.set 2 + local.get 0 + local.get 7 + i32.sub + local.set 9 + i32.const 1 + local.set 12 + end + local.get 4 + i32.const 1 + i32.shl + local.get 5 + i32.const 31 + i32.shr_u + i32.or + local.set 4 + local.get 5 + i32.const 1 + i32.shl + local.set 5 + local.get 10 + i32.const 1 + i32.shl + local.get 11 + i32.const 31 + i32.shr_u + i32.or + local.set 10 + local.get 12 + local.get 11 + i32.const 1 + i32.shl + i32.or + local.set 11 + local.get 8 + i32.const -1 + i32.add + local.tee 8 + br_if 0 (;@1;) + end + block ;; label = @1 + block ;; label = @2 + local.get 3 + local.get 1 + i32.xor + i32.const -1 + i32.le_s + br_if 0 (;@2;) + local.get 11 + local.set 2 + br 1 (;@1;) + end + i32.const 0 + local.get 11 + i32.sub + local.set 2 + local.get 11 + i32.eqz + local.get 10 + i32.const -1 + i32.xor + i32.add + local.set 10 + end + local.get 10 + i64.extend_i32_u + i64.const 32 + i64.shl + local.get 2 + i64.extend_i32_u + i64.or) + (func $sub64_stack (type 0) (param i32 i32 i32 i32) (result i64) + local.get 1 + local.get 3 + i32.sub + local.get 0 + local.get 2 + i32.lt_u + i32.sub + i64.extend_i32_u + i64.const 32 + i64.shl + local.get 0 + local.get 2 + i32.sub + i64.extend_i32_u + i64.or) + (table (;0;) 1 1 funcref) + (memory (;0;) 16) + (global $__stack_pointer (mut i32) (i32.const 1048576)) + (global (;1;) i32 (i32.const 1048576)) + (global (;2;) i32 (i32.const 1048576)) + (export "memory" (memory 0)) + (export "karatsuba_mul64_stack" (func $karatsuba_mul64_stack)) + (export "add64_stack" (func $add64_stack)) + (export "div64u_stack" (func $div64u_stack)) + (export "div64s_stack" (func $div64s_stack)) + (export "sub64_stack" (func $sub64_stack)) + (export "__data_end" (global 1)) + (export "__heap_base" (global 2))) diff --git a/snippets/test.rs b/snippets/test.rs new file mode 100644 index 000000000..8d92cae9d --- /dev/null +++ b/snippets/test.rs @@ -0,0 +1,63 @@ +use crate::{add64_impl, div64s_impl, div64u_impl, karatsuba_mul64_impl, sub64_impl}; +use rand::Rng; + +#[test] +fn test_i64_mul_fuzz() { + for _ in 0..1_000_000 { + let a = rand::rng().random::(); + let b = rand::rng().random::(); + let c = a.wrapping_mul(b); + let (r_lo, r_hi) = + karatsuba_mul64_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); + let r = (r_hi as u64) << 32 | r_lo as u64; + assert_eq!(c, r); + } +} + +#[test] +fn test_i64_add_fuzz() { + for _ in 0..1_000_000 { + let a = rand::rng().random::(); + let b = rand::rng().random::(); + let c = a.wrapping_add(b); + let (r_lo, r_hi) = add64_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); + let r = (r_hi as u64) << 32 | r_lo as u64; + assert_eq!(c, r); + } +} + +#[test] +fn test_i64_div_u_fuzz() { + for _ in 0..1_000_000 { + let a = rand::rng().random::(); + let b = rand::rng().random::(); + let c = a.wrapping_div(b); + let (r_lo, r_hi) = div64u_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); + let r = (r_hi as u64) << 32 | r_lo as u64; + assert_eq!(c, r); + } +} + +#[test] +fn test_i64_div_s_fuzz() { + for _ in 0..1_000_000 { + let a = rand::rng().random::(); + let b = rand::rng().random::(); + let c = a.wrapping_div(b); + let (r_lo, r_hi) = div64s_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); + let r = (r_hi as i64) << 32 | r_lo as i64; + assert_eq!(c, r); + } +} + +#[test] +fn test_i64_sub_fuzz() { + for _ in 0..1_000_000 { + let a = rand::rng().random::(); + let b = rand::rng().random::(); + let c = a.wrapping_sub(b); + let (r_lo, r_hi) = sub64_impl(a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32); + let r = (r_hi as u64) << 32 | r_lo as u64; + assert_eq!(c, r); + } +} From 4546163868ca2e19dfaf1872f19f38f646c37255 Mon Sep 17 00:00:00 2001 From: Dmitry Savonin Date: Thu, 15 May 2025 15:30:52 +0400 Subject: [PATCH 009/141] feat(i32): add emitter for karatsuba i64 mul --- snippets/Cargo.toml | 1 + snippets/lib.rs | 1 + snippets/rwasm.rs | 185 +++++++++++++++++++++++++++++++++++ src/compiler.rs | 30 ++++++ src/lib.rs | 4 + src/types/instruction_set.rs | 2 +- src/types/module.rs | 10 ++ src/vm/context.rs | 4 + src/vm/executor.rs | 5 + src/vm/opcodes.rs | 1 + src/vm/tracer.rs | 8 +- src/vm/value_stack.rs | 11 +-- 12 files changed, 251 insertions(+), 11 deletions(-) create mode 100644 snippets/rwasm.rs create mode 100644 src/compiler.rs diff --git a/snippets/Cargo.toml b/snippets/Cargo.toml index 1fc329c70..8f0ea092d 100644 --- a/snippets/Cargo.toml +++ b/snippets/Cargo.toml @@ -22,3 +22,4 @@ codegen-units = 1 [dependencies] walrus = "0.23.3" +rwasm = { path = ".." } diff --git a/snippets/lib.rs b/snippets/lib.rs index a68f23c30..49a42d494 100644 --- a/snippets/lib.rs +++ b/snippets/lib.rs @@ -1,6 +1,7 @@ #![allow(clippy::needless_range_loop)] mod extractor; +mod rwasm; #[cfg(test)] mod test; diff --git a/snippets/rwasm.rs b/snippets/rwasm.rs new file mode 100644 index 000000000..902ae5304 --- /dev/null +++ b/snippets/rwasm.rs @@ -0,0 +1,185 @@ +use rand::Rng; +use rwasm::{DropKeep, ExecutorConfig, InstructionSet, RwasmExecutor, RwasmModule, StackAlloc}; + +pub fn emit_i64_mul(is: &mut InstructionSet) { + is.op_stack_alloc(StackAlloc { + max_stack_height: 10, + }); + is.op_i32_const(0); // (local i32) + is.op_i32_const(0); // (local i32) + is.op_i32_const(0); // (local i32) + is.op_local_get(4); // local.get 3 + is.op_local_get(6); // local.get 2 + is.op_i32_add(); // i32.add + is.op_local_get(7); // local.get 1 + is.op_local_get(9); // local.get 0 + is.op_i32_add(); // i32.add + is.op_i32_mul(); // i32.mul + is.op_local_get(5); // local.get 3 + is.op_local_get(8); // local.get 1 + is.op_i32_mul(); // i32.mul + is.op_local_get(7); // local.get 2 + is.op_i32_const(65535); // i32.const 65535 + is.op_i32_and(); // i32.and + is.op_local_tee(9); // local.tee 1 + is.op_local_get(10); // local.get 0 + is.op_i32_const(65535); // i32.const 65535 + is.op_i32_and(); // i32.and + is.op_local_tee(8); // local.tee 3 + is.op_i32_mul(); // i32.mul + is.op_local_tee(6); // local.tee 4 + is.op_local_get(8); // local.get 2 + is.op_i32_const(16); // i32.const 16 + is.op_i32_shr_u(); // i32.shr_u + is.op_local_tee(6); // local.tee 5 + is.op_local_get(8); // local.get 3 + is.op_i32_mul(); // i32.mul + is.op_local_tee(8); // local.tee 3 + is.op_local_get(10); // local.get 1 + is.op_local_get(12); // local.get 0 + is.op_i32_const(16); // i32.const 16 + is.op_i32_shr_u(); // i32.shr_u + is.op_local_tee(7); // local.tee 6 + is.op_i32_mul(); // i32.mul + is.op_i32_add(); // i32.add + is.op_local_tee(11); // local.tee 0 + is.op_i32_const(16); // i32.const 16 + is.op_i32_shl(); // i32.shl + is.op_i32_add(); // i32.add + is.op_local_tee(8); // local.tee 2 + is.op_i32_add(); // i32.add + is.op_i32_sub(); // i32.sub + is.op_local_get(6); // local.get 2 + is.op_local_get(5); // local.get 4 + is.op_i32_lt_u(); // i32.lt_u + is.op_i32_add(); // i32.add + is.op_local_get(3); // local.get 5 + is.op_local_get(3); // local.get 6 + is.op_i32_mul(); // i32.mul + is.op_local_tee(8); // local.tee 1 + is.op_local_get(9); // local.get 0 + is.op_i32_const(16); // i32.const 16 + is.op_i32_shr_u(); // i32.shr_u + is.op_local_get(10); // local.get 0 + is.op_local_get(8); // local.get 3 + is.op_i32_lt_u(); // i32.lt_u + is.op_i32_const(16); // i32.const 16 + is.op_i32_shl(); // i32.shl + is.op_i32_or(); // i32.or + is.op_i32_add(); // i32.add + is.op_local_tee(9); // local.tee 0 + is.op_i32_add(); // i32.add + is.op_local_get(8); // local.get 0 + is.op_local_get(8); // local.get 1 + is.op_i32_lt_u(); // i32.lt_u + is.op_i32_add(); // i32.add + is.op_local_get(6); + // TODO(dmitry123): "how efficiently make drop=7 keep=2?" + is.op_local_set(7); + is.op_local_set(7); + is.op_drop(); + is.op_drop(); + is.op_drop(); + is.op_drop(); + is.op_drop(); +} + +#[allow(unused)] +pub fn trace_execution_logs(vm: &RwasmExecutor<()>) { + let trace = vm.tracer().unwrap().logs.len(); + println!("execution trace ({} steps):", trace); + println!("fuel consumed: {}", vm.fuel_consumed()); + let logs = &vm.tracer().unwrap().logs; + println!("execution trace ({} steps):", logs.len()); + for log in logs.iter().rev().take(100_000).rev() { + println!( + " - pc={} opcode={:?}({:?}) gas={} stack={:?}", + log.program_counter, + log.opcode, + log.value, + log.consumed_fuel, + log.stack + .iter() + .map(|v| v.to_string()) + .rev() + .take(100) + .rev() + .collect::>(), + ); + } +} + +fn exec_instrs(mut is: InstructionSet, inputs: Vec) -> Vec { + is.op_return(DropKeep::none()); + let rwasm_module = RwasmModule::with_one_function(is); + let mut vm = RwasmExecutor::new( + rwasm_module.into(), + ExecutorConfig::default().trace_enabled(true), + (), + ); + for i in inputs { + vm.caller().stack_push(i); + } + let exit_code = vm.run().unwrap(); + assert_eq!(exit_code, 0); + vm.caller() + .dump_stack() + .iter() + .map(|v| v.as_u32()) + .collect::>() +} + +#[test] +fn test_rwasm_i64_mul() { + let mut is = InstructionSet::new(); + emit_i64_mul(&mut is); + + let test_case_u64 = |a: u64, b: u64| { + let c = a.wrapping_mul(b); + let output = exec_instrs( + is.clone(), + vec![a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32], + ); + assert_eq!(output.len(), 2); + let r = (output[0] as u64) << 32 | output[1] as u64; + assert_eq!(c, r); + }; + + let test_case_i64 = |a: i64, b: i64| { + test_case_u64(a as u64, b as u64); + }; + + // random test cases + for _ in 0..100_000 { + let a = rand::rng().random::(); + let b = rand::rng().random::(); + test_case_u64(a, b); + let a = rand::rng().random::(); + let b = rand::rng().random::(); + test_case_u64(a as u64, b as u64); + } + + // u64 test cases + test_case_u64(0x0000_0000_0000_0000, 0x0000_0000_0000_0000); // 0 × 0 + test_case_u64(0x0000_0000_0000_0000, 0x0000_0000_075B_CD15); // 0 × n + test_case_u64(0x0000_0000_0000_0001, 0xFFFF_FFFF_FFFF_FFFF); // 1 × max + test_case_u64(0xFFFF_FFFF_FFFF_FFFF, 0xFFFF_FFFF_FFFF_FFFF); // (−1)² → 1 + test_case_u64(0x0000_0000_FFFF_FFFF, 0x0000_0000_FFFF_FFFF); // 32-bit × 32-bit + test_case_u64(0x0000_0001_0000_0000, 0x0000_0001_0000_0000); // 2³² × 2³² → wrap 0 + test_case_u64(0xDEAD_BEEF_CAFE_BABE, 0x1234_5678_9ABC_DEF0); // random dense bits + test_case_u64(0x8000_0000_0000_0000, 0x0000_0000_0000_0002); // high bit × 2 → 0 + test_case_u64(0x7FFF_FFFF_FFFF_FFFF, 0x0000_0000_0000_0002); // just-below-wrap × 2 + test_case_u64(0x5555_5555_5555_5555, 0xAAAA_AAAA_AAAA_AAAA); // alternating masks + + // i64 test cases + test_case_i64(0, 0); // 0 × 0 + test_case_i64(0, -123_456_789); // 0 × −n + test_case_i64(1, -1); // 1 × −1 + test_case_i64(-1, -1); // (−1)² + test_case_i64(9_223_372_036_854_775_807, 2); // i64::MAX × 2 → wrap + test_case_i64(i64::MIN, -1); // MIN × −1 → wrap + test_case_i64(i64::MIN, 2); // MIN × 2 → 0 + test_case_i64(-81_985_529_216_486_895, 538_030_035_483_195_255); // mixed signs, dense + test_case_i64(-81_985_529_216_486_895, -538_030_035_483_195_255); // neg × neg → pos + test_case_i64(81_985_529_216_486_895, -81_985_529_216_486_895); // pos × neg +} diff --git a/src/compiler.rs b/src/compiler.rs new file mode 100644 index 000000000..23d31b548 --- /dev/null +++ b/src/compiler.rs @@ -0,0 +1,30 @@ +use rwasm_legacy::{ + engine::RwasmConfig, + rwasm::{BinaryFormat, BinaryFormatWriter, RwasmModule}, + Error, +}; + +pub struct RwasmCompilationResult { + pub rwasm_bytecode: Vec, + pub constructor_params: Vec, +} + +pub fn compile_wasm_to_rwasm( + wasm_binary: &[u8], + rwasm_config: RwasmConfig, +) -> Result { + let mut config = RwasmModule::default_config(None); + config.rwasm_config(rwasm_config); + let (rwasm_module, constructor_params) = + RwasmModule::compile_and_retrieve_input(wasm_binary, &config)?; + let length = rwasm_module.encoded_length(); + let mut rwasm_bytecode = vec![0u8; length]; + let mut binary_format_writer = BinaryFormatWriter::new(&mut rwasm_bytecode); + rwasm_module + .write_binary(&mut binary_format_writer) + .expect("failed to encode rwasm bytecode"); + Ok(RwasmCompilationResult { + rwasm_bytecode, + constructor_params, + }) +} diff --git a/src/lib.rs b/src/lib.rs index 888fc62f3..26377ad60 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,15 +2,19 @@ #![allow(dead_code)] #![warn(unused_crate_dependencies)] +mod compiler; mod types; mod vm; extern crate alloc; extern crate core; +pub use compiler::*; pub use types::*; pub use vm::*; pub mod legacy { pub use rwasm_legacy::*; } + +use libm as _; diff --git a/src/types/instruction_set.rs b/src/types/instruction_set.rs index b474aa2f1..f278cf194 100644 --- a/src/types/instruction_set.rs +++ b/src/types/instruction_set.rs @@ -77,7 +77,7 @@ macro_rules! impl_opcode { impl InstructionSet { pub fn new() -> Self { - Self::default() + Self { instr: vec![] } } pub fn push(&mut self, opcode: Opcode, data: OpcodeData) -> u32 { diff --git a/src/types/module.rs b/src/types/module.rs index b16675930..9308b974e 100644 --- a/src/types/module.rs +++ b/src/types/module.rs @@ -36,6 +36,16 @@ impl RwasmModule { } } + pub fn with_one_function(code_section: InstructionSet) -> Self { + RwasmModule { + code_section, + memory_section: vec![], + element_section: vec![], + source_pc: 0, + func_section: vec![0], + } + } + pub fn new(sink: &[u8]) -> Self { let module: RwasmModule; (module, _) = bincode::decode_from_slice(sink, bincode::config::legacy()) diff --git a/src/vm/context.rs b/src/vm/context.rs index 347eb1fca..7bf78d60c 100644 --- a/src/vm/context.rs +++ b/src/vm/context.rs @@ -84,4 +84,8 @@ impl<'a, T> Caller<'a, T> { pub fn context(&self) -> &T { self.vm.context() } + + pub fn dump_stack(&mut self) -> Vec { + self.vm.value_stack.dump_stack(self.vm.sp) + } } diff --git a/src/vm/executor.rs b/src/vm/executor.rs index 229d3edbc..283819342 100644 --- a/src/vm/executor.rs +++ b/src/vm/executor.rs @@ -28,6 +28,7 @@ use crate::{ tracer::Tracer, value_stack::{ValueStack, ValueStackPtr}, }, + Caller, }; use alloc::{sync::Arc, vec, vec::Vec}; use bitvec::{bitvec, prelude::BitVec}; @@ -187,6 +188,10 @@ impl RwasmExecutor { } } + pub fn caller(&mut self) -> Caller { + Caller::new(self) + } + pub fn set_syscall_handler(&mut self, handler: SyscallHandler) { self.syscall_handler = handler; } diff --git a/src/vm/opcodes.rs b/src/vm/opcodes.rs index 1ccde4f3b..09ebd6f4e 100644 --- a/src/vm/opcodes.rs +++ b/src/vm/opcodes.rs @@ -54,6 +54,7 @@ pub(crate) fn run_the_loop(vm: &mut RwasmExecutor) -> Result, pub table_changes: Vec, pub table_size_changes: Vec, @@ -105,6 +109,7 @@ impl Tracer { &mut self, program_counter: u32, opcode: Opcode, + value: OpcodeData, stack: Vec, meta: &OpcodeMeta, memory_size: u32, @@ -116,6 +121,7 @@ impl Tracer { let opcode_state = TracerInstrState { program_counter, opcode, + value, memory_changes, table_changes, table_size_changes, diff --git a/src/vm/value_stack.rs b/src/vm/value_stack.rs index 8c4ca6185..c1ecb6409 100644 --- a/src/vm/value_stack.rs +++ b/src/vm/value_stack.rs @@ -419,12 +419,7 @@ impl ValueStackPtr { // Wasm validation and `wasmi` codegen to never run out // of valid bounds using this method. self.ptr = unsafe { self.ptr.add(delta) }; - // let diff = self.ptr as isize - self.src as isize; - // if diff < 0 { - // unreachable!("STACK UNDERFLOW") - // } else if diff > self.len as isize { - // unreachable!("STACK OVERFLOW") - // } + debug_assert!(self.ptr >= self.src, "stack underflow"); } /// Decreases the [`ValueStackPtr`] of `self` by one. @@ -434,9 +429,7 @@ impl ValueStackPtr { // Wasm validation and `wasmi` codegen to never run out // of valid bounds using this method. self.ptr = unsafe { self.ptr.sub(delta) }; - if self.ptr < self.src { - unreachable!("STACK UNDERFLOW") - } + debug_assert!(self.ptr >= self.src, "stack underflow"); } /// Pushes the `T` to the end of the [`ValueStack`]. From c314b613555fd5b68eb6ce42d91ca99bb8a2f0aa Mon Sep 17 00:00:00 2001 From: Dmitry Savonin Date: Thu, 15 May 2025 15:41:49 +0400 Subject: [PATCH 010/141] chore: add benchmarks --- benchmarks/.gitignore | 4 ++ benchmarks/Cargo.toml | 25 ++++++++ benchmarks/Makefile | 6 ++ benchmarks/README.md | 16 +++++ benchmarks/bench.rs | 110 +++++++++++++++++++++++++++++++++++ benchmarks/fibonacci-program | Bin 0 -> 78328 bytes benchmarks/greeting-elf | Bin 0 -> 112420 bytes benchmarks/keccak-elf | Bin 0 -> 118836 bytes benchmarks/lib.rs | 18 ++++++ benchmarks/rust-toolchain | 1 + benchmarks/test.rs | 66 +++++++++++++++++++++ 11 files changed, 246 insertions(+) create mode 100644 benchmarks/.gitignore create mode 100644 benchmarks/Cargo.toml create mode 100644 benchmarks/Makefile create mode 100644 benchmarks/README.md create mode 100644 benchmarks/bench.rs create mode 100755 benchmarks/fibonacci-program create mode 100755 benchmarks/greeting-elf create mode 100755 benchmarks/keccak-elf create mode 100644 benchmarks/lib.rs create mode 100644 benchmarks/rust-toolchain create mode 100644 benchmarks/test.rs diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore new file mode 100644 index 000000000..c7cb67382 --- /dev/null +++ b/benchmarks/.gitignore @@ -0,0 +1,4 @@ +target +Cargo.lock +lib.wat +lib.wasm \ No newline at end of file diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml new file mode 100644 index 000000000..db9b20061 --- /dev/null +++ b/benchmarks/Cargo.toml @@ -0,0 +1,25 @@ +[package] +name = "benchmark-fib" +version = "0.1.0" +edition = "2021" + +[lib] +crate-type = ["cdylib"] +path = "lib.rs" + +[profile.release] +panic = "abort" +lto = true +opt-level = 3 +strip = true +debug = false +debug-assertions = false +rpath = false +codegen-units = 1 + +[dependencies] + +[dev-dependencies] +rwasm = { path = ".." } +wasmi = { version = "=0.31.2" } +sp1-core-executor = "4.2.0" diff --git a/benchmarks/Makefile b/benchmarks/Makefile new file mode 100644 index 000000000..329a3d2a6 --- /dev/null +++ b/benchmarks/Makefile @@ -0,0 +1,6 @@ +RUSTFLAGS="-C link-arg=-zstack-size=1024" + +build: + RUSTFLAGS="-C link-arg=-zstack-size=0" cargo b --target=wasm32-unknown-unknown --release --no-default-features + cp ./target/wasm32-unknown-unknown/release/fib.wasm ./lib.wasm + wasm2wat ./lib.wasm > ./lib.wat \ No newline at end of file diff --git a/benchmarks/README.md b/benchmarks/README.md new file mode 100644 index 000000000..cd2bb46ea --- /dev/null +++ b/benchmarks/README.md @@ -0,0 +1,16 @@ +These benchmarks are not finalized yet. +Here we test the cost of running SP1's RISC-V, Wasmi & rWasm in Ethereum execution environment. +When it's required to load the account from storage, only raw bytes. + +Current results: + +| Test | Native | Risc-V | Wasmi | rWasm | +|---------------|--------|--------|-------|-------| +| Fibonacci(43) | ~12ns | ~7ms | ~6us | ~2us | + +PS: + +1. Risc-V is slow because of tracing +2. Wasmi is slow because of Wasm validation rules + +Tested on Apple M3 MAX \ No newline at end of file diff --git a/benchmarks/bench.rs b/benchmarks/bench.rs new file mode 100644 index 000000000..3815279bd --- /dev/null +++ b/benchmarks/bench.rs @@ -0,0 +1,110 @@ +extern crate test; + +use rwasm::Caller; +use sp1_core_executor::{ExecutionState, Executor, ExecutorMode, Program}; +use test::Bencher; + +#[bench] +fn bench_wasmi(b: &mut Bencher) { + b.iter(|| { + use wasmi::{Engine, Linker, Module, Store}; + let wasm = include_bytes!("./lib.wasm"); + let engine = Engine::default(); + let module = Module::new(&engine, &wasm[..]).unwrap(); + let mut store = Store::new(&engine, ()); + let linker = >::new(&engine); + let instance = linker + .instantiate(&mut store, &module) + .unwrap() + .start(&mut store) + .unwrap(); + let _result = instance + .get_typed_func::(&store, "main") + .unwrap() + .call(&mut store, 43) + .unwrap(); + // assert_eq!(result, 433494437); + }); +} + +#[bench] +fn bench_rwasm(b: &mut Bencher) { + use rwasm::{ + legacy::{engine::RwasmConfig, Config, Engine, Module}, + ExecutorConfig, + RwasmExecutor, + RwasmModule, + }; + use std::sync::Arc; + + let wasm = include_bytes!("./lib.wasm"); + + let mut config = Config::default(); + config + .wasm_mutable_global(false) + .wasm_saturating_float_to_int(false) + .wasm_sign_extension(false) + .wasm_multi_value(false) + .wasm_mutable_global(true) + .wasm_saturating_float_to_int(true) + .wasm_sign_extension(true) + .wasm_multi_value(true) + .wasm_bulk_memory(true) + .wasm_reference_types(true) + .wasm_tail_call(true) + .wasm_extended_const(true); + config.rwasm_config(RwasmConfig { + state_router: None, + entrypoint_name: Some("main".to_string()), + import_linker: None, + wrap_import_functions: true, + translate_drop_keep: false, + allow_malformed_entrypoint_func_type: true, + use_32bit_mode: false, + builtins_consume_fuel: false, + }); + let engine = Engine::new(&config); + let wasm_module = Module::new(&engine, &wasm[..]).unwrap(); + let rwasm_module = rwasm::legacy::rwasm::RwasmModule::from_module(&wasm_module); + let mut encoded_rwasm_module = Vec::new(); + use rwasm::legacy::rwasm::BinaryFormat; + rwasm_module + .write_binary_to_vec(&mut encoded_rwasm_module) + .unwrap(); + b.iter(|| { + let rwasm_module = RwasmModule::new(&encoded_rwasm_module); + let mut vm = RwasmExecutor::new(Arc::new(rwasm_module), ExecutorConfig::default(), ()); + Caller::new(&mut vm).stack_push(43); + vm.run().unwrap(); + let _result: i32 = Caller::new(&mut vm).stack_pop_as(); + // assert_eq!(result, 433494437); + // vm.reset_pc(); + }); +} + +#[bench] +fn bench_riscv(b: &mut Bencher) { + b.iter(|| { + let elf = include_bytes!("./fibonacci-program"); + let mut executor = Executor::new(Program::from(elf).unwrap(), Default::default()); + executor.executor_mode = ExecutorMode::Trace; + executor.execute().unwrap(); + executor.state = ExecutionState::new(executor.program.pc_start); + }); +} + +#[bench] +fn bench_native(b: &mut Bencher) { + b.iter(|| { + pub fn main(n: i32) -> i32 { + let (mut a, mut b) = (0, 1); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a + } + core::hint::black_box(main(core::hint::black_box(43))); + }); +} diff --git a/benchmarks/fibonacci-program b/benchmarks/fibonacci-program new file mode 100755 index 0000000000000000000000000000000000000000..0e10bb9adeff5e0263cf7bc2d30086fb8966ca8a GIT binary patch literal 78328 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 literal 0 HcmV?d00001 diff --git a/benchmarks/greeting-elf b/benchmarks/greeting-elf new file mode 100755 index 0000000000000000000000000000000000000000..8d2f611372053c2a1f7c3a0460322f9c4aaaf998 GIT binary patch literal 112420 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 z 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 Ji9_+;{|AV3lb`?q literal 0 HcmV?d00001 diff --git a/benchmarks/keccak-elf b/benchmarks/keccak-elf new file mode 100755 index 0000000000000000000000000000000000000000..d3d18b1cd81c9a4748945112a8be58835b2d0906 GIT binary patch literal 118836 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 i32 { + let (mut a, mut b) = (0, 1); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a +} diff --git a/benchmarks/rust-toolchain b/benchmarks/rust-toolchain new file mode 100644 index 000000000..5bb1c814c --- /dev/null +++ b/benchmarks/rust-toolchain @@ -0,0 +1 @@ +nightly-2025-01-27 \ No newline at end of file diff --git a/benchmarks/test.rs b/benchmarks/test.rs new file mode 100644 index 000000000..3bcdf143e --- /dev/null +++ b/benchmarks/test.rs @@ -0,0 +1,66 @@ +use sp1_core_executor::{ExecutionState, Executor, ExecutorMode, Program}; + +#[test] +fn test_rwasm() { + use rwasm::{ + legacy::{engine::RwasmConfig, Config, Engine, Module}, + Caller, + ExecutorConfig, + RwasmExecutor, + RwasmModule, + }; + use std::sync::Arc; + + let wasm = include_bytes!("./lib.wasm"); + + let mut config = Config::default(); + config + .wasm_mutable_global(false) + .wasm_saturating_float_to_int(false) + .wasm_sign_extension(false) + .wasm_multi_value(false) + .wasm_mutable_global(true) + .wasm_saturating_float_to_int(true) + .wasm_sign_extension(true) + .wasm_multi_value(true) + .wasm_bulk_memory(true) + .wasm_reference_types(true) + .wasm_tail_call(true) + .wasm_extended_const(true); + config.rwasm_config(RwasmConfig { + state_router: None, + entrypoint_name: Some("main".to_string()), + import_linker: None, + wrap_import_functions: true, + translate_drop_keep: false, + allow_malformed_entrypoint_func_type: true, + use_32bit_mode: false, + builtins_consume_fuel: false, + }); + let engine = Engine::new(&config); + let wasm_module = Module::new(&engine, &wasm[..]).unwrap(); + let rwasm_module = rwasm::legacy::rwasm::RwasmModule::from_module(&wasm_module); + let mut encoded_rwasm_module = Vec::new(); + use rwasm::legacy::rwasm::BinaryFormat; + rwasm_module + .write_binary_to_vec(&mut encoded_rwasm_module) + .unwrap(); + let rwasm_module = RwasmModule::new(&encoded_rwasm_module); + let mut vm = RwasmExecutor::new(Arc::new(rwasm_module), ExecutorConfig::default(), ()); + + Caller::new(&mut vm).stack_push(43); + vm.run().unwrap(); + let result: i32 = Caller::new(&mut vm).stack_pop_as(); + assert_eq!(result, 433494437); + vm.reset(None); +} + +#[test] +fn test_riscv() { + let elf = include_bytes!("./keccak-elf"); + let mut executor = Executor::new(Program::from(elf).unwrap(), Default::default()); + executor.executor_mode = ExecutorMode::Simple; + executor.execute().unwrap(); + println!("global clock: {}", executor.state.global_clk); + executor.state = ExecutionState::new(executor.program.pc_start); +} From 4e2a5d48fcd4a091456752a1e681b9539e5b2385 Mon Sep 17 00:00:00 2001 From: Dmitry Savonin Date: Sun, 18 May 2025 22:58:23 +0400 Subject: [PATCH 011/141] feat(compiler): the new version of rwasm compiler --- Cargo.lock | 1 + Cargo.toml | 3 +- benchmarks/Cargo.toml | 2 +- benchmarks/Makefile | 4 +- benchmarks/bench.rs | 6 +- snippets/rwasm.rs | 4 +- src/compiler.rs | 19 + src/compiler/config.rs | 84 + src/compiler/control_flow.rs | 535 +++ src/compiler/drop_keep.rs | 88 + src/compiler/entrypoint.rs | 7 + src/compiler/error.rs | 28 + src/compiler/func_builder.rs | 205 + src/compiler/instr_loc.rs | 37 + src/compiler/labels.rs | 221 ++ src/compiler/locals_registry.rs | 71 + src/compiler/parser.rs | 731 ++++ src/compiler/segment_builder.rs | 184 + src/compiler/tests.rs | 17 + src/compiler/translator.rs | 6568 +++++++++++++++++++++++++++++++ src/compiler/utils.rs | 15 + src/compiler/value_stack.rs | 87 + src/lib.rs | 1 + src/types/block_fuel.rs | 12 +- src/types/branch_offset.rs | 12 +- src/types/compiled_expr.rs | 403 ++ src/types/drop_keep.rs | 31 +- src/types/global_variable.rs | 8 + src/types/host_error.rs | 6 +- src/types/import_linker.rs | 39 +- src/types/instruction.rs | 32 +- src/types/instruction_set.rs | 79 +- src/types/mod.rs | 7 + src/types/module.rs | 24 + src/types/untyped_value.rs | 6 +- src/types/utils.rs | 6 +- src/types/value.rs | 8 +- src/vm/instr_ptr.rs | 8 +- src/vm/instruction_table.rs | 2 +- src/vm/opcodes.rs | 2 +- src/vm/value_stack.rs | 14 +- 41 files changed, 9513 insertions(+), 104 deletions(-) create mode 100644 src/compiler/config.rs create mode 100644 src/compiler/control_flow.rs create mode 100644 src/compiler/drop_keep.rs create mode 100644 src/compiler/entrypoint.rs create mode 100644 src/compiler/error.rs create mode 100644 src/compiler/func_builder.rs create mode 100644 src/compiler/instr_loc.rs create mode 100644 src/compiler/labels.rs create mode 100644 src/compiler/locals_registry.rs create mode 100644 src/compiler/parser.rs create mode 100644 src/compiler/segment_builder.rs create mode 100644 src/compiler/tests.rs create mode 100644 src/compiler/translator.rs create mode 100644 src/compiler/utils.rs create mode 100644 src/compiler/value_stack.rs create mode 100644 src/types/compiled_expr.rs create mode 100644 src/types/global_variable.rs diff --git a/Cargo.lock b/Cargo.lock index aad49b3e2..8063466b0 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -310,6 +310,7 @@ dependencies = [ "paste", "rand", "rwasm-legacy", + "smallvec", "tiny-keccak", "wasmparser-nostd", ] diff --git a/Cargo.toml b/Cargo.toml index b09faa16b..da2e72f2e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ bincode = { version = "2.0.1", default-features = false, features = ["alloc", "d num-traits = { version = "0.2", default-features = false } bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } libm = "0.2.1" +smallvec = "1.15.0" # an rwasm legacy dependency rwasm-legacy = { path = "./legacy", default-features = false } @@ -30,7 +31,7 @@ rwasm-legacy = { path = "./legacy", default-features = false } rand = "0.9.1" [features] -default = ["std"] +default = ["std", "debug-print"] std = [ "num_enum/std", "bytes/std", diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index db9b20061..27a24f61d 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "benchmark-fib" +name = "fib" version = "0.1.0" edition = "2021" diff --git a/benchmarks/Makefile b/benchmarks/Makefile index 329a3d2a6..a60b7fa0c 100644 --- a/benchmarks/Makefile +++ b/benchmarks/Makefile @@ -1,6 +1,4 @@ -RUSTFLAGS="-C link-arg=-zstack-size=1024" - build: - RUSTFLAGS="-C link-arg=-zstack-size=0" cargo b --target=wasm32-unknown-unknown --release --no-default-features + RUSTFLAGS="-C link-arg=-zstack-size=65536" cargo b --target=wasm32-unknown-unknown --release --no-default-features cp ./target/wasm32-unknown-unknown/release/fib.wasm ./lib.wasm wasm2wat ./lib.wasm > ./lib.wat \ No newline at end of file diff --git a/benchmarks/bench.rs b/benchmarks/bench.rs index 3815279bd..b9cd8e01b 100644 --- a/benchmarks/bench.rs +++ b/benchmarks/bench.rs @@ -21,7 +21,7 @@ fn bench_wasmi(b: &mut Bencher) { let _result = instance .get_typed_func::(&store, "main") .unwrap() - .call(&mut store, 43) + .call(&mut store, 47) .unwrap(); // assert_eq!(result, 433494437); }); @@ -74,7 +74,7 @@ fn bench_rwasm(b: &mut Bencher) { b.iter(|| { let rwasm_module = RwasmModule::new(&encoded_rwasm_module); let mut vm = RwasmExecutor::new(Arc::new(rwasm_module), ExecutorConfig::default(), ()); - Caller::new(&mut vm).stack_push(43); + Caller::new(&mut vm).stack_push(47); vm.run().unwrap(); let _result: i32 = Caller::new(&mut vm).stack_pop_as(); // assert_eq!(result, 433494437); @@ -105,6 +105,6 @@ fn bench_native(b: &mut Bencher) { } a } - core::hint::black_box(main(core::hint::black_box(43))); + core::hint::black_box(main(core::hint::black_box(47))); }); } diff --git a/snippets/rwasm.rs b/snippets/rwasm.rs index 902ae5304..7ef9daf6a 100644 --- a/snippets/rwasm.rs +++ b/snippets/rwasm.rs @@ -109,7 +109,7 @@ pub fn trace_execution_logs(vm: &RwasmExecutor<()>) { } } -fn exec_instrs(mut is: InstructionSet, inputs: Vec) -> Vec { +fn run_vm_instr(mut is: InstructionSet, inputs: Vec) -> Vec { is.op_return(DropKeep::none()); let rwasm_module = RwasmModule::with_one_function(is); let mut vm = RwasmExecutor::new( @@ -136,7 +136,7 @@ fn test_rwasm_i64_mul() { let test_case_u64 = |a: u64, b: u64| { let c = a.wrapping_mul(b); - let output = exec_instrs( + let output = run_vm_instr( is.clone(), vec![a as u32, (a >> 32) as u32, b as u32, (b >> 32) as u32], ); diff --git a/src/compiler.rs b/src/compiler.rs index 23d31b548..7947db039 100644 --- a/src/compiler.rs +++ b/src/compiler.rs @@ -1,3 +1,22 @@ +mod config; +mod control_flow; +mod drop_keep; +mod entrypoint; +mod error; +mod func_builder; +mod instr_loc; +mod labels; +mod locals_registry; +mod parser; +mod segment_builder; +#[cfg(test)] +mod tests; +mod translator; +mod utils; +mod value_stack; + +pub use self::{config::CompilationConfig, error::CompilationError}; +use alloc::{vec, vec::Vec}; use rwasm_legacy::{ engine::RwasmConfig, rwasm::{BinaryFormat, BinaryFormatWriter, RwasmModule}, diff --git a/src/compiler/config.rs b/src/compiler/config.rs new file mode 100644 index 000000000..d9d1c8959 --- /dev/null +++ b/src/compiler/config.rs @@ -0,0 +1,84 @@ +use crate::{ImportLinker, Opcode, OpcodeData}; +use wasmparser::WasmFeatures; + +#[derive(Debug, Clone)] +pub struct StateRouterConfig { + /// List of states to be router based on the state + pub states: Box<[(Box, u32)]>, + /// Instruction that describes how we determine an input state + pub opcode: (Opcode, OpcodeData), +} + +#[derive(Default, Clone)] +pub struct CompilationConfig { + /// State router is used to choose one of the function based on the index provided. + /// P.S: this flag doesn't work if you have WASM's start entry point. + pub state_router: Option, + /// Entrypoint that stores bytecode for module init. + /// P.S: this flag doesn't work if you have WASM's start entry point. + pub entrypoint_name: Option>, + /// Import linker that stores mapping from function to special identifiers that is used + /// to remember unique external calls ids. We need this to simplify a proving process to + /// forward external calls to corresponding circuits. + pub import_linker: Option, + /// Do we need to wrap input functions to convert them from ExternRef to FuncRef (we need it to + /// simplify tables sometimes)? It's necessary only for rWASM mode where we replace all + /// external calls with import linker mapping. + pub wrap_import_functions: bool, + /// An option to disable malformed entrypoint func type check. We need this check for e2e tests + /// where we manage stack manually. + pub allow_malformed_entrypoint_func_type: bool, + /// Should fuel-charging instructions be injected before each builtin call. + pub builtins_consume_fuel: bool, + /// A boolean field that indicates whether floating-point operations are enabled. It's used + /// only for running WebAssembly testing suite to make sure rWasm fully satisfies WebAssembly + /// standards. + pub enable_floating_point: bool, +} + +impl CompilationConfig { + /// Returns the WebAssembly features configuration for the current instance. + pub fn wasm_features(&self) -> WasmFeatures { + let mut wasm_features = WasmFeatures::default(); + wasm_features.floats = self.enable_floating_point; + wasm_features + } + + pub fn with_state_router(mut self, state_router: StateRouterConfig) -> Self { + self.state_router = Some(state_router); + self + } + + pub fn with_entrypoint_name(mut self, name: Box) -> Self { + self.entrypoint_name = Some(name); + self + } + + pub fn with_import_linker(mut self, import_linker: ImportLinker) -> Self { + self.import_linker = Some(import_linker); + self + } + + pub fn with_wrap_import_functions(mut self, wrap_import_functions: bool) -> Self { + self.wrap_import_functions = wrap_import_functions; + self + } + + pub fn with_allow_malformed_entrypoint_func_type( + mut self, + allow_malformed_entrypoint_func_type: bool, + ) -> Self { + self.allow_malformed_entrypoint_func_type = allow_malformed_entrypoint_func_type; + self + } + + pub fn with_builtins_consume_fuel(mut self, builtins_consume_fuel: bool) -> Self { + self.builtins_consume_fuel = builtins_consume_fuel; + self + } + + pub fn with_enable_floating_point(mut self, enable_floating_point: bool) -> Self { + self.enable_floating_point = enable_floating_point; + self + } +} diff --git a/src/compiler/control_flow.rs b/src/compiler/control_flow.rs new file mode 100644 index 000000000..45c7efac4 --- /dev/null +++ b/src/compiler/control_flow.rs @@ -0,0 +1,535 @@ +use crate::compiler::{instr_loc::InstrLoc, labels::LabelRef}; +use alloc::vec::Vec; +use wasmparser::BlockType; + +/// The stack of control flow frames. +#[derive(Debug, Default)] +pub struct ControlFlowStack { + frames: Vec, +} + +impl ControlFlowStack { + /// Resets the [`ControlFlowStack`] to allow for reuse. + pub fn reset(&mut self) { + self.frames.clear() + } + + /// Returns `true` if `relative_depth` points to the first control flow frame. + pub fn is_root(&self, relative_depth: u32) -> bool { + debug_assert!(!self.is_empty()); + relative_depth as usize == self.len() - 1 + } + + /// Returns the current depth of the stack of the [`ControlFlowStack`]. + pub fn len(&self) -> usize { + self.frames.len() + } + + /// Returns `true` if the [`ControlFlowStack`] is empty. + pub fn is_empty(&self) -> bool { + self.frames.len() == 0 + } + + /// Pushes a new control flow frame to the [`ControlFlowStack`]. + pub fn push_frame(&mut self, frame: T) + where + T: Into, + { + self.frames.push(frame.into()) + } + + /// Pops the last control flow frame from the [`ControlFlowStack`]. + /// + /// # Panics + /// + /// If the [`ControlFlowStack`] is empty. + pub fn pop_frame(&mut self) -> ControlFrame { + self.frames + .pop() + .expect("tried to pop the control flow frame from the empty control flow stack") + } + + /// Returns the last control flow frame on the control stack. + pub fn last(&self) -> &ControlFrame { + self.frames.last().expect( + "tried to exclusively peek the last control flow \ + frame from an empty control flow stack", + ) + } + + /// Returns a shared reference to the control flow frame at the given `depth`. + /// + /// A `depth` of 0 is equal to calling [`ControlFlowStack::last`]. + /// + /// # Panics + /// + /// If `depth` exceeds the length of the stack of control flow frames. + pub fn nth_back(&self, depth: u32) -> &ControlFrame { + let len = self.len(); + self.frames + .iter() + .nth_back(depth as usize) + .unwrap_or_else(|| { + panic!( + "tried to peek the {depth}-th control flow frame, but there are only {len} control flow frames", + ) + }) + } +} + +/// A Wasm `block` control flow frame. +#[derive(Debug, Copy, Clone)] +pub struct BlockControlFrame { + /// The type of the [`BlockControlFrame`]. + block_type: BlockType, + /// The value stack height upon entering the + /// [`BlockControlFrame`]. + stack_height: u32, + /// Label representing the end of the + /// [`BlockControlFrame`]. + end_label: LabelRef, + /// Instruction to consume fuel upon entering the basic block if fuel metering is enabled. + /// + /// # Note + /// + /// This might be a reference to the consume fuel instruction of the parent + /// [`ControlFrame`] of the + /// [`BlockControlFrame`]. + consume_fuel: Option, +} + +impl BlockControlFrame { + /// Creates a new [`BlockControlFrame`]. + pub fn new( + block_type: BlockType, + end_label: LabelRef, + stack_height: u32, + consume_fuel: Option, + ) -> Self { + Self { + block_type, + stack_height, + end_label, + consume_fuel, + } + } + + /// Returns the label for the branch destination of the + /// [`BlockControlFrame`]. + /// + /// # Note + /// + /// Branches to [`BlockControlFrame`] jump to the + /// end of the frame. + pub fn branch_destination(&self) -> LabelRef { + self.end_label + } + + /// Returns the label to the end of the + /// [`BlockControlFrame`]. + pub fn end_label(&self) -> LabelRef { + self.end_label + } + + /// Returns the value stack height upon entering the + /// [`BlockControlFrame`]. + pub fn stack_height(&self) -> u32 { + self.stack_height + } + + /// Returns the [`BlockType`] of the + /// [`BlockControlFrame`]. + pub fn block_type(&self) -> BlockType { + self.block_type + } + + /// Returns a reference to the [`ConsumeFuel`] instruction of the + /// [`BlockControlFrame`] if any. + /// + /// Returns `None` if fuel metering is disabled. + /// + /// # Note + /// + /// A [`BlockControlFrame`] might share its + /// [`ConsumeFuel`] instruction with its child + /// [`BlockControlFrame`]. + /// + /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel + pub fn consume_fuel_instr(&self) -> Option { + self.consume_fuel + } +} + +/// A Wasm `loop` control flow frame. +#[derive(Debug, Copy, Clone)] +pub struct LoopControlFrame { + /// The type of the [`LoopControlFrame`]. + block_type: BlockType, + /// The value stack height upon entering the + /// [`LoopControlFrame`]. + stack_height: u32, + /// Label representing the head of the + /// [`LoopControlFrame`]. + head_label: LabelRef, + /// Instruction to consume fuel upon entering the basic block if fuel metering is enabled. + /// + /// # Note + /// + /// This must be `Some` if fuel metering is enabled and `None` otherwise. + consume_fuel: Option, +} + +impl LoopControlFrame { + /// Creates a new [`LoopControlFrame`]. + pub fn new( + block_type: BlockType, + head_label: LabelRef, + stack_height: u32, + consume_fuel: Option, + ) -> Self { + Self { + block_type, + stack_height, + head_label, + consume_fuel, + } + } + + /// Returns the label for the branch destination of the + /// [`LoopControlFrame`]. + /// + /// # Note + /// + /// Branches to [`LoopControlFrame`] jump to the + /// head of the loop. + pub fn branch_destination(&self) -> LabelRef { + self.head_label + } + + /// Returns the value stack height upon entering the + /// [`LoopControlFrame`]. + pub fn stack_height(&self) -> u32 { + self.stack_height + } + + /// Returns the [`BlockType`] of the + /// [`LoopControlFrame`]. + pub fn block_type(&self) -> BlockType { + self.block_type + } + + /// Returns a reference to the [`ConsumeFuel`] instruction of the + /// [`BlockControlFrame`] if any. + /// + /// Returns `None` if fuel metering is disabled. + /// + /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel + pub fn consume_fuel_instr(&self) -> Option { + self.consume_fuel + } +} + +/// A Wasm `if` and `else` control flow frames. +#[derive(Debug, Copy, Clone)] +pub struct IfControlFrame { + /// The type of the [`IfControlFrame`]. + block_type: BlockType, + /// The value stack height upon entering the + /// [`IfControlFrame`]. + stack_height: u32, + /// Label representing the end of the + /// [`IfControlFrame`]. + end_label: LabelRef, + /// Label representing the optional `else` branch of the + /// [`IfControlFrame`]. + else_label: LabelRef, + /// End of `then` branch is reachable. + /// + /// # Note + /// + /// - This is `None` upon entering the `if` control flow frame. Once the optional `else` case + /// or the `end` of the `if` control flow frame is reached this field will be computed. + /// - This information is important to know how to continue after a diverging `if` control flow + /// frame. + /// - An `end_of_else_is_reachable` field is not needed since it will be easily computed once + /// the translation reaches the end of the `if`. + end_of_then_is_reachable: Option, + /// Instruction to consume fuel upon entering the basic block if fuel metering is enabled. + /// + /// This is used for both `then` and `else` blocks. When entering the `else` + /// block this field is updated to represent the [`ConsumeFuel`] instruction + /// of the `else` block instead of the `then` block. This is possible because + /// only one of them is needed at the same time during translation. + /// + /// # Note + /// + /// This must be `Some` if fuel metering is enabled and `None` otherwise. + /// + /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel + consume_fuel: Option, +} + +impl IfControlFrame { + /// Creates a new [`IfControlFrame`]. + pub fn new( + block_type: BlockType, + end_label: LabelRef, + else_label: LabelRef, + stack_height: u32, + consume_fuel: Option, + ) -> Self { + assert_ne!( + end_label, else_label, + "end and else labels must be different" + ); + Self { + block_type, + stack_height, + end_label, + else_label, + end_of_then_is_reachable: None, + consume_fuel, + } + } + + /// Returns the label for the branch destination of the + /// [`IfControlFrame`]. + /// + /// # Note + /// + /// Branches to [`IfControlFrame`] jump to the end + /// of the if and else frame. + pub fn branch_destination(&self) -> LabelRef { + self.end_label + } + + /// Returns the label to the end of the + /// [`IfControlFrame`]. + pub fn end_label(&self) -> LabelRef { + self.end_label + } + + /// Returns the label to the optional `else` of the + /// [`IfControlFrame`]. + pub fn else_label(&self) -> LabelRef { + self.else_label + } + + /// Returns the value stack height upon entering the + /// [`IfControlFrame`]. + pub fn stack_height(&self) -> u32 { + self.stack_height + } + + /// Returns the [`BlockType`] of the + /// [`IfControlFrame`]. + pub fn block_type(&self) -> BlockType { + self.block_type + } + + /// Updates the reachability of the end of the `then` branch. + /// + /// # Panics + /// + /// If this information has already been provided prior. + pub fn update_end_of_then_reachability(&mut self, reachable: bool) { + assert!(self.end_of_then_is_reachable.is_none()); + self.end_of_then_is_reachable = Some(reachable); + } + + /// Returns a reference to the [`ConsumeFuel`] instruction of the + /// [`BlockControlFrame`] if any. + /// + /// Returns `None` if fuel metering is disabled. + /// + /// # Note + /// + /// This returns the [`ConsumeFuel`] instruction for both `then` and `else` blocks. + /// When entering the `if` block it represents the [`ConsumeFuel`] instruction until + /// the `else` block entered. This is possible because only one of them is needed + /// at the same time during translation. + /// + /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel + pub fn consume_fuel_instr(&self) -> Option { + self.consume_fuel + } + + /// Updates the [`ConsumeFuel`] instruction for when the `else` block is entered. + /// + /// # Note + /// + /// This is required since the `consume_fuel` field represents the [`ConsumeFuel`] + /// instruction for both `then` and `else` blocks. This is possible because only one + /// of them is needed at the same time during translation. + /// + /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel + pub fn update_consume_fuel_instr(&mut self, instr: InstrLoc) { + assert!( + self.consume_fuel.is_some(), + "can only update the consumption fuel instruction if it existed before" + ); + self.consume_fuel = Some(instr); + } +} + +/// An unreachable control flow frame of any kind. +#[derive(Debug, Copy, Clone)] +pub struct UnreachableControlFrame { + /// The non-SSA input and output types of the unreachable control frame. + pub block_type: BlockType, + /// The kind of the unreachable control flow frame. + pub kind: ControlFrameKind, +} + +/// The kind of control flow frame. +#[derive(Debug, Copy, Clone)] +pub enum ControlFrameKind { + /// A basic `block` control flow frame. + Block, + /// A `loop` control flow frame. + Loop, + /// An `if` and `else` block control flow frame. + If, +} + +impl UnreachableControlFrame { + /// Creates a new [`UnreachableControlFrame`] with + /// the given type and kind. + pub fn new(kind: ControlFrameKind, block_type: BlockType) -> Self { + Self { block_type, kind } + } + + /// Returns the [`ControlFrameKind`] of the + /// [`UnreachableControlFrame`]. + pub fn kind(&self) -> ControlFrameKind { + self.kind + } + + /// Returns the [`BlockType`] of the + /// [`IfControlFrame`]. + pub fn block_type(&self) -> BlockType { + self.block_type + } +} + +/// A control flow frame. +#[derive(Debug, Copy, Clone)] +pub enum ControlFrame { + /// Basic block control frame. + Block(BlockControlFrame), + /// Loop control frame. + Loop(LoopControlFrame), + /// If and else control frame. + If(IfControlFrame), + /// An unreachable control frame. + Unreachable(UnreachableControlFrame), +} + +impl From for ControlFrame { + fn from(frame: BlockControlFrame) -> Self { + Self::Block(frame) + } +} + +impl From for ControlFrame { + fn from(frame: LoopControlFrame) -> Self { + Self::Loop(frame) + } +} + +impl From for ControlFrame { + fn from(frame: IfControlFrame) -> Self { + Self::If(frame) + } +} + +impl From for ControlFrame { + fn from(frame: UnreachableControlFrame) -> Self { + Self::Unreachable(frame) + } +} + +impl ControlFrame { + /// Returns the [`ControlFrameKind`] of the + /// [`ControlFrame`]. + pub fn kind(&self) -> ControlFrameKind { + match self { + ControlFrame::Block(_) => ControlFrameKind::Block, + ControlFrame::Loop(_) => ControlFrameKind::Loop, + ControlFrame::If(_) => ControlFrameKind::If, + ControlFrame::Unreachable(frame) => frame.kind(), + } + } + + /// Returns the label for the branch destination of the + /// [`ControlFrame`]. + pub fn branch_destination(&self) -> LabelRef { + match self { + Self::Block(frame) => frame.branch_destination(), + Self::Loop(frame) => frame.branch_destination(), + Self::If(frame) => frame.branch_destination(), + Self::Unreachable(frame) => panic!( + "tried to get `branch_destination` for an unreachable control frame: {:?}", + frame + ), + } + } + + /// Returns a label which should be resolved at the `End` Wasm opcode. + /// + /// All [`ControlFrame`] kinds have it except [`ControlFrame::Loop`]. + /// To a [`ControlFrame::Loop`] to branch outside, it is required to be wrapped in another + /// control frame such as [`ControlFrame::Block`]. + pub fn end_label(&self) -> LabelRef { + match self { + Self::Block(frame) => frame.end_label(), + Self::If(frame) => frame.end_label(), + Self::Loop(frame) => { + panic!("tried to get `end_label` for a loop control frame: {frame:?}") + } + Self::Unreachable(frame) => { + panic!("tried to get `end_label` for an unreachable control frame: {frame:?}") + } + } + } + + /// Returns the value stack height upon entering the control flow frame. + pub fn stack_height(&self) -> Option { + match self { + Self::Block(frame) => Some(frame.stack_height()), + Self::Loop(frame) => Some(frame.stack_height()), + Self::If(frame) => Some(frame.stack_height()), + Self::Unreachable(_frame) => None, + } + } + + /// Returns the [`BlockType`] of the control flow frame. + pub fn block_type(&self) -> BlockType { + match self { + Self::Block(frame) => frame.block_type(), + Self::Loop(frame) => frame.block_type(), + Self::If(frame) => frame.block_type(), + Self::Unreachable(frame) => frame.block_type(), + } + } + + /// Returns `true` if the control flow frame is reachable. + pub fn is_reachable(&self) -> bool { + !matches!(self, ControlFrame::Unreachable(_)) + } + + /// Returns a reference to the [`ConsumeFuel`] instruction of the + /// [`ControlFrame`] if any. + /// + /// Returns `None` if fuel metering is disabled. + /// + /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel + pub fn consume_fuel_instr(&self) -> Option { + match self { + ControlFrame::Block(frame) => frame.consume_fuel_instr(), + ControlFrame::Loop(frame) => frame.consume_fuel_instr(), + ControlFrame::If(frame) => frame.consume_fuel_instr(), + ControlFrame::Unreachable(_) => None, + } + } +} diff --git a/src/compiler/drop_keep.rs b/src/compiler/drop_keep.rs new file mode 100644 index 000000000..c1172eea8 --- /dev/null +++ b/src/compiler/drop_keep.rs @@ -0,0 +1,88 @@ +use crate::{compiler::value_stack::ValueStackHeight, DropKeep, InstructionSet}; + +pub fn translate_drop_keep( + instr_builder: &mut InstructionSet, + drop_keep: DropKeep, + height: &mut ValueStackHeight, +) -> usize { + let (drop, keep) = (drop_keep.drop(), drop_keep.keep()); + if drop == 0 { + return 0; + } + let mut opcode_count = 0; + if drop >= keep { + (0..keep).for_each(|_| { + instr_builder.op_local_set(drop as u32); + opcode_count += 1; + }); + (0..(drop - keep)).for_each(|_| { + instr_builder.op_drop(); + opcode_count += 1; + }); + } else { + height.push(); + height.pop1(); + (0..keep).for_each(|i| { + instr_builder.op_local_get(keep as u32 - i as u32); + instr_builder.op_local_set(keep as u32 + drop as u32 - i as u32); + opcode_count += 2; + }); + (0..drop).for_each(|_| { + instr_builder.op_drop(); + opcode_count += 1; + }); + } + opcode_count +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::{Opcode, OpcodeData}; + + #[test] + fn test_drop_keep_translation() { + macro_rules! drop_keep { + ($drop:literal, $keep:literal) => { + DropKeep::new($drop, $keep).unwrap() + }; + } + let tests = vec![ + (vec![100, 20, 120], vec![120], drop_keep!(2, 1)), + (vec![1, 2], vec![1, 2], drop_keep!(0, 0)), + (vec![1, 2, 3], vec![1, 2, 3], drop_keep!(0, 3)), + (vec![1, 2, 3, 4], vec![3, 4], drop_keep!(2, 2)), + (vec![2, 3, 7], vec![3, 7], drop_keep!(1, 2)), + (vec![1, 2, 3, 4, 5, 6], vec![3, 4, 5, 6], drop_keep!(2, 4)), + (vec![7, 100, 20, 3], vec![7], drop_keep!(3, 0)), + (vec![100, 20, 120], vec![120], drop_keep!(2, 1)), + (vec![1, 2, 3, 4, 5], vec![5], drop_keep!(4, 1)), + ]; + for (input, output, drop_keep) in tests.iter() { + let mut instr_builder = InstructionSet::new(); + let mut stack_height = ValueStackHeight::default(); + translate_drop_keep(&mut instr_builder, *drop_keep, &mut stack_height); + let mut stack = input.clone(); + for instr in instr_builder.instr { + match instr { + (Opcode::LocalSet, OpcodeData::LocalDepth(index)) => { + let last = stack.last().unwrap(); + let len = stack.len(); + *stack.get_mut(len - 1 - index.to_usize()).unwrap() = *last; + stack.pop(); + } + (Opcode::LocalGet, OpcodeData::LocalDepth(index)) => { + let len = stack.len(); + let item = *stack.get(len - index.to_usize()).unwrap(); + stack.push(item); + } + (Opcode::Drop, _) => { + stack.pop(); + } + _ => unreachable!("unknown opcode: {:?}", instr), + } + } + assert_eq!(stack, *output); + } + } +} diff --git a/src/compiler/entrypoint.rs b/src/compiler/entrypoint.rs new file mode 100644 index 000000000..9f32f01ce --- /dev/null +++ b/src/compiler/entrypoint.rs @@ -0,0 +1,7 @@ +use crate::compiler::translator::InstructionTranslator; + +pub struct EntrypointTranslator { + pub translator: InstructionTranslator, +} + +impl EntrypointTranslator {} diff --git a/src/compiler/error.rs b/src/compiler/error.rs new file mode 100644 index 000000000..152a1caa5 --- /dev/null +++ b/src/compiler/error.rs @@ -0,0 +1,28 @@ +use wasmparser::BinaryReaderError; + +#[derive(Debug)] +pub enum CompilationError { + BranchOffsetOutOfBounds, + BlockFuelOutOfBounds, + NotSupportedExtension, + DropKeepOutOfBounds, + BranchTableTargetsOutOfBounds, + MalformedWasmBinary(BinaryReaderError), + NotSupportedImportType, + NotSupportedExportType, + UnresolvedImportFunction, + MalformedImportFunctionType, + NonDefaultMemoryIndex, + ConstEvaluationFailed, + NotSupportedLocalType, + NotSupportedGlobalType, + MemorySegmentsOverflow, + MissingEntrypoint, + MalformedFuncType, +} + +impl From for CompilationError { + fn from(err: BinaryReaderError) -> Self { + CompilationError::MalformedWasmBinary(err) + } +} diff --git a/src/compiler/func_builder.rs b/src/compiler/func_builder.rs new file mode 100644 index 000000000..5e69029e8 --- /dev/null +++ b/src/compiler/func_builder.rs @@ -0,0 +1,205 @@ +use crate::{ + compiler::translator::{FuncTranslatorAllocations, InstructionTranslator, ReusableAllocations}, + CompilationError, + FuncIdx, + StackAlloc, +}; +use core::mem::take; +use wasmparser::{ + BinaryReaderError, + FuncValidator, + FunctionBody, + ValType, + ValidatorResources, + VisitOperator, +}; + +pub struct FuncBuilder<'a> { + pub(crate) func_body: FunctionBody<'a>, + pub(crate) validator: FuncValidator, + pub(crate) func_idx: FuncIdx, + pub(crate) translator: InstructionTranslator, + pub(crate) pos: usize, +} + +impl<'a> FuncBuilder<'a> { + pub fn new( + func_body: FunctionBody<'a>, + validator: FuncValidator, + func_idx: FuncIdx, + allocations: FuncTranslatorAllocations, + ) -> Self { + Self { + func_body, + validator, + func_idx, + translator: InstructionTranslator::new(allocations), + pos: 0, + } + } + + pub fn translate(mut self) -> Result { + self.translator.prepare(self.func_idx)?; + // emit special opcodes before the beginning of the function + self.translate_signature_check(); + self.translate_stack_alloc(); + self.translate_locals()?; + let offset = self.translate_operators()?; + self.validator.finish(offset)?; + self.translator.finish()?; + Ok(ReusableAllocations { + translation: take(&mut self.translator.alloc), + validation: self.validator.into_allocations(), + }) + } + + fn translate_locals(&mut self) -> Result<(), CompilationError> { + // translate locals + let mut locals_reader = self.func_body.get_locals_reader()?; + let locals_count = locals_reader.get_count() as usize; + for _ in 0..locals_count { + let offset = locals_reader.original_position(); + let (amount, value_type) = locals_reader.read()?; + + self.validator.define_locals(offset, amount, value_type)?; + match value_type { + ValType::I32 | ValType::I64 => {} + // TODO(dmitry123): "make sure this type is not allowed with floats disabled" + ValType::F32 | ValType::F64 => {} + ValType::V128 => return Err(CompilationError::NotSupportedLocalType), + ValType::FuncRef => {} + _ => return Err(CompilationError::NotSupportedLocalType), + } + + for _ in 0..amount as usize { + self.translator.alloc.instruction_set.op_i32_const(0); + // for i64 type, we need to push 2 values on the stack + if value_type == ValType::I64 { + self.translator.alloc.instruction_set.op_i32_const(0); + } + self.translator.alloc.stack_types.push(value_type); + } + + self.translator.stack_height.push_n(amount); + if value_type == ValType::I64 { + self.translator.stack_height.push_n(amount); + } + } + + // we exclude i64 locals from this check to satisfy wasm fuel calculation policy + let validated_locals = self.validator.len_locals(); + self.translator.bump_fuel_consumption( + self.translator + .fuel_costs + .fuel_for_locals(u64::from(validated_locals)), + )?; + Ok(()) + } + + fn translate_signature_check(&mut self) { + let func_type_idx = self.translator.alloc.resolve_func_type_index(self.func_idx); + self.translator + .alloc + .instruction_set + .op_signature_check(func_type_idx); + } + + fn translate_stack_alloc(&mut self) { + self.translator + .alloc + .instruction_set + .op_stack_check(StackAlloc { + // we use `u32::MAX` here because we replace it with + // the final calculated value later + max_stack_height: u32::MAX, + }); + } + + /// Translates the Wasm operators of the Wasm function. + /// + /// Returns the offset of the `End` Wasm operator. + fn translate_operators(&mut self) -> Result { + let mut reader = self.func_body.get_operators_reader()?; + while !reader.eof() { + self.pos = reader.original_position(); + reader.visit_operator(self)??; + } + reader.ensure_end()?; + Ok(reader.original_position()) + } + + /// Translates into `rwasm` bytecode if the current code path is reachable. + fn validate_then_translate( + &mut self, + validate: V, + translate: T, + ) -> Result<(), CompilationError> + where + V: FnOnce(&mut FuncValidator) -> Result<(), BinaryReaderError>, + T: FnOnce(&mut InstructionTranslator) -> Result<(), CompilationError>, + { + validate(&mut self.validator)?; + translate(&mut self.translator)?; + Ok(()) + } +} + +macro_rules! impl_visit_operator { + ( @mvp BrTable { $arg:ident: $argty:ty } => $visit:ident $($rest:tt)* ) => { + // We need to special case the `BrTable` operand since its + // arguments (a.k.a. `BrTable<'a>`) are not `Copy` which all + // the other impls make use of. + fn $visit(&mut self, $arg: $argty) -> Self::Output { + let offset = self.pos; + let arg_cloned = $arg.clone(); + self.validate_then_translate( + |validator| validator.visitor(offset).$visit(arg_cloned), + |translator| translator.$visit($arg), + ) + } + impl_visit_operator!($($rest)*); + }; + ( @mvp $($rest:tt)* ) => { + impl_visit_operator!(@@supported $($rest)*); + }; + ( @sign_extension $($rest:tt)* ) => { + impl_visit_operator!(@@supported $($rest)*); + }; + ( @saturating_float_to_int $($rest:tt)* ) => { + impl_visit_operator!(@@supported $($rest)*); + }; + ( @bulk_memory $($rest:tt)* ) => { + impl_visit_operator!(@@supported $($rest)*); + }; + ( @reference_types $($rest:tt)* ) => { + impl_visit_operator!(@@supported $($rest)*); + }; + ( @tail_call $($rest:tt)* ) => { + impl_visit_operator!(@@supported $($rest)*); + }; + ( @@supported $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $($rest:tt)* ) => { + fn $visit(&mut self $($(,$arg: $argty)*)?) -> Self::Output { + let offset = self.pos; + self.validate_then_translate( + |v| v.visitor(offset).$visit($($($arg),*)?), + |t| t.$visit($($($arg),*)?), + ) + } + impl_visit_operator!($($rest)*); + }; + ( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident $($rest:tt)* ) => { + // Wildcard match arm for all the other (yet) unsupported Wasm proposals. + fn $visit(&mut self $($(, $arg: $argty)*)?) -> Self::Output { + let offset = self.pos; + self.validator.visitor(offset).$visit($($($arg),*)?).map_err(::core::convert::Into::into) + } + impl_visit_operator!($($rest)*); + }; + () => {}; +} + +impl<'a> VisitOperator<'a> for FuncBuilder<'a> { + type Output = Result<(), CompilationError>; + + wasmparser::for_each_operator!(impl_visit_operator); +} diff --git a/src/compiler/instr_loc.rs b/src/compiler/instr_loc.rs new file mode 100644 index 000000000..0e8f680cc --- /dev/null +++ b/src/compiler/instr_loc.rs @@ -0,0 +1,37 @@ +/// A reference to an instruction of the partially +/// constructed function body of the [`InstructionsBuilder`]. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct InstrLoc(u32); + +impl InstrLoc { + /// Creates an [`rwasm_legacy::engine::Instr`] from the given `usize` value. + /// + /// # Note + /// + /// This intentionally is an API intended for test purposes only. + /// + /// # Panics + /// + /// If the `value` exceeds limitations for [`rwasm_legacy::engine::Instr`]. + pub fn from_usize(value: usize) -> Self { + let value = value.try_into().unwrap_or_else(|error| { + panic!("invalid index {value} for instruction reference: {error}") + }); + Self(value) + } + + /// Returns a ` usize ` representation of the instruction index. + pub fn into_usize(self) -> usize { + self.0 as usize + } + + /// Creates an [`rwasm_legacy::engine::Instr`] form the given `u32` value. + pub fn from_u32(value: u32) -> Self { + Self(value) + } + + /// Returns an `u32` representation of the instruction index. + pub fn into_u32(self) -> u32 { + self.0 + } +} diff --git a/src/compiler/labels.rs b/src/compiler/labels.rs new file mode 100644 index 000000000..058f8227d --- /dev/null +++ b/src/compiler/labels.rs @@ -0,0 +1,221 @@ +use crate::{ + compiler::{error::CompilationError, instr_loc::InstrLoc}, + BranchOffset, +}; +use alloc::vec::Vec; +use core::{ + fmt::{self, Display}, + slice::Iter as SliceIter, +}; + +/// A label during the `rwasm` compilation process. +#[derive(Debug, Copy, Clone)] +pub enum Label { + /// The label has already been pinned to a particular [`Instr`]. + Pinned(InstrLoc), + /// The label is still unpinned. + Unpinned, +} + +/// A reference to an [`Label`]. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct LabelRef(u32); + +impl LabelRef { + /// Returns the `usize` value of the [`LabelRef`]. + #[inline] + pub(crate) fn into_usize(self) -> usize { + self.0 as usize + } + + pub(crate) fn new(label: u32) -> LabelRef { + LabelRef(label) + } +} + +/// The label registry. +/// +/// Allows allocating new labels to pin them and resolve pinned ones. +#[derive(Debug, Default)] +pub struct LabelRegistry { + labels: Vec