diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index c82ead641..956781a36 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -12,7 +12,7 @@ jobs: if: github.event_name == 'push' runs-on: ubuntu-amd64-8core steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 - name: Run Criterion (save baseline "devel") @@ -40,14 +40,14 @@ jobs: contents: read pull-requests: write # needed to comment on PRs (won’t work for forks; see note below) steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - uses: dtolnay/rust-toolchain@stable - uses: Swatinem/rust-cache@v2 # Try to fetch the latest successful baseline from devel - name: Download baseline from devel id: dl - uses: dawidd6/action-download-artifact@v3 + uses: dawidd6/action-download-artifact@v11 continue-on-error: true # allow first PRs without a baseline with: workflow: bench.yml diff --git a/Cargo.lock b/Cargo.lock index 27973d9bc..1fb4317b3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -139,9 +139,9 @@ dependencies = [ [[package]] name = "cfg-if" -version = "1.0.0" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" [[package]] name = "cobs" @@ -457,6 +457,12 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "foldhash" version = "0.1.5" @@ -1105,11 +1111,14 @@ dependencies = [ "bincode", "bitvec", "bytes", + "cfg-if", "directories", "downcast-rs", + "fnv", "futures", "hashbrown", "hex-literal", + "libc", "libm", "num-derive", "num-traits", diff --git a/Cargo.toml b/Cargo.toml index ca1b6155f..8f33a3e31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,7 @@ [package] name = "rwasm" edition = "2021" -authors = [ - "Dmitry Savonin ", -] +authors = ["Dmitry Savonin "] repository = "https://github.com/fluentlabs-xyz/rwasm" readme = "README.md" license = "MIT" @@ -16,28 +14,45 @@ hashbrown = { version = "0.15.2", features = ["alloc"] } tiny-keccak = { version = "2.0.2", features = ["keccak"] } paste = { version = "1.0", 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"] } +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" smallvec = "1.15.0" num-derive = "0.4.2" spin = "0.10.0" +fnv = { version = "1.0.7", default-features = false } # tracing serde = { version = "1.0.219", features = ["derive"], optional = true } # wasmtime -wasmtime = { git = "https://github.com/fluentlabs-xyz/wasmtime", branch = "devel", optional = true, features = ["disable-fpu", "cache"] } -#wasmtime = { path = "../wasmtime/crates/wasmtime", optional = true, features = ["disable-fpu"] } +wasmtime = { git = "https://github.com/fluentlabs-xyz/wasmtime", branch = "devel", optional = true, features = [ + "disable-fpu", + "cache", +] } +#wasmtime = { path = "../wasmtime/crates/wasmtime", optional = true, features = ["disable-fpu", "cache"] } anyhow = { version = "1.0.98", default-features = false, optional = true } directories = { version = "6.0.0", optional = true } futures = { version = "0.3.31", optional = true } +cfg-if = "1.0.4" # wasmi wasmi = { version = "0.47.0", default-features = false } +# unix-memory +libc = { version = "0.2.172", default-features = false, features = [ + "align", + "extra_traits", + "const-extern-fn", +], optional = true } + [dev-dependencies] rand = "0.9.1" wat = "1.230.0" @@ -51,14 +66,21 @@ std = [ "num-traits/std", "bitvec/std", "wasmtime?/std", + "unix-memory", + "pooling-allocator", ] more-max-pages = [] -serde = [ - "dep:serde", "serde/derive" -] +serde = ["dep:serde", "serde/derive"] tracing = ["serde"] -debug-print = [] +test-build = [] +debug-print = ["std"] fpu = [] wasmtime = ["dep:wasmtime", "dep:anyhow", "dep:futures"] cache-compiled-artifacts = ["wasmtime", "dep:directories"] -pooling-allocator = [] \ No newline at end of file +pooling-allocator = [] +unix-memory = ["dep:libc"] + +[[test]] +name = "integration" +path = "tests/snippets.rs" +required-features = ["test-build"] diff --git a/Makefile b/Makefile index 8d544c518..a006c34ae 100644 --- a/Makefile +++ b/Makefile @@ -5,11 +5,14 @@ test-specific-cases: cd wasm && make cd snippets && make # run tests - cargo test --color=always --no-fail-fast --manifest-path Cargo.toml - cargo test --color=always --no-fail-fast --manifest-path e2e/Cargo.toml + cargo test --color=always --no-fail-fast --manifest-path Cargo.toml --no-default-features --features=std,wasmtime + cargo test --color=always --no-fail-fast --manifest-path Cargo.toml --no-default-features --features=std,wasmtime,unix-memory + cargo test --color=always --no-fail-fast --manifest-path e2e/Cargo.toml --no-default-features --features=std,wasmtime + cargo test --color=always --no-fail-fast --manifest-path e2e/Cargo.toml --no-default-features --features=std,wasmtime,unix-memory cargo +nightly-2025-09-20 test --color=always --no-fail-fast --manifest-path snippets/Cargo.toml # run nitro test (with release flag) - cargo test --release --package rwasm --test nitro-verifier test_nitro_verifier -- --ignored + cargo test --release --package rwasm --test nitro-verifier test_nitro_verifier --no-default-features --features=std,wasmtime -- --ignored + cargo test --release --package rwasm --test nitro-verifier test_nitro_verifier --no-default-features --features=std,wasmtime,unix-memory -- --ignored .PHONY: coverage coverage: @@ -31,4 +34,8 @@ clean: # Delete all Cargo.lock files except the root find . -name Cargo.lock ! -path './Cargo.lock' -type f -exec rm -f {} + +.PHONY: test +test: + cargo test + all: test-specific-cases \ No newline at end of file diff --git a/benchmarks/.gitignore b/benchmarks/.gitignore index c7cb67382..17cd2545e 100644 --- a/benchmarks/.gitignore +++ b/benchmarks/.gitignore @@ -1,4 +1,4 @@ target Cargo.lock lib.wat -lib.wasm \ No newline at end of file +lib.wasm diff --git a/benchmarks/Cargo.toml b/benchmarks/Cargo.toml index 17b3dc758..fd2526ad2 100644 --- a/benchmarks/Cargo.toml +++ b/benchmarks/Cargo.toml @@ -17,12 +17,42 @@ debug-assertions = false rpath = false codegen-units = 1 -[dependencies] -criterion = { version = "0.7.0", default-features = false, features = [] } - [dev-dependencies] +criterion = { version = "0.7.0", default-features = false, features = [] } rwasm = { path = "..", features = ["std", "wasmtime"] } +rand = { version = "0.9.2" } +wat = "1.230.0" +bitvec = { version = "1.0.1", default-features = false, features = ["alloc"] } +revm-interpreter = "25.0.3" +revm-bytecode = "6.2.2" +hex-literal = "1.0.0" [[bench]] -name = "bench" -harness = false \ No newline at end of file +name = "bitvec" +harness = false + +[[bench]] +name = "compilation" +harness = false + +[[bench]] +name = "fib32" +harness = false + +[[bench]] +name = "fib64" +harness = false + +[[bench]] +name = "fib256" +harness = false + +[[bench]] +name = "parsing" +harness = false + +[dependencies] +alloy-primitives = { version = "1.4.0", default-features = false } + +[features] +default = ["rwasm/unix-memory"] diff --git a/benchmarks/Makefile b/benchmarks/Makefile index 5e88d7d6e..6e8384642 100644 --- a/benchmarks/Makefile +++ b/benchmarks/Makefile @@ -1,5 +1,5 @@ .PHONY: build build: - RUSTFLAGS="-C link-arg=-zstack-size=0" cargo b --target=wasm32-unknown-unknown --release --no-default-features + RUSTFLAGS="-C link-arg=-zstack-size=1024" cargo b --target-dir=./target --target=wasm32-unknown-unknown --release --no-default-features cp ./target/wasm32-unknown-unknown/release/fib.wasm ./lib.wasm - wasm2wat ./lib.wasm > ./lib.wat || true \ No newline at end of file + wasm2wat ./lib.wasm > ./lib.wat || true diff --git a/benchmarks/bench.rs b/benchmarks/bench.rs new file mode 100644 index 000000000..79c08a53f --- /dev/null +++ b/benchmarks/bench.rs @@ -0,0 +1,171 @@ +extern crate test; + +use rwasm::{ + always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, + CompilationConfig, ExecutionEngine, ImportLinker, RwasmModule, RwasmStore, Strategy, Value, +}; +use std::rc::Rc; +use test::Bencher; + +const FIB_VALUE: i32 = 47; + +#[bench] +fn bench_wasmi_no_cache(b: &mut Bencher) { + use wasmi::{Engine, Linker, Module, Store}; + let engine = Engine::default(); + b.iter(|| { + let wasm = include_bytes!("./lib.wasm"); + 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, FIB_VALUE) + .unwrap(); + core::hint::black_box(result); + }); +} + +#[bench] +fn bench_wasmi(b: &mut Bencher) { + use wasmi::{Engine, Linker, Module, Store}; + let engine = Engine::default(); + let wasm_binary = include_bytes!("./lib.wasm"); + let module = Module::new(&engine, &wasm_binary[..]).unwrap(); + let mut store = Store::new(&engine, ()); + let linker = >::new(&engine); + let instance = linker + .instantiate(&mut store, &module) + .unwrap() + .start(&mut store) + .unwrap(); + b.iter(|| { + let result = instance + .get_typed_func::(&store, "main") + .unwrap() + .call(&mut store, FIB_VALUE) + .unwrap(); + core::hint::black_box(result); + }); +} + +#[bench] +fn bench_rwasm_no_cache(b: &mut Bencher) { + let wasm_binary = include_bytes!("./lib.wasm"); + + let config = CompilationConfig::default() + .with_entrypoint_name("main".into()) + .with_allow_malformed_entrypoint_func_type(true); + let (rwasm_module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + let encoded_rwasm_module = rwasm_module.serialize(); + let mut store = RwasmStore::<()>::default(); + let mut engine = ExecutionEngine::new(); + + b.iter(|| { + let (rwasm_module, _) = RwasmModule::new(&encoded_rwasm_module); + let mut result = [Value::I32(0)]; + engine + .execute( + &mut store, + &rwasm_module, + &[Value::I32(FIB_VALUE)], + &mut result, + None, + ) + .unwrap(); + core::hint::black_box(result); + store.reset(true); + }); +} + +#[bench] +fn bench_rwasm(b: &mut Bencher) { + let wasm_binary = include_bytes!("./lib.wasm"); + + let config = CompilationConfig::default() + .with_entrypoint_name("main".into()) + .with_allow_malformed_entrypoint_func_type(true); + let (rwasm_module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + let encoded_rwasm_module = rwasm_module.serialize(); + let mut store = RwasmStore::<()>::default(); + let mut engine = ExecutionEngine::new(); + let (rwasm_module, _) = RwasmModule::new(&encoded_rwasm_module); + + b.iter(|| { + let mut result = [Value::I32(0); 1]; + engine + .execute( + &mut store, + &rwasm_module, + &[Value::I32(FIB_VALUE)], + &mut result, + None, + ) + .unwrap(); + core::hint::black_box(result); + store.reset(true); + }); +} + +fn bench_strategy(b: &mut Bencher, strategy: Strategy) { + let mut store = strategy.create_store( + Rc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + ); + b.iter(|| { + let mut result = [Value::I32(0)]; + strategy + .execute( + &mut store, + "main", + &[Value::I32(FIB_VALUE)], + &mut result, + Some(1_000_000), + ) + .unwrap(); + core::hint::black_box(result); + }); +} + +#[bench] +fn bench_strategy_wasmtime(b: &mut Bencher) { + let wasm_binary = include_bytes!("./lib.wasm"); + let strategy = Strategy::Wasmtime { + module: Rc::new( + compile_wasmtime_module(CompilationConfig::default(), wasm_binary).unwrap(), + ), + }; + bench_strategy(b, strategy) +} + +#[bench] +fn bench_strategy_wasmi(b: &mut Bencher) { + let wasm_binary = include_bytes!("./lib.wasm"); + let strategy = Strategy::Wasmi { + module: Rc::new(compile_wasmi_module(CompilationConfig::default(), wasm_binary).unwrap()), + }; + bench_strategy(b, strategy) +} + +#[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(FIB_VALUE))); + }); +} diff --git a/benchmarks/benches/bench.rs b/benchmarks/benches/bench.rs deleted file mode 100644 index 8f9e7aea3..000000000 --- a/benchmarks/benches/bench.rs +++ /dev/null @@ -1,98 +0,0 @@ -use criterion::{criterion_main, Bencher, Criterion}; -use rwasm::{ - always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, - CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, -}; -use std::{sync::Arc, time::Duration}; - -const FIB_VALUE: i32 = 43; - -fn bench_comparisons(c: &mut Criterion) { - let mut group = c.benchmark_group("Comparisons"); - - // bench_native - { - pub fn fib(n: i32) -> i32 { - let (mut a, mut b) = (0, 1); - for _ in 0..n { - let t = a; - a = b; - b = t + b; - } - a - } - group.bench_function("bench_native", |b| { - b.iter(|| { - core::hint::black_box(fib(core::hint::black_box(FIB_VALUE))); - }); - }); - }; - - fn bench_strategy(b: &mut Bencher, strategy: Strategy) { - b.iter(|| { - let mut store = strategy.create_store( - Arc::new(ImportLinker::default()), - (), - always_failing_syscall_handler, - FuelConfig::default(), - ); - let mut result = [Value::I32(0)]; - strategy - .execute(&mut store, "main", &[Value::I32(FIB_VALUE)], &mut result) - .unwrap(); - core::hint::black_box(result); - }); - } - - { - let wasm_binary = include_bytes!("../lib.wasm"); - let config = CompilationConfig::default().with_consume_fuel(false); - let module = compile_wasmtime_module(config, wasm_binary).unwrap(); - group.bench_function("bench_strategy_wasmtime", |b| { - let strategy = Strategy::Wasmtime { - module: module.clone(), - }; - bench_strategy(b, strategy); - }); - } - - { - let wasm_binary = include_bytes!("../lib.wasm"); - let config = CompilationConfig::default().with_consume_fuel(false); - let module = compile_wasmi_module(config, wasm_binary).unwrap(); - group.bench_function("bench_strategy_wasmi", |b| { - let strategy = Strategy::Wasmi { - module: module.clone(), - }; - bench_strategy(b, strategy); - }); - } - - { - let wasm_binary = include_bytes!("../lib.wasm"); - let config = CompilationConfig::default() - .with_entrypoint_name("main".into()) - .with_allow_malformed_entrypoint_func_type(true) - .with_consume_fuel(false); - let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); - group.bench_function("bench_strategy_rwasm", |b| { - let strategy = Strategy::Rwasm { - module: module.clone(), - engine: ExecutionEngine::acquire_shared(), - }; - bench_strategy(b, strategy); - }); - } - - group.finish(); -} - -pub fn benches() { - let mut criterion: Criterion<_> = Criterion::default() - .configure_from_args() - .warm_up_time(Duration::from_secs(1)) - .measurement_time(Duration::from_secs(1)) - .sample_size(1000); - bench_comparisons(&mut criterion); -} -criterion_main!(benches); diff --git a/benchmarks/benches/bitvec.rs b/benchmarks/benches/bitvec.rs new file mode 100644 index 000000000..af68edb64 --- /dev/null +++ b/benchmarks/benches/bitvec.rs @@ -0,0 +1,109 @@ +use bitvec::{order::Lsb0, vec::BitVec}; +use criterion::{criterion_main, Criterion}; +use rwasm::{ + bitvec_inlined::{BitVecInlined, USIZE_BITS}, + CompilationConfig, Config, ExecutionEngine, RwasmModule, RwasmStore, Value, +}; +use std::time::Duration; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons"); + + const BITVEC_STORE_COUNT: usize = 1; + const BITVEC_INLINED_STORE_COUNT: usize = BITVEC_STORE_COUNT; + const BITVEC_INLINED_STORE_COUNT_HALF: usize = BITVEC_STORE_COUNT / 2; + let bitvec_bits = USIZE_BITS * BITVEC_STORE_COUNT; + let random_sets_count = 1000; + let random_idxs_values = + core::iter::repeat_with(|| (rand::random_range(..bitvec_bits), rand::random::())) + .take(random_sets_count) + .collect::>(); + + // bitvec + { + group.bench_function("bitvec", |b| { + b.iter(|| { + for i in 0..random_sets_count { + let mut bv = BitVec::::repeat(true, bitvec_bits); + let (idx, value) = random_idxs_values[i]; + bv.set(idx, value); + core::hint::black_box(bv); + } + }); + }); + }; + + // bitvec_inlined + { + group.bench_function("bitvec_inlined", |b| { + b.iter(|| { + for i in 0..random_sets_count { + let mut bv = + BitVecInlined::<{ BITVEC_INLINED_STORE_COUNT }>::repeat(true, bitvec_bits); + let (idx, value) = random_idxs_values[i]; + bv.set(idx, value); + core::hint::black_box(bv); + } + }); + }); + }; + + // bitvec_inlined (half store) + { + let mut bv = + BitVecInlined::<{ BITVEC_INLINED_STORE_COUNT_HALF }>::repeat(true, bitvec_bits); + group.bench_function("bitvec_inlined (half of inline store)", |b| { + b.iter(|| { + for i in 0..random_sets_count { + let (idx, value) = random_idxs_values[i]; + bv.set(idx, value); + } + }); + }); + }; + + { + let wasm_binary = wat::parse_str( + r#" + (module + (memory 1) + (data (i32.const 0) "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzab") + (func (export "64_good1") (param $i i32) (result i64) + (i64.load offset=0 (local.get $i)) ;; 0x6867666564636261 'abcdefgh' + ) + ) + "#, + ) + .unwrap(); + let config = CompilationConfig::default() + .with_entrypoint_name("64_good1".into()) + .with_allow_malformed_entrypoint_func_type(true); + let (rwasm_module, _) = RwasmModule::compile(config, &wasm_binary).unwrap(); + println!("{}", rwasm_module); + let mut store = RwasmStore::<()>::default(); + let engine = ExecutionEngine::acquire_shared(); + let mut result = [Value::I64(0); 1]; + group.bench_function("bitvec_inlined (through ExecutionEngine)", |b| { + b.iter(|| { + for _ in 0..random_sets_count { + engine + .execute(&mut store, &rwasm_module, &[Value::I32(0)], &mut result) + .unwrap(); + assert_eq!(result[0].i64().unwrap(), 0x6867666564636261); + } + }); + }); + }; + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/benches/compilation.rs b/benchmarks/benches/compilation.rs new file mode 100644 index 000000000..e3e7c7daa --- /dev/null +++ b/benchmarks/benches/compilation.rs @@ -0,0 +1,73 @@ +use criterion::{criterion_main, Criterion}; +use hex_literal::hex; +use revm_bytecode::Bytecode; +use rwasm::{compile_wasmi_module, compile_wasmtime_module, CompilationConfig, RwasmModule}; +use std::time::Duration; + +const FIB_VALUE: i64 = 43; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons Module Compilation"); + + // bench_evm + { + group.bench_function("bench_evm", |b| { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063e78692bb1461002d575b5f5ffd5b610047600480360381019061004291906100fd565b61005d565b6040516100549190610137565b60405180910390f35b5f5f5f90505f600190505f600290505b8467ffffffffffffffff168167ffffffffffffffff16116100b1575f8284610095919061017d565b90508293508092505080806100a9906101b8565b91505061006d565b508092505050919050565b5f5ffd5b5f67ffffffffffffffff82169050919050565b6100dc816100c0565b81146100e6575f5ffd5b50565b5f813590506100f7816100d3565b92915050565b5f60208284031215610112576101116100bc565b5b5f61011f848285016100e9565b91505092915050565b610131816100c0565b82525050565b5f60208201905061014a5f830184610128565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610187826100c0565b9150610192836100c0565b9250828201905067ffffffffffffffff8111156101b2576101b1610150565b5b92915050565b5f6101c2826100c0565b915067ffffffffffffffff82036101dc576101db610150565b5b60018201905091905056fea2646970667358221220b9932107a06e2c6f884433417401d45c3d48c85efc8e1d3110c6fba210eb5abc64736f6c634300081e0033"); + b.iter(|| { + let bytecode = Bytecode::new_raw(core::hint::black_box(evm_bytecode.into())); + core::hint::black_box(bytecode); + }); + }); + }; + + { + group.bench_function("bench_wasmtime", |b| { + let wasm_binary = include_bytes!("../lib.wasm"); + b.iter(|| { + let config = CompilationConfig::default().with_consume_fuel(false); + let module = + compile_wasmtime_module(config, core::hint::black_box(wasm_binary)).unwrap(); + core::hint::black_box(module); + }); + }); + } + + { + group.bench_function("bench_wasmi", |b| { + let wasm_binary = include_bytes!("../lib.wasm"); + b.iter(|| { + let config = CompilationConfig::default().with_consume_fuel(false); + let module = + compile_wasmi_module(config, core::hint::black_box(wasm_binary)).unwrap(); + core::hint::black_box(module); + }); + }); + } + + { + group.bench_function("bench_rwasm", |b| { + let wasm_binary = include_bytes!("../lib.wasm"); + b.iter(|| { + let config = CompilationConfig::default() + .with_entrypoint_name("fib64".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = + RwasmModule::compile(config, core::hint::black_box(wasm_binary)).unwrap(); + core::hint::black_box(module); + }); + }); + } + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/benches/fib256.rs b/benchmarks/benches/fib256.rs new file mode 100644 index 000000000..549af922e --- /dev/null +++ b/benchmarks/benches/fib256.rs @@ -0,0 +1,139 @@ +use alloy_primitives::U256; +use criterion::{criterion_main, Bencher, Criterion}; +use hex_literal::hex; +use revm_bytecode::Bytecode; +use revm_interpreter::{ + host::DummyHost, + instruction_table, + interpreter::{EthInterpreter, ExtBytecode}, + CallInput, InputsImpl, Interpreter, SharedMemory, +}; +use rwasm::{ + always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, + CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, +}; +use std::{sync::Arc, time::Duration}; + +const FIB_VALUE: i64 = 43; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons Fib256"); + + // bench_native + { + pub fn fib256(n: u64) -> U256 { + let (mut a, mut b) = (U256::ZERO, U256::ONE); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a + } + group.bench_function("bench_native", |b| { + b.iter(|| { + core::hint::black_box(fib256(core::hint::black_box(FIB_VALUE as u64))); + }); + }); + }; + + // bench_evm + { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063e78692bb1461002d575b5f5ffd5b610047600480360381019061004291906100fd565b61005d565b6040516100549190610137565b60405180910390f35b5f5f5f90505f600190505f600290505b8467ffffffffffffffff168167ffffffffffffffff16116100b1575f8284610095919061017d565b90508293508092505080806100a9906101b8565b91505061006d565b508092505050919050565b5f5ffd5b5f67ffffffffffffffff82169050919050565b6100dc816100c0565b81146100e6575f5ffd5b50565b5f813590506100f7816100d3565b92915050565b5f60208284031215610112576101116100bc565b5b5f61011f848285016100e9565b91505092915050565b610131816100c0565b82525050565b5f60208201905061014a5f830184610128565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610187826100c0565b9150610192836100c0565b9250828201905067ffffffffffffffff8111156101b2576101b1610150565b5b92915050565b5f6101c2826100c0565b915067ffffffffffffffff82036101dc576101db610150565b5b60018201905091905056fea2646970667358221220b9932107a06e2c6f884433417401d45c3d48c85efc8e1d3110c6fba210eb5abc64736f6c634300081e0033"); + group.bench_function("bench_evm", |b| { + let bytecode = Bytecode::new_raw(evm_bytecode.into()); + let instruction_table = instruction_table::(); + b.iter(|| { + let mut interpreter = Interpreter::new( + SharedMemory::new(), + ExtBytecode::new_with_hash(bytecode.clone(), [1u8; 32].into()), + InputsImpl { + target_address: Default::default(), + bytecode_address: None, + caller_address: Default::default(), + input: CallInput::Bytes(hex!("e78692bb000000000000000000000000000000000000000000000000000000000000002b").into()), + call_value: Default::default(), + }, + true, + Default::default(), + 100_000_000, + ); + let result = interpreter.run_plain::(&instruction_table, &mut DummyHost {}); + core::hint::black_box(result); + }); + }); + }; + + fn bench_strategy(b: &mut Bencher, strategy: Strategy) { + b.iter(|| { + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = []; + strategy + .execute( + &mut store, + "fib256", + &[Value::I32(0), Value::I64(FIB_VALUE)], + &mut result, + ) + .unwrap(); + core::hint::black_box(result); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmtime_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmtime", |b| { + let strategy = Strategy::Wasmtime { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmi_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmi", |b| { + let strategy = Strategy::Wasmi { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib64".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + group.bench_function("bench_rwasm", |b| { + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + bench_strategy(b, strategy); + }); + } + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/benches/fib32.rs b/benchmarks/benches/fib32.rs new file mode 100644 index 000000000..9bc56d0a5 --- /dev/null +++ b/benchmarks/benches/fib32.rs @@ -0,0 +1,145 @@ +use criterion::{criterion_main, Bencher, Criterion}; +use hex_literal::hex; +use revm_bytecode::Bytecode; +use revm_interpreter::{ + host::DummyHost, + instruction_table, + interpreter::{EthInterpreter, ExtBytecode}, + CallInput, InputsImpl, Interpreter, SharedMemory, +}; +use rwasm::{ + always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, + CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, +}; +use std::{sync::Arc, time::Duration}; + +const FIB_VALUE: i32 = 43; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons fib32"); + + // bench_native + { + pub fn fib32(n: u32) -> u32 { + let (mut a, mut b) = (0, 1); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a + } + group.bench_function("bench_native", |b| { + b.iter(|| { + core::hint::black_box(fib32(core::hint::black_box(FIB_VALUE as u32))); + }); + }); + }; + + // bench_evm + { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063f9b7c7e51461002d575b5f5ffd5b610047600480360381019061004291906100f1565b61005d565b604051610054919061012b565b60405180910390f35b5f5f5f90505f600190505f600290505b8463ffffffff168163ffffffff16116100a9575f828461008d9190610171565b90508293508092505080806100a1906101a8565b91505061006d565b508092505050919050565b5f5ffd5b5f63ffffffff82169050919050565b6100d0816100b8565b81146100da575f5ffd5b50565b5f813590506100eb816100c7565b92915050565b5f60208284031215610106576101056100b4565b5b5f610113848285016100dd565b91505092915050565b610125816100b8565b82525050565b5f60208201905061013e5f83018461011c565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f61017b826100b8565b9150610186836100b8565b9250828201905063ffffffff8111156101a2576101a1610144565b5b92915050565b5f6101b2826100b8565b915063ffffffff82036101c8576101c7610144565b5b60018201905091905056fea26469706673582212206f34ca4baf4d7f4a2ab9c7060b71c1f28bca433c9959aabaa5c1ac6323863d2364736f6c634300081e0033"); + group.bench_function("bench_evm", |b| { + let bytecode = Bytecode::new_raw(evm_bytecode.into()); + let instruction_table = instruction_table::(); + b.iter(|| { + let mut interpreter = Interpreter::new( + SharedMemory::new(), + ExtBytecode::new_with_hash(bytecode.clone(), [1u8; 32].into()), + InputsImpl { + target_address: Default::default(), + bytecode_address: None, + caller_address: Default::default(), + input: CallInput::Bytes(hex!("f9b7c7e5000000000000000000000000000000000000000000000000000000000000002b").into()), + call_value: Default::default(), + }, + true, + Default::default(), + 100_000_000, + ); + let result = interpreter.run_plain::(&instruction_table, &mut DummyHost {}); + // match &result { + // InterpreterAction::NewFrame(_) => unreachable!(), + // InterpreterAction::Return(result) => { + // if !result.is_ok() { + // println!("{:?}", result); + // } + // assert!(result.is_ok()); + // assert_eq!(result.output.len(), 32); + // assert_eq!(result.output.as_ref(), hex!("0000000000000000000000000000000000000000000000000000000019d699a5")); + // } + // } + core::hint::black_box(result); + }); + }); + }; + + fn bench_strategy(b: &mut Bencher, strategy: Strategy) { + b.iter(|| { + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = [Value::I32(0)]; + strategy + .execute(&mut store, "fib32", &[Value::I32(FIB_VALUE)], &mut result) + .unwrap(); + core::hint::black_box(result); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmtime_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmtime", |b| { + let strategy = Strategy::Wasmtime { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmi_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmi", |b| { + let strategy = Strategy::Wasmi { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib32".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + group.bench_function("bench_rwasm", |b| { + let engine = ExecutionEngine::acquire_shared(); + let strategy = Strategy::Rwasm { + module: module.clone(), + engine, + }; + bench_strategy(b, strategy); + }); + } + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/benches/fib64.rs b/benchmarks/benches/fib64.rs new file mode 100644 index 000000000..8802adbd3 --- /dev/null +++ b/benchmarks/benches/fib64.rs @@ -0,0 +1,144 @@ +use criterion::{criterion_main, Bencher, Criterion}; +use hex_literal::hex; +use revm_bytecode::Bytecode; +use revm_interpreter::{ + host::DummyHost, + instruction_table, + interpreter::{EthInterpreter, ExtBytecode}, + CallInput, InputsImpl, Interpreter, SharedMemory, +}; +use rwasm::{ + always_failing_syscall_handler, compile_wasmi_module, compile_wasmtime_module, + CompilationConfig, ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, +}; +use std::{sync::Arc, time::Duration}; + +const FIB_VALUE: i64 = 43; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons fib64"); + + // bench_native + { + pub fn fib64(n: u64) -> u64 { + let (mut a, mut b) = (0, 1); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a + } + group.bench_function("bench_native", |b| { + b.iter(|| { + core::hint::black_box(fib64(core::hint::black_box(FIB_VALUE as u64))); + }); + }); + }; + + // bench_evm + { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063e78692bb1461002d575b5f5ffd5b610047600480360381019061004291906100fd565b61005d565b6040516100549190610137565b60405180910390f35b5f5f5f90505f600190505f600290505b8467ffffffffffffffff168167ffffffffffffffff16116100b1575f8284610095919061017d565b90508293508092505080806100a9906101b8565b91505061006d565b508092505050919050565b5f5ffd5b5f67ffffffffffffffff82169050919050565b6100dc816100c0565b81146100e6575f5ffd5b50565b5f813590506100f7816100d3565b92915050565b5f60208284031215610112576101116100bc565b5b5f61011f848285016100e9565b91505092915050565b610131816100c0565b82525050565b5f60208201905061014a5f830184610128565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610187826100c0565b9150610192836100c0565b9250828201905067ffffffffffffffff8111156101b2576101b1610150565b5b92915050565b5f6101c2826100c0565b915067ffffffffffffffff82036101dc576101db610150565b5b60018201905091905056fea2646970667358221220b9932107a06e2c6f884433417401d45c3d48c85efc8e1d3110c6fba210eb5abc64736f6c634300081e0033"); + group.bench_function("bench_evm", |b| { + let bytecode = Bytecode::new_raw(evm_bytecode.into()); + let instruction_table = instruction_table::(); + b.iter(|| { + let mut interpreter = Interpreter::new( + SharedMemory::new(), + ExtBytecode::new_with_hash(bytecode.clone(), [1u8; 32].into()), + InputsImpl { + target_address: Default::default(), + bytecode_address: None, + caller_address: Default::default(), + input: CallInput::Bytes(hex!("e78692bb000000000000000000000000000000000000000000000000000000000000002b").into()), + call_value: Default::default(), + }, + true, + Default::default(), + 100_000_000, + ); + let result = interpreter.run_plain::(&instruction_table, &mut DummyHost {}); + // match &result { + // InterpreterAction::NewFrame(_) => unreachable!(), + // InterpreterAction::Return(result) => { + // if !result.is_ok() { + // println!("{:?}", result); + // } + // assert!(result.is_ok()); + // assert_eq!(result.output.len(), 32); + // assert_eq!(result.output.as_ref(), hex!("00000000000000000000000000000000000000000000000027f80ddaa1ba7878")); + // } + // } + core::hint::black_box(result); + }); + }); + }; + + fn bench_strategy(b: &mut Bencher, strategy: Strategy) { + b.iter(|| { + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = [Value::I64(0)]; + strategy + .execute(&mut store, "fib64", &[Value::I64(FIB_VALUE)], &mut result) + .unwrap(); + core::hint::black_box(result); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmtime_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmtime", |b| { + let strategy = Strategy::Wasmtime { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmi_module(config, wasm_binary).unwrap(); + group.bench_function("bench_wasmi", |b| { + let strategy = Strategy::Wasmi { + module: module.clone(), + }; + bench_strategy(b, strategy); + }); + } + + { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib64".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + group.bench_function("bench_rwasm", |b| { + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + bench_strategy(b, strategy); + }); + } + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/benches/parsing.rs b/benchmarks/benches/parsing.rs new file mode 100644 index 000000000..617b576ee --- /dev/null +++ b/benchmarks/benches/parsing.rs @@ -0,0 +1,88 @@ +use criterion::{criterion_main, Criterion}; +use hex_literal::hex; +use revm_bytecode::Bytecode; +use rwasm::{ + compile_wasmi_module, compile_wasmtime_module, wasmtime::deserialize_wasmtime_module, + CompilationConfig, RwasmModule, RwasmModuleView, +}; +use std::time::Duration; + +const FIB_VALUE: i64 = 43; + +fn bench_comparisons(c: &mut Criterion) { + let mut group = c.benchmark_group("Comparisons Module Parsing"); + + // bench_evm + group.bench_function("bench_evm", |b| { + let evm_bytecode = hex!("608060405234801561000f575f5ffd5b5060043610610029575f3560e01c8063e78692bb1461002d575b5f5ffd5b610047600480360381019061004291906100fd565b61005d565b6040516100549190610137565b60405180910390f35b5f5f5f90505f600190505f600290505b8467ffffffffffffffff168167ffffffffffffffff16116100b1575f8284610095919061017d565b90508293508092505080806100a9906101b8565b91505061006d565b508092505050919050565b5f5ffd5b5f67ffffffffffffffff82169050919050565b6100dc816100c0565b81146100e6575f5ffd5b50565b5f813590506100f7816100d3565b92915050565b5f60208284031215610112576101116100bc565b5b5f61011f848285016100e9565b91505092915050565b610131816100c0565b82525050565b5f60208201905061014a5f830184610128565b92915050565b7f4e487b71000000000000000000000000000000000000000000000000000000005f52601160045260245ffd5b5f610187826100c0565b9150610192836100c0565b9250828201905067ffffffffffffffff8111156101b2576101b1610150565b5b92915050565b5f6101c2826100c0565b915067ffffffffffffffff82036101dc576101db610150565b5b60018201905091905056fea2646970667358221220b9932107a06e2c6f884433417401d45c3d48c85efc8e1d3110c6fba210eb5abc64736f6c634300081e0033"); + let bytecode = Bytecode::new_raw(core::hint::black_box(evm_bytecode.into())); + bytecode.original_bytes(); + b.iter(|| { + core::hint::black_box(&bytecode); + }); + }); + + group.bench_function("bench_wasmtime", |b| { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmtime_module(config, core::hint::black_box(wasm_binary)).unwrap(); + let raw_module = module.serialize().unwrap(); + b.iter(|| { + let module = deserialize_wasmtime_module( + CompilationConfig::default(), + core::hint::black_box(&raw_module), + ) + .unwrap(); + core::hint::black_box(module); + }); + }); + + group.bench_function("bench_wasmi", |b| { + let wasm_binary = include_bytes!("../lib.wasm"); + b.iter(|| { + let config = CompilationConfig::default().with_consume_fuel(false); + let module = compile_wasmi_module(config, core::hint::black_box(wasm_binary)).unwrap(); + core::hint::black_box(module); + }); + }); + + group.bench_function("bench_rwasm", |b| { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib64".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, core::hint::black_box(wasm_binary)).unwrap(); + let raw_module = module.serialize(); + b.iter(|| { + let (module, _) = RwasmModule::new(core::hint::black_box(&raw_module)); + core::hint::black_box(module); + }); + }); + + group.bench_function("bench_rwasm_view", |b| { + let wasm_binary = include_bytes!("../lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib64".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, core::hint::black_box(wasm_binary)).unwrap(); + let raw_module = module.serialize(); + b.iter(|| { + let (module, _) = RwasmModuleView::new(core::hint::black_box(&raw_module)); + core::hint::black_box(module); + }); + }); + + group.finish(); +} + +pub fn benches() { + let mut criterion: Criterion<_> = Criterion::default() + .configure_from_args() + .warm_up_time(Duration::from_millis(500)) + .measurement_time(Duration::from_secs(1)) + .sample_size(200); + bench_comparisons(&mut criterion); +} +criterion_main!(benches); diff --git a/benchmarks/lib.rs b/benchmarks/lib.rs index 9e395eaf9..7636d5d1c 100644 --- a/benchmarks/lib.rs +++ b/benchmarks/lib.rs @@ -1,6 +1,7 @@ -#[cfg(target_arch = "wasm32")] +use alloy_primitives::U256; + #[no_mangle] -pub fn main(n: i32) -> i32 { +pub fn fib32(n: u32) -> u32 { let (mut a, mut b) = (0, 1); for _ in 0..n { let temp = a; @@ -9,3 +10,90 @@ pub fn main(n: i32) -> i32 { } a } + +#[no_mangle] +pub fn fib64(n: u64) -> u64 { + let (mut a, mut b) = (0, 1); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a +} + +#[no_mangle] +pub fn fib256(n: u64) -> U256 { + let (mut a, mut b) = (U256::ZERO, U256::ONE); + for _ in 0..n { + let temp = a; + a = b; + b = temp + b; + } + a +} + +#[cfg(test)] +mod tests { + use rwasm::{ + always_failing_syscall_handler, compile_wasmtime_module, CompilationConfig, + ExecutionEngine, FuelConfig, ImportLinker, RwasmModule, Strategy, Value, + }; + use std::{sync::Arc, time::Instant}; + + const FIB_VALUE: i32 = 41; + + #[test] + fn fib32_rwasm_test() { + let wasm_binary = include_bytes!("./lib.wasm"); + let config = CompilationConfig::default() + .with_entrypoint_name("fib32".into()) + .with_allow_malformed_entrypoint_func_type(true) + .with_consume_fuel(false); + let (module, _) = RwasmModule::compile(config, wasm_binary).unwrap(); + let strategy = Strategy::Rwasm { + module: module.clone(), + engine: ExecutionEngine::acquire_shared(), + }; + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + let mut result = [Value::I32(0)]; + strategy + .execute(&mut store, "fib32", &[Value::I32(FIB_VALUE)], &mut result) + .unwrap(); + assert_eq!(165580141, result[0].i32().unwrap()); + core::hint::black_box(result); + } + + #[test] + fn fib32_wasmtime_test() { + let wasm_binary = include_bytes!("./lib.wasm"); + let config = CompilationConfig::default().with_consume_fuel(false); + let time = Instant::now(); + let module = compile_wasmtime_module(config, wasm_binary).unwrap(); + println!("module {}", time.elapsed().as_nanos()); + let strategy = Strategy::Wasmtime { + module: module.clone(), + }; + let time = Instant::now(); + let mut store = strategy.create_store( + Arc::new(ImportLinker::default()), + (), + always_failing_syscall_handler, + FuelConfig::default(), + ); + println!("store {}", time.elapsed().as_nanos()); + let mut result = [Value::I32(0)]; + let time = Instant::now(); + strategy + .execute(&mut store, "fib32", &[Value::I32(FIB_VALUE)], &mut result) + .unwrap(); + println!("exec {}", time.elapsed().as_nanos()); + assert_eq!(165580141, result[0].i32().unwrap()); + core::hint::black_box(result); + } +} diff --git a/benchmarks/rust-toolchain b/benchmarks/rust-toolchain index d10e348d2..942a33c1a 100644 --- a/benchmarks/rust-toolchain +++ b/benchmarks/rust-toolchain @@ -1 +1 @@ -nightly-2025-09-20 \ No newline at end of file +nightly-2025-09-20 diff --git a/e2e/Cargo.toml b/e2e/Cargo.toml index a03079ff5..48c769522 100644 --- a/e2e/Cargo.toml +++ b/e2e/Cargo.toml @@ -9,5 +9,8 @@ anyhow = "1.0.71" wast = "=64.0" [features] -default = [] -debug-print = ["rwasm/debug-print"] \ No newline at end of file +default = ["std"] +std = ["rwasm/std"] +debug-print = ["rwasm/debug-print"] +wasmtime = ["rwasm/wasmtime"] +unix-memory = ["rwasm/unix-memory"] \ No newline at end of file diff --git a/e2e/src/lib.rs b/e2e/src/lib.rs index 3d47aa42b..63c8d8f3e 100644 --- a/e2e/src/lib.rs +++ b/e2e/src/lib.rs @@ -49,6 +49,17 @@ macro_rules! define_spec_tests { }; } +#[cfg(test)] +mod tests { + use crate::run; + use std::fmt; + + #[test] + fn specific_test() { + run::run_wasm_spec_test(&fmt::format(format_args!("{}/{}", "testsuite", "global"))); + } +} + define_spec_tests! { let runner = run::run_wasm_spec_test; diff --git a/legacy/Cargo.toml b/legacy/Cargo.toml deleted file mode 100644 index 0a227b984..000000000 --- a/legacy/Cargo.toml +++ /dev/null @@ -1,41 +0,0 @@ -[package] -name = "rwasm-legacy" -version = "0.30.0" -documentation = "" -description = "rwasm" -edition = "2021" - -[dependencies] -wasmparser = { version = "0.100.1", package = "wasmparser-nostd", default-features = false } -spin = { version = "0.9", default-features = false, features = [ - "mutex", - "spin_mutex", - "rwlock", -] } -smallvec = { version = "1.10.0", features = ["union"] } -libm = "0.2.1" -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 = { version = "0.27.1", optional = true } -strum_macros = { version = "0.27.1", optional = true } - -[dev-dependencies] -hex-literal = "0.4.1" -wat = "1" -assert_matches = "1.5" -wast = "52.0" -anyhow = "1.0" -criterion = { version = "0.4", default-features = false } -rand = "0.8.2" - -[features] -default = ["std"] -# Use `no-default-features` for a `no_std` build. -std = ["num-traits/std", "downcast-rs/std", "byteorder/std", "dep:strum", "dep:strum_macros"] -print-trace = ["std"] -e2e = [] diff --git a/legacy/README.md b/legacy/README.md deleted file mode 100644 index dd12886e5..000000000 --- a/legacy/README.md +++ /dev/null @@ -1,197 +0,0 @@ - -| Continuous Integration | Test Coverage | Documentation | Crates.io | -|:----------------------:|:--------------------:|:----------------:|:--------------------:| -| [![ci][1]][2] | [![codecov][3]][4] | [![docs][5]][6] | [![crates][7]][8] | - -[1]: https://github.com/paritytech/wasmi/workflows/Rust%20-%20Continuous%20Integration/badge.svg?branch=master -[2]: https://github.com/paritytech/wasmi/actions?query=workflow%3A%22Rust+-+Continuous+Integration%22+branch%3Amaster -[3]: https://codecov.io/gh/paritytech/wasmi/branch/master/graph/badge.svg -[4]: https://codecov.io/gh/paritytech/wasmi/branch/master -[5]: https://docs.rs/wasmi/badge.svg -[6]: https://docs.rs/wasmi -[7]: https://img.shields.io/crates/v/wasmi.svg -[8]: https://crates.io/crates/wasmi - -[license-mit-badge]: https://img.shields.io/badge/license-MIT-blue.svg -[license-apache-badge]: https://img.shields.io/badge/license-APACHE-orange.svg - -# `wasmi`- WebAssembly (Wasm) Interpreter - -`wasmi` is an efficient WebAssembly interpreter with low-overhead and support -for embedded environment such as WebAssembly itself. - -At Parity we are using `wasmi` in [Substrate](https://github.com/paritytech/substrate) -as the execution engine for our WebAssembly based smart contracts. -Furthermore we run `wasmi` within the Substrate runtime which is a WebAssembly -environment itself and driven via [Wasmtime] at the time of this writing. -As such `wasmi`'s implementation requires a high degree of correctness and -Wasm specification conformance. - -Since `wasmi` is relatively lightweight compared to other Wasm virtual machines -such as Wasmtime it is also a decent option for initial prototyping. - -[Wasmtime]: https://github.com/bytecodealliance/wasmtime - -## Distinct Features - -The following list states some of the distinct features of `wasmi`. - -- Focus on simple, correct and deterministic WebAssembly execution. -- Can itself run inside of WebAssembly. -- Low-overhead and cross-platform WebAssembly runtime. -- Loosely mirrors the [Wasmtime API](https://docs.rs/wasmtime/). -- Resumable function calls. -- Built-in support for fuel metering. -- 100% official WebAssembly spec testsuite compliance. - -## WebAssembly Proposals - -The new `wasmi` engine supports a variety of WebAssembly proposals and will support even more of them in the future. - -| WebAssembly Proposal | Status | Comment | -|:--|:--:|:--| -| [`mutable-global`] | ✅ | Since version `0.14.0`. | -| [`saturating-float-to-int`] | ✅ | Since version `0.14.0`. | -| [`sign-extension`] | ✅ | Since version `0.14.0`. | -| [`multi-value`] | ✅ | Since version `0.14.0`. | -| [`bulk-memory`] | ✅ | Since version `0.24.0`. [(#628)] | -| [`reference-types`] | ✅ | Since version `0.24.0`. [(#635)] | -| [`simd`] | ❌ | Unlikely to be supported. | -| [`tail-calls`] | ✅ | Since version `0.28.0`. [(#683)] | -| [`extended-const`] | ✅ | Since version `0.29.0`. [(#707)] | -| | | -| [WASI] | 🟡 | Experimental support via the [`wasmi_wasi` crate] or the `wasmi` CLI application. | - -[`mutable-global`]: https://github.com/WebAssembly/mutable-global -[`saturating-float-to-int`]: https://github.com/WebAssembly/nontrapping-float-to-int-conversions -[`sign-extension`]: https://github.com/WebAssembly/sign-extension-ops -[`multi-value`]: https://github.com/WebAssembly/multi-value -[`reference-types`]: https://github.com/WebAssembly/reference-types -[`bulk-memory`]: https://github.com/WebAssembly/bulk-memory-operations -[`simd` ]: https://github.com/webassembly/simd -[`tail-calls`]: https://github.com/WebAssembly/tail-call -[`extended-const`]: https://github.com/WebAssembly/extended-const - -[WASI]: https://github.com/WebAssembly/WASI -[`wasmi_wasi` crate]: ./crates/wasi - -[(#363)]: https://github.com/paritytech/wasmi/issues/363 -[(#364)]: https://github.com/paritytech/wasmi/issues/364 -[(#496)]: https://github.com/paritytech/wasmi/issues/496 -[(#628)]: https://github.com/paritytech/wasmi/pull/628 -[(#635)]: https://github.com/paritytech/wasmi/pull/635 -[(#638)]: https://github.com/paritytech/wasmi/pull/638 -[(#683)]: https://github.com/paritytech/wasmi/pull/683 -[(#707)]: https://github.com/paritytech/wasmi/pull/707 - -## Usage - -### As CLI Application - -Install the newest `wasmi` CLI version via: -```console -cargo install wasmi_cli -``` -Then run arbitrary `wasm32-unknown-unknown` Wasm blobs via: -```console -wasmi_cli []* -``` - -### As Rust Library - -Any Rust crate can depend on the [`wasmi` crate](https://crates.io/crates/wasmi) -in order to integrate a WebAssembly intepreter into their stack. - -Refer to the [`wasmi` crate docs](https://docs.rs/wasmi) to learn how to use the `wasmi` crate as library. - -## Development - -### Building - -Clone `wasmi` from our official repository and then build using the standard `cargo` procedure: - -```console -git clone https://github.com/paritytech/wasmi.git -cd wasmi -cargo build -``` - -### Testing - -In order to test `wasmi` you need to initialize and update the Git submodules using: - -```console -git submodule update --init --recursive -``` - -Alternatively you can provide `--recursive` flag to `git clone` command while cloning the repository: - -```console -git clone https://github.com/paritytech/wasmi.git --recursive -``` - -After Git submodules have been initialized and updated you can test using: - -```console -cargo test --workspace -``` - -### Benchmarks - -In order to benchmark `wasmi` use the following command: - -```console -cargo bench -``` - -You can filter which set of benchmarks to run: -- `cargo bench translate` - - Only runs benchmarks concerned with WebAssembly module translation. - -- `cargo bench instantiate` - - Only runs benchmarks concerned with WebAssembly module instantiation. - -- `cargo bench execute` - - Only runs benchmarks concerned with executing WebAssembly functions. - -## Supported Platforms - -Supported platforms are primarily Linux, MacOS, Windows and WebAssembly. -Other platforms might be working but are not guaranteed to be so by the `wasmi` maintainers. - -Use the following command in order to produce a WebAssembly build: - -```console -cargo build --no-default-features --target wasm32-unknown-unknown -``` - -## Production Builds - -In order to reap the most performance out of `wasmi` we highly recommended -to compile the `wasmi` crate using the following Cargo `profile`: - -```toml -[profile.release] -lto = "fat" -codegen-units = 1 -``` - -When compiling for the WebAssembly target we highly recommend to post-optimize -`wasmi` using [Binaryen]'s `wasm-opt` tool since our experiments displayed a -80-100% performance improvements when executed under Wasmtime and also -slightly smaller Wasm binaries. - -[Binaryen]: https://github.com/WebAssembly/binaryen - -## License - -`wasmi` is primarily distributed under the terms of both the MIT -license and the APACHE license (Version 2.0), at your choice. - -See `LICENSE-APACHE` and `LICENSE-MIT` for details. - -## Contribution - -Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in `wasmi` by you, as defined in the APACHE 2.0 license, shall be -dual licensed as above, without any additional terms or conditions. diff --git a/legacy/src/arena/component_vec.rs b/legacy/src/arena/component_vec.rs deleted file mode 100644 index cbd8b0407..000000000 --- a/legacy/src/arena/component_vec.rs +++ /dev/null @@ -1,224 +0,0 @@ -use crate::arena::ArenaIndex; -use alloc::vec::Vec; -use core::{ - fmt::{self, Debug}, - marker::PhantomData, - ops::{Index, IndexMut}, -}; - -/// Stores components for entities backed by a [`Vec`]. -pub struct ComponentVec { - components: Vec>, - marker: PhantomData Idx>, -} - -/// [`ComponentVec`] does not store `Idx` therefore it is `Send` without its bound. -unsafe impl Send for ComponentVec where T: Send {} - -/// [`ComponentVec`] does not store `Idx` therefore it is `Sync` without its bound. -unsafe impl Sync for ComponentVec where T: Send {} - -impl Debug for ComponentVec -where - T: Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ComponentVec") - .field("components", &DebugComponents(&self.components)) - .finish() - } -} - -struct DebugComponents<'a, T>(&'a [Option]); - -impl<'a, T> Debug for DebugComponents<'a, T> -where - T: Debug, -{ - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let mut map = f.debug_map(); - let components = self - .0 - .iter() - .enumerate() - .filter_map(|(n, component)| component.as_ref().map(|c| (n, c))); - for (idx, component) in components { - map.entry(&idx, component); - } - map.finish() - } -} - -impl Default for ComponentVec { - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for ComponentVec -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.components.eq(&other.components) - } -} - -impl Eq for ComponentVec where T: Eq {} - -impl ComponentVec { - /// Creates a new empty [`ComponentVec`]. - pub fn new() -> Self { - Self { - components: Vec::new(), - marker: PhantomData, - } - } - - /// Clears all components from the [`ComponentVec`]. - pub fn clear(&mut self) { - self.components.clear(); - } -} - -impl ComponentVec -where - Idx: ArenaIndex, -{ - /// Sets the `component` for the entity at `index`. - /// - /// Returns the old component of the same entity if any. - pub fn set(&mut self, index: Idx, component: T) -> Option { - let index = index.into_usize(); - if index >= self.components.len() { - // The underlying vector does not have enough capacity - // and is required to be enlarged. - self.components.resize_with(index + 1, || None); - } - self.components[index].replace(component) - } - - /// Unsets the component for the entity at `index` and returns it if any. - pub fn unset(&mut self, index: Idx) -> Option { - self.components - .get_mut(index.into_usize()) - .and_then(Option::take) - } - - /// Returns a shared reference to the component at the `index` if any. - /// - /// Returns `None` if no component is stored under the `index`. - #[inline] - pub fn get(&self, index: Idx) -> Option<&T> { - self.components - .get(index.into_usize()) - .and_then(Option::as_ref) - } - - /// Returns an exclusive reference to the component at the `index` if any. - /// - /// Returns `None` if no component is stored under the `index`. - #[inline] - pub fn get_mut(&mut self, index: Idx) -> Option<&mut T> { - self.components - .get_mut(index.into_usize()) - .and_then(Option::as_mut) - } -} - -impl Index for ComponentVec -where - Idx: ArenaIndex, -{ - type Output = T; - - #[inline] - fn index(&self, index: Idx) -> &Self::Output { - self.get(index) - .unwrap_or_else(|| panic!("missing component at index: {}", index.into_usize())) - } -} - -impl IndexMut for ComponentVec -where - Idx: ArenaIndex, -{ - #[inline] - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - self.get_mut(index) - .unwrap_or_else(|| panic!("missing component at index: {}", index.into_usize())) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - /// Add `n` components and perform checks along the way. - fn add_components(vec: &mut ComponentVec, n: usize) { - for i in 0..n { - let mut str = format!("{i}"); - assert!(vec.get(i).is_none()); - assert!(vec.get_mut(i).is_none()); - assert!(vec.set(i, str.clone()).is_none()); - assert_eq!(vec.get(i), Some(&str)); - assert_eq!(vec.get_mut(i), Some(&mut str)); - assert_eq!(&vec[i], &str); - assert_eq!(&mut vec[i], &mut str); - } - } - - #[test] - fn it_works() { - let mut vec = >::new(); - let n = 10; - add_components(&mut vec, n); - // Remove components in reverse order for fun. - // Check if components have been removed properly. - for i in (0..n).rev() { - let str = format!("{i}"); - assert_eq!(vec.unset(i), Some(str)); - assert!(vec.get(i).is_none()); - assert!(vec.get_mut(i).is_none()); - } - } - - #[test] - fn clear_works() { - let mut vec = >::new(); - let n = 10; - add_components(&mut vec, n); - // Clear component vec and check if components have been removed properly. - vec.clear(); - for i in 0..n { - assert!(vec.get(i).is_none()); - assert!(vec.get_mut(i).is_none()); - } - } - - #[test] - fn debug_works() { - let mut vec = >::new(); - add_components(&mut vec, 4); - { - let debug_str = format!("{vec:?}"); - let expected_str = "\ - ComponentVec { components: {0: \"0\", 1: \"1\", 2: \"2\", 3: \"3\"} }\ - "; - assert_eq!(debug_str, expected_str); - } - { - let debug_str = format!("{vec:#?}"); - let expected_str = "\ - ComponentVec {\n \ - components: {\n \ - 0: \"0\",\n \ - 1: \"1\",\n \ - 2: \"2\",\n \ - 3: \"3\",\n \ - },\n}\ - "; - assert_eq!(debug_str, expected_str); - } - } -} diff --git a/legacy/src/arena/dedup.rs b/legacy/src/arena/dedup.rs deleted file mode 100644 index 3585a90ef..000000000 --- a/legacy/src/arena/dedup.rs +++ /dev/null @@ -1,174 +0,0 @@ -use super::{Arena, ArenaIndex, Iter, IterMut}; -use alloc::collections::BTreeMap; -use core::ops::{Index, IndexMut}; - -/// A deduplicating arena allocator with a given index and entity type. -/// -/// For performance reasons the arena cannot deallocate single entities. -#[derive(Debug)] -pub struct DedupArena { - entity2idx: BTreeMap, - entities: Arena, -} - -impl Default for DedupArena { - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for DedupArena -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.entities.eq(&other.entities) - } -} - -impl Eq for DedupArena where T: Eq {} - -impl DedupArena { - /// Creates a new empty deduplicating entity arena. - pub fn new() -> Self { - Self { - entity2idx: BTreeMap::new(), - entities: Arena::new(), - } - } - - /// Returns the allocated number of entities. - #[inline] - pub fn len(&self) -> usize { - self.entities.len() - } - - /// Returns `true` if the [`Arena`] has not yet allocated entities. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Clears all entities from the arena. - pub fn clear(&mut self) { - self.entity2idx.clear(); - self.entities.clear(); - } - - /// Returns an iterator over the shared reference of the [`Arena`] entities. - pub fn iter(&self) -> Iter { - self.entities.iter() - } - - /// Returns an iterator over the exclusive reference of the [`Arena`] entities. - pub fn iter_mut(&mut self) -> IterMut { - self.entities.iter_mut() - } -} - -impl DedupArena -where - Idx: ArenaIndex, - T: Ord + Clone, -{ - /// Returns the next entity index. - fn next_index(&self) -> Idx { - self.entities.next_index() - } - - /// Allocates a new entity and returns its index. - /// - /// # Note - /// - /// Only allocates if the entity does not already exist in the [`DedupArena`]. - pub fn alloc(&mut self, entity: T) -> Idx { - match self.entity2idx.get(&entity) { - Some(index) => *index, - None => { - let index = self.next_index(); - self.entity2idx.insert(entity.clone(), index); - self.entities.alloc(entity); - index - } - } - } - - /// Returns a shared reference to the entity at the given index if any. - #[inline] - pub fn get(&self, index: Idx) -> Option<&T> { - self.entities.get(index) - } - - /// Returns an exclusive reference to the entity at the given index if any. - #[inline] - pub fn get_mut(&mut self, index: Idx) -> Option<&mut T> { - self.entities.get_mut(index) - } -} - -impl FromIterator for DedupArena -where - Idx: ArenaIndex, - T: Clone + Ord, -{ - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - let entities = Arena::from_iter(iter); - let entity2idx = entities - .iter() - .map(|(idx, entity)| (entity.clone(), idx)) - .collect::>(); - Self { - entity2idx, - entities, - } - } -} - -impl<'a, Idx, T> IntoIterator for &'a DedupArena -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a T); - type IntoIter = Iter<'a, Idx, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a, Idx, T> IntoIterator for &'a mut DedupArena -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a mut T); - type IntoIter = IterMut<'a, Idx, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} - -impl Index for DedupArena -where - Idx: ArenaIndex, -{ - type Output = T; - - #[inline] - fn index(&self, index: Idx) -> &Self::Output { - &self.entities[index] - } -} - -impl IndexMut for DedupArena -where - Idx: ArenaIndex, -{ - #[inline] - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - &mut self.entities[index] - } -} diff --git a/legacy/src/arena/guarded.rs b/legacy/src/arena/guarded.rs deleted file mode 100644 index e7905a41c..000000000 --- a/legacy/src/arena/guarded.rs +++ /dev/null @@ -1,34 +0,0 @@ -use crate::arena::ArenaIndex; - -/// A guarded entity. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct GuardedEntity { - guard_idx: GuardIdx, - entity_idx: EntityIdx, -} - -impl GuardedEntity { - /// Creates a new [`GuardedEntity`]. - pub fn new(guard_idx: GuardIdx, entity_idx: EntityIdx) -> Self { - Self { - guard_idx, - entity_idx, - } - } -} - -impl GuardedEntity -where - GuardIdx: ArenaIndex, - EntityIdx: ArenaIndex, -{ - /// Returns the entity index of the [`GuardedEntity`]. - /// - /// Return `None` if the `guard_index` does not match. - pub fn entity_index(&self, guard_index: GuardIdx) -> Option { - if self.guard_idx.into_usize() != guard_index.into_usize() { - return None; - } - Some(self.entity_idx) - } -} diff --git a/legacy/src/arena/mod.rs b/legacy/src/arena/mod.rs deleted file mode 100644 index f4a939dd8..000000000 --- a/legacy/src/arena/mod.rs +++ /dev/null @@ -1,335 +0,0 @@ -//! Fast arena allocators for different usage purposes. -//! -//! They cannot deallocate single allocated entities for extra efficiency. -//! These allocators mainly serve as the backbone for an efficient Wasm store -//! implementation. - -#![warn( - clippy::cast_lossless, - clippy::missing_errors_doc, - clippy::used_underscore_binding, - clippy::redundant_closure_for_method_calls, - clippy::type_repetition_in_bounds, - clippy::inconsistent_struct_constructor, - clippy::default_trait_access, - clippy::map_unwrap_or, - clippy::items_after_statements -)] -#[cfg(not(feature = "std"))] -extern crate alloc; -#[cfg(feature = "std")] -extern crate std as alloc; - -mod component_vec; -mod dedup; -mod guarded; - -#[cfg(test)] -mod tests; - -pub use self::{component_vec::ComponentVec, dedup::DedupArena, guarded::GuardedEntity}; -use alloc::vec::Vec; -use core::{ - iter::{DoubleEndedIterator, Enumerate, ExactSizeIterator}, - marker::PhantomData, - ops::{Index, IndexMut}, - slice, -}; - -/// Types that can be used as indices for arenas. -pub trait ArenaIndex: Copy { - /// Converts the [`ArenaIndex`] into the underlying `usize` value. - fn into_usize(self) -> usize; - /// Converts the `usize` value into the associated [`ArenaIndex`]. - fn from_usize(value: usize) -> Self; -} - -/// An arena allocator with a given index and entity type. -/// -/// For performance reasons the arena cannot deallocate single entities. -#[derive(Debug)] -pub struct Arena { - entities: Vec, - marker: PhantomData, -} - -/// `Arena` does not store `Idx` therefore it is `Send` without its bound. -unsafe impl Send for Arena where T: Send {} - -/// `Arena` does not store `Idx` therefore it is `Sync` without its bound. -unsafe impl Sync for Arena where T: Send {} - -impl Default for Arena { - fn default() -> Self { - Self::new() - } -} - -impl PartialEq for Arena -where - T: PartialEq, -{ - fn eq(&self, other: &Self) -> bool { - self.entities.eq(&other.entities) - } -} - -impl Eq for Arena where T: Eq {} - -impl Arena { - /// Creates a new empty entity arena. - pub fn new() -> Self { - Self { - entities: Vec::new(), - marker: PhantomData, - } - } - - /// Returns the allocated number of entities. - #[inline] - pub fn len(&self) -> usize { - self.entities.len() - } - - /// Returns `true` if the arena has not yet allocated entities. - #[inline] - pub fn is_empty(&self) -> bool { - self.len() == 0 - } - - /// Clears all entities from the arena. - pub fn clear(&mut self) { - self.entities.clear(); - } - - /// Returns an iterator over the shared reference of the arena entities. - pub fn iter(&self) -> Iter { - Iter { - iter: self.entities.iter().enumerate(), - marker: PhantomData, - } - } - - /// Returns an iterator over the exclusive reference of the arena entities. - pub fn iter_mut(&mut self) -> IterMut { - IterMut { - iter: self.entities.iter_mut().enumerate(), - marker: PhantomData, - } - } -} - -impl Arena -where - Idx: ArenaIndex, -{ - /// Returns the next entity index. - fn next_index(&self) -> Idx { - Idx::from_usize(self.entities.len()) - } - - /// Allocates a new entity and returns its index. - #[inline] - pub fn alloc(&mut self, entity: T) -> Idx { - let index = self.next_index(); - self.entities.push(entity); - index - } - - /// Returns a shared reference to the entity at the given index if any. - #[inline] - pub fn get(&self, index: Idx) -> Option<&T> { - self.entities.get(index.into_usize()) - } - - /// Returns an exclusive reference to the entity at the given index if any. - #[inline] - pub fn get_mut(&mut self, index: Idx) -> Option<&mut T> { - self.entities.get_mut(index.into_usize()) - } - - /// Returns an exclusive reference to the pair of entities at the given indices if any. - /// - /// Returns `None` if `fst` and `snd` refer to the same entity. - /// Returns `None` if either `fst` or `snd` is invalid for this [`Arena`]. - #[inline] - pub fn get_pair_mut(&mut self, fst: Idx, snd: Idx) -> Option<(&mut T, &mut T)> { - let fst_index = fst.into_usize(); - let snd_index = snd.into_usize(); - if fst_index == snd_index { - return None; - } - if fst_index > snd_index { - let (fst, snd) = self.get_pair_mut(snd, fst)?; - return Some((snd, fst)); - } - // At this point we know that fst_index < snd_index. - let (fst_set, snd_set) = self.entities.split_at_mut(snd_index); - let fst = fst_set.get_mut(fst_index)?; - let snd = snd_set.get_mut(0)?; - Some((fst, snd)) - } -} - -impl FromIterator for Arena { - fn from_iter(iter: I) -> Self - where - I: IntoIterator, - { - Self { - entities: Vec::from_iter(iter), - marker: PhantomData, - } - } -} - -impl<'a, Idx, T> IntoIterator for &'a Arena -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a T); - type IntoIter = Iter<'a, Idx, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a, Idx, T> IntoIterator for &'a mut Arena -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a mut T); - type IntoIter = IterMut<'a, Idx, T>; - - fn into_iter(self) -> Self::IntoIter { - self.iter_mut() - } -} - -/// An iterator over shared references of arena entities and their indices. -#[derive(Debug)] -pub struct Iter<'a, Idx, T> { - iter: Enumerate>, - marker: PhantomData Idx>, -} - -impl<'a, Idx, T> Iterator for Iter<'a, Idx, T> -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a T); - - #[inline] - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(idx, entity)| (Idx::from_usize(idx), entity)) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, Idx, T> DoubleEndedIterator for Iter<'a, Idx, T> -where - Idx: ArenaIndex, -{ - #[inline] - fn next_back(&mut self) -> Option { - self.iter - .next() - .map(|(idx, entity)| (Idx::from_usize(idx), entity)) - } -} - -impl<'a, Idx, T> ExactSizeIterator for Iter<'a, Idx, T> -where - Idx: ArenaIndex, -{ - fn len(&self) -> usize { - self.iter.len() - } -} - -/// An iterator over exclusive references of arena entities and their indices. -#[derive(Debug)] -pub struct IterMut<'a, Idx, T> { - iter: Enumerate>, - marker: PhantomData Idx>, -} - -impl<'a, Idx, T> Iterator for IterMut<'a, Idx, T> -where - Idx: ArenaIndex, -{ - type Item = (Idx, &'a mut T); - - #[inline] - fn next(&mut self) -> Option { - self.iter - .next() - .map(|(idx, entity)| (Idx::from_usize(idx), entity)) - } - - #[inline] - fn size_hint(&self) -> (usize, Option) { - self.iter.size_hint() - } -} - -impl<'a, Idx, T> DoubleEndedIterator for IterMut<'a, Idx, T> -where - Idx: ArenaIndex, -{ - #[inline] - fn next_back(&mut self) -> Option { - self.iter - .next() - .map(|(idx, entity)| (Idx::from_usize(idx), entity)) - } -} - -impl<'a, Idx, T> ExactSizeIterator for IterMut<'a, Idx, T> -where - Idx: ArenaIndex, -{ - #[inline] - fn len(&self) -> usize { - self.iter.len() - } -} - -impl Arena { - /// Panics with an index out of bounds message. - fn index_out_of_bounds(len: usize, index: usize) -> ! { - panic!("index out of bounds: the len is {len} but the index is {index}") - } -} - -impl Index for Arena -where - Idx: ArenaIndex, -{ - type Output = T; - - #[inline] - fn index(&self, index: Idx) -> &Self::Output { - self.get(index) - .unwrap_or_else(|| Self::index_out_of_bounds(self.len(), index.into_usize())) - } -} - -impl IndexMut for Arena -where - Idx: ArenaIndex, -{ - #[inline] - fn index_mut(&mut self, index: Idx) -> &mut Self::Output { - let len = self.len(); - self.get_mut(index) - .unwrap_or_else(|| Self::index_out_of_bounds(len, index.into_usize())) - } -} diff --git a/legacy/src/arena/tests.rs b/legacy/src/arena/tests.rs deleted file mode 100644 index e7ce1ffef..000000000 --- a/legacy/src/arena/tests.rs +++ /dev/null @@ -1,161 +0,0 @@ -use super::*; - -impl ArenaIndex for usize { - fn into_usize(self) -> usize { - self - } - - fn from_usize(value: usize) -> Self { - value - } -} - -const TEST_ENTITIES: &[&str] = &["a", "b", "c", "d"]; - -mod arena { - use super::*; - - fn alloc_arena(entities: &[&'static str]) -> Arena { - let mut arena = >::new(); - // Check that the given arena is actually empty. - assert_eq!(arena.len(), 0); - assert!(arena.is_empty()); - // Fill arena and check invariants while doing so. - for idx in 0..entities.len() { - assert!(arena.get(idx).is_none()); - } - for (n, str) in entities.iter().enumerate() { - assert_eq!(arena.alloc(str), n); - } - // Check state of filled arena. - assert_eq!(arena.len(), entities.len()); - assert!(!arena.is_empty()); - for (n, str) in entities.iter().enumerate() { - assert_eq!(arena.get(n), Some(str)); - assert_eq!(&arena[n], str); - } - assert_eq!(arena.get(arena.len()), None); - // Return filled arena. - arena - } - - #[test] - fn alloc_works() { - alloc_arena(TEST_ENTITIES); - } - - #[test] - fn clear_works() { - let mut arena = alloc_arena(TEST_ENTITIES); - // Clear the arena and check if all elements are removed. - arena.clear(); - assert_eq!(arena.len(), 0); - assert!(arena.is_empty()); - for idx in 0..arena.len() { - assert_eq!(arena.get(idx), None); - } - assert_eq!(arena.get(arena.len()), None); - } - - #[test] - fn iter_works() { - let arena = alloc_arena(TEST_ENTITIES); - assert!(arena.iter().eq(TEST_ENTITIES.iter().enumerate())); - } - - #[test] - fn from_iter_works() { - let expected = alloc_arena(TEST_ENTITIES); - let actual = TEST_ENTITIES.iter().copied().collect::>(); - assert_eq!(actual, expected); - } - - #[test] - fn duplicates_work() { - let mut arena = alloc_arena(TEST_ENTITIES); - // Re-inserting the same entities into the filled arena will - // result in new and unique indices since the standard arena - // type does not deduplicate its entities. - let previous_len = arena.len(); - for (idx, str) in TEST_ENTITIES.iter().enumerate() { - let offset = previous_len + idx; - assert_eq!(arena.alloc(str), offset); - assert_eq!(arena.get(offset), Some(str)); - } - // Assert that the arena actually did increase in size since - // there is no deduplication of equal entities. - assert_eq!(arena.len(), previous_len + TEST_ENTITIES.len()); - } -} - -mod dedup_arena { - use super::*; - - fn alloc_dedup_arena(entities: &[&'static str]) -> DedupArena { - let mut arena = >::new(); - // Check that the given arena is actually empty. - assert_eq!(arena.len(), 0); - assert!(arena.is_empty()); - // Fill arena and check invariants while doing so. - for idx in 0..entities.len() { - assert!(arena.get(idx).is_none()); - } - for (n, str) in entities.iter().enumerate() { - assert_eq!(arena.alloc(str), n); - } - // Check state of filled arena. - assert_eq!(arena.len(), entities.len()); - assert!(!arena.is_empty()); - for (n, str) in entities.iter().enumerate() { - assert_eq!(arena.get(n), Some(str)); - assert_eq!(&arena[n], str); - } - assert_eq!(arena.get(arena.len()), None); - // Return filled arena. - arena - } - - #[test] - fn alloc_works() { - alloc_dedup_arena(TEST_ENTITIES); - } - - #[test] - fn clear_works() { - let mut arena = alloc_dedup_arena(TEST_ENTITIES); - // Clear the arena and check if all elements are removed. - arena.clear(); - assert_eq!(arena.len(), 0); - assert!(arena.is_empty()); - for idx in 0..arena.len() { - assert_eq!(arena.get(idx), None); - } - assert_eq!(arena.get(arena.len()), None); - } - - #[test] - fn iter_works() { - let arena = alloc_dedup_arena(TEST_ENTITIES); - assert!(arena.iter().eq(TEST_ENTITIES.iter().enumerate())); - } - - #[test] - fn from_iter_works() { - let expected = alloc_dedup_arena(TEST_ENTITIES); - let actual = TEST_ENTITIES.iter().copied().collect::>(); - assert_eq!(actual, expected); - } - - #[test] - fn duplicates_work() { - let mut arena = alloc_dedup_arena(TEST_ENTITIES); - // Re-inserting the same entities into the filled arena will - // yield back the same indices as their already allocated entities. - for (idx, str) in TEST_ENTITIES.iter().enumerate() { - assert_eq!(arena.alloc(str), idx); - assert_eq!(arena.get(idx), Some(str)); - } - // Assert that the deduplicating arena did not increase in size. - assert_eq!(arena.len(), TEST_ENTITIES.len()); - } -} diff --git a/legacy/src/core/import_linker.rs b/legacy/src/core/import_linker.rs deleted file mode 100644 index b92e01738..000000000 --- a/legacy/src/core/import_linker.rs +++ /dev/null @@ -1,53 +0,0 @@ -use crate::{core::ValueType, module::ImportName}; -use hashbrown::HashMap; -use crate::engine::bytecode::Instruction; - -#[derive(Debug, Default, Clone)] -pub struct ImportLinker { - func_by_name: HashMap, -} - -#[derive(Debug, Clone)] -pub struct ImportLinkerEntity { - pub func_idx: u32, - pub fuel_procedure: &'static [Instruction], - pub params: &'static [ValueType], - pub result: &'static [ValueType], -} - -impl From for ImportLinker -where - I: IntoIterator, -{ - fn from(iter: I) -> Self { - Self { - func_by_name: HashMap::from_iter(iter), - } - } -} - -impl ImportLinker { - pub fn insert_function( - &mut self, - import_name: ImportName, - func_idx: u32, - fuel_procedure: &'static [Instruction], - params: &'static [ValueType], - result: &'static [ValueType], - ) { - let last_value = self.func_by_name.insert( - import_name, - ImportLinkerEntity { - func_idx, - fuel_procedure, - 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/legacy/src/core/mod.rs b/legacy/src/core/mod.rs deleted file mode 100644 index c18ce4015..000000000 --- a/legacy/src/core/mod.rs +++ /dev/null @@ -1,48 +0,0 @@ -#![warn( - clippy::cast_lossless, - clippy::missing_errors_doc, - clippy::used_underscore_binding, - clippy::redundant_closure_for_method_calls, - clippy::type_repetition_in_bounds, - clippy::inconsistent_struct_constructor, - clippy::default_trait_access, - clippy::map_unwrap_or, - clippy::items_after_statements -)] - -mod host_error; -mod import_linker; -mod nan_preserving_float; -mod rwasm; -mod trap; -mod units; -mod untyped; -mod value; - -#[cfg(not(feature = "std"))] -extern crate alloc; - -#[cfg(feature = "std")] -extern crate std as alloc; - -use self::value::{ - ArithmeticOps, - ExtendInto, - Float, - Integer, - LittleEndianConvert, - SignExtendFrom, - TruncateSaturateInto, - TryTruncateInto, - WrapInto, -}; -pub use self::{ - host_error::HostError, - import_linker::*, - nan_preserving_float::{F32, F64}, - rwasm::*, - trap::{Trap, TrapCode}, - units::{Bytes, Pages}, - untyped::{DecodeUntypedSlice, EncodeUntypedSlice, UntypedError, UntypedValue}, - value::ValueType, -}; diff --git a/legacy/src/core/nan_preserving_float.rs b/legacy/src/core/nan_preserving_float.rs deleted file mode 100644 index bb455c062..000000000 --- a/legacy/src/core/nan_preserving_float.rs +++ /dev/null @@ -1,270 +0,0 @@ -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::thread_rng(); - let iter = iter::repeat(()).map(|_| rng.gen()); - - test_ops::(iter.take(1000)); - } - - #[test] - fn test_ops_f64() { - let mut rng = rand::thread_rng(); - let iter = iter::repeat(()).map(|_| rng.gen()); - - 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/legacy/src/core/rwasm.rs b/legacy/src/core/rwasm.rs deleted file mode 100644 index 257a88693..000000000 --- a/legacy/src/core/rwasm.rs +++ /dev/null @@ -1,19 +0,0 @@ -/// 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 proving process we have to limit max -/// number of pages, tables, etc. We found 1024 is enough. -pub const N_MAX_TABLES: usize = 1024; -pub const N_MAX_TABLE_ELEMENTS: u32 = 1024; - -pub const N_MAX_STACK_HEIGHT: usize = 4096; -pub const N_MAX_RECURSION_DEPTH: usize = 1024; - -/// Max possible amount of data segments -pub const N_MAX_DATA_SEGMENTS: usize = 1024; -pub const N_MAX_ELEM_SEGMENTS: usize = 1024; -pub const N_MAX_GLOBALS: usize = 1024; diff --git a/legacy/src/core/trap.rs b/legacy/src/core/trap.rs deleted file mode 100644 index b82748693..000000000 --- a/legacy/src/core/trap.rs +++ /dev/null @@ -1,330 +0,0 @@ -use crate::core::HostError; -use alloc::{boxed::Box, string::String}; -use core::fmt::{self, Display}; -#[cfg(feature = "std")] -use std::error::Error as StdError; - -/// Error type which can be returned by Wasm code or by the host environment. -/// -/// Under some conditions, Wasm execution may produce a [`Trap`], -/// which immediately aborts execution. -/// Traps cannot be handled by WebAssembly code, but are reported to the -/// host embedder. -#[derive(Debug)] -pub struct Trap { - /// The cloneable reason of a [`Trap`]. - reason: Box, -} - -#[test] -fn trap_size() { - assert_eq!( - core::mem::size_of::(), - core::mem::size_of::<*const ()>() - ); -} - -/// The reason of a [`Trap`]. -#[derive(Debug)] -enum TrapReason { - /// Traps during Wasm execution. - InstructionTrap(TrapCode), - /// An `i32` exit status code. - /// - /// # Note - /// - /// This is useful for some WASI functions. - I32Exit(i32), - /// An error decribed by a display message. - Message(Box), - /// Traps and errors during host execution. - Host(Box), -} - -impl TrapReason { - /// Returns the classic `i32` exit program code of a `Trap` if any. - /// - /// Otherwise returns `None`. - pub fn i32_exit_status(&self) -> Option { - if let Self::I32Exit(status) = self { - return Some(*status); - } - None - } - - /// Returns a shared reference to the [`HostError`] if any. - #[inline] - pub fn as_host(&self) -> Option<&dyn HostError> { - if let Self::Host(host_error) = self { - return Some(&**host_error); - } - None - } - - /// Returns an exclusive reference to the [`HostError`] if any. - #[inline] - pub fn as_host_mut(&mut self) -> Option<&mut dyn HostError> { - if let Self::Host(host_error) = self { - return Some(&mut **host_error); - } - None - } - - /// Consumes `self` to return the [`HostError`] if any. - #[inline] - pub fn into_host(self) -> Option> { - if let Self::Host(host_error) = self { - return Some(host_error); - } - None - } - - /// Returns the [`TrapCode`] traps originating from Wasm execution. - #[inline] - pub fn trap_code(&self) -> Option { - if let Self::InstructionTrap(trap_code) = self { - return Some(*trap_code); - } - None - } -} - -impl Trap { - /// Create a new [`Trap`] from the [`TrapReason`]. - fn with_reason(reason: TrapReason) -> Self { - Self { - reason: Box::new(reason), - } - } - - /// Creates a new [`Trap`] described by a `message`. - #[cold] // traps are exceptional, this helps move handling off the main path - pub fn new(message: T) -> Self - where - T: Into, - { - Self::with_reason(TrapReason::Message(message.into().into_boxed_str())) - } - - /// Downcasts the [`Trap`] into the `T: HostError` if possible. - /// - /// Returns `None` otherwise. - #[inline] - pub fn downcast_ref(&self) -> Option<&T> - where - T: HostError, - { - self.reason - .as_host() - .and_then(<(dyn HostError + 'static)>::downcast_ref) - } - - /// Downcasts the [`Trap`] into the `T: HostError` if possible. - /// - /// Returns `None` otherwise. - #[inline] - pub fn downcast_mut(&mut self) -> Option<&mut T> - where - T: HostError, - { - self.reason - .as_host_mut() - .and_then(<(dyn HostError + 'static)>::downcast_mut) - } - - /// Consumes `self` to downcast the [`Trap`] into the `T: HostError` if possible. - /// - /// Returns `None` otherwise. - #[inline] - pub fn downcast(self) -> Option - where - T: HostError, - { - self.reason - .into_host() - .and_then(|error| error.downcast().ok()) - .map(|boxed| *boxed) - } - - /// Creates a new `Trap` representing an explicit program exit with a classic `i32` - /// exit status value. - #[cold] // see Trap::new - pub fn i32_exit(status: i32) -> Self { - Self::with_reason(TrapReason::I32Exit(status)) - } - - /// Returns the classic `i32` exit program code of a `Trap` if any. - /// - /// Otherwise returns `None`. - #[inline] - pub fn i32_exit_status(&self) -> Option { - self.reason.i32_exit_status() - } - - /// Returns the [`TrapCode`] traps originating from Wasm execution. - #[inline] - pub fn trap_code(&self) -> Option { - self.reason.trap_code() - } -} - -impl From for Trap { - #[cold] // see Trap::new - fn from(error: TrapCode) -> Self { - Self::with_reason(TrapReason::InstructionTrap(error)) - } -} - -impl From for Trap -where - E: HostError, -{ - #[inline] - #[cold] // see Trap::new - fn from(host_error: E) -> Self { - Self::with_reason(TrapReason::Host(Box::new(host_error))) - } -} - -impl Display for TrapReason { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - Self::InstructionTrap(trap_code) => Display::fmt(trap_code, f), - Self::I32Exit(status) => write!(f, "Exited with i32 exit status {status}"), - Self::Message(message) => write!(f, "{message}"), - Self::Host(host_error) => Display::fmt(host_error, f), - } - } -} - -impl Display for Trap { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - ::fmt(&self.reason, f) - } -} - -#[cfg(feature = "std")] -impl StdError for Trap { - fn description(&self) -> &str { - self.trap_code().map_or("", |code| code.trap_message()) - } -} - -/// Error type which can be thrown by wasm code or by host environment. -/// -/// See [`Trap`] for details. -/// -/// [`Trap`]: struct.Trap.html -#[derive(Debug, Copy, Clone)] -pub enum TrapCode { - /// Wasm code executed `unreachable` opcode. - /// - /// This indicates that unreachable Wasm code was actually reached. - /// This opcode have a similar purpose as `ud2` in x86. - UnreachableCodeReached, - - /// Attempt to load or store at the address which - /// lies outside of bounds of the memory. - /// - /// Since addresses are interpreted as unsigned integers, out of bounds access - /// can't happen with negative addresses (i.e. they will always wrap). - MemoryOutOfBounds, - - /// Attempt to access table element at index which - /// lies outside of bounds. - /// - /// This typically can happen when `call_indirect` is executed - /// with index that lies out of bounds. - /// - /// Since indexes are interpreted as unsigned integers, out of bounds access - /// can't happen with negative indexes (i.e. they will always wrap). - TableOutOfBounds, - - /// Indicates that a `call_indirect` instruction called a function at - /// an uninitialized (i.e. `null`) table index. - IndirectCallToNull, - - /// Attempt to divide by zero. - /// - /// This trap typically can happen if `div` or `rem` is executed with - /// zero as divider. - IntegerDivisionByZero, - - /// An integer arithmetic operation caused an overflow. - /// - /// This can happen when trying to do signed division (or get the remainder) - /// -2N-1 over -1. This is because the result +2N-1 - /// isn't representable as a N-bit signed integer. - IntegerOverflow, - - /// Attempted to make an invalid conversion to an integer type. - /// - /// This can for example happen when trying to truncate NaNs, - /// infinity, or value for which the result is out of range into an integer. - BadConversionToInteger, - - /// Stack overflow. - /// - /// This is likely caused by some infinite or very deep recursion. - /// Extensive inlining might also be the cause of stack overflow. - StackOverflow, - - /// Attempt to invoke a function with mismatching signature. - /// - /// This can happen with indirect calls as they always - /// specify the expected signature of function. If an indirect call is executed - /// with an index that points to a function with signature different of what is - /// expected by this indirect call, this trap is raised. - BadSignature, - - /// This trap is raised when a WebAssembly execution ran out of fuel. - /// - /// The `wasmi` execution engine can be configured to instrument its - /// internal bytecode so that fuel is consumed for each executed instruction. - /// This is useful to deterministically halt or yield a WebAssembly execution. - OutOfFuel, - - /// This trap is raised when a growth operation was attempted and an - /// installed `wasmi::ResourceLimiter` returned `Err(...)` from the - /// associated `table_growing` or `memory_growing` method, indicating a - /// desire on the part of the embedder to trap the interpreter rather than - /// merely fail the growth operation. - GrowthOperationLimited, - - /// This error happens when we can't resolve function by its offset, usually - /// it should never happen. Maybe it's better to think how to replace this - /// error with panic. - UnresolvedFunction, -} - -impl TrapCode { - /// Returns the trap message as specified by the WebAssembly specification. - /// - /// # Note - /// - /// This API is primarily useful for the Wasm spec testsuite but might have - /// other uses since it avoid heap memory allocation in certain cases. - pub fn trap_message(&self) -> &'static str { - match self { - Self::UnreachableCodeReached => "wasm `unreachable` instruction executed", - Self::MemoryOutOfBounds => "out of bounds memory access", - Self::TableOutOfBounds => "undefined element: out of bounds table access", - Self::IndirectCallToNull => "uninitialized element 2", /* TODO: fixme, remove the */ - // trailing " 2" again - Self::IntegerDivisionByZero => "integer divide by zero", - Self::IntegerOverflow => "integer overflow", - Self::BadConversionToInteger => "invalid conversion to integer", - Self::StackOverflow => "call stack exhausted", - Self::BadSignature => "indirect call type mismatch", - Self::OutOfFuel => "all fuel consumed by WebAssembly", - Self::GrowthOperationLimited => "growth operation limited", - Self::UnresolvedFunction => "unresolved function by offset", - } - } -} - -impl Display for TrapCode { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.trap_message()) - } -} diff --git a/legacy/src/core/units.rs b/legacy/src/core/units.rs deleted file mode 100644 index a087fd07c..000000000 --- a/legacy/src/core/units.rs +++ /dev/null @@ -1,320 +0,0 @@ -/// 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 amount 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)) - } - - /// 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/legacy/src/core/untyped.rs b/legacy/src/core/untyped.rs deleted file mode 100644 index 26cb701d2..000000000 --- a/legacy/src/core/untyped.rs +++ /dev/null @@ -1,1655 +0,0 @@ -use crate::{ - core::{ - value::{LoadInto, StoreFrom}, - ArithmeticOps, - ExtendInto, - Float, - Integer, - LittleEndianConvert, - SignExtendFrom, - TrapCode, - TruncateSaturateInto, - TryTruncateInto, - ValueType, - WrapInto, - F32, - F64, - }, - value::split_i64_to_i32, -}; -use alloc::vec::Vec; -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)] -#[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 const fn from_bits(bits: u64) -> Self { - Self { bits } - } - /// Returns the underlying bits of the [`UntypedValue`]. - pub const 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(TrapCode::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<(), TrapCode> - 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<(), TrapCode> - 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<(), TrapCode> { - 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<(), TrapCode> { - 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<(), TrapCode> { - 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<(), TrapCode> { - 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<(), TrapCode> { - 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<(), TrapCode> { - 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<(), TrapCode> { - 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<(), TrapCode> { - 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<(), TrapCode> { - 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, 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() { - [ $( [< _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/legacy/src/core/value.rs b/legacy/src/core/value.rs deleted file mode 100644 index 88ce8c3d6..000000000 --- a/legacy/src/core/value.rs +++ /dev/null @@ -1,923 +0,0 @@ -use crate::core::{ - nan_preserving_float::{F32, F64}, - TrapCode, -}; -use core::{f32, i32, i64, u32, u64}; -use wasmparser::ValType; - -/// 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 ValueType { - /// Returns `true` if [`ValueType`] is a Wasm numeric type. - /// - /// This is `true` for [`ValueType::I32`], [`ValueType::I64`], - /// [`ValueType::F32`] and [`ValueType::F64`]. - pub fn is_num(&self) -> bool { - matches!(self, Self::I32 | Self::I64 | Self::F32 | Self::F64) - } - - /// Returns `true` if [`ValueType`] is a Wasm reference type. - /// - /// This is `true` for [`ValueType::FuncRef`] and [`ValueType::ExternRef`]. - pub fn is_ref(&self) -> bool { - matches!(self, Self::ExternRef | Self::FuncRef) - } -} - -impl From for ValueType { - fn from(value: ValType) -> Self { - match value { - ValType::I32 => ValueType::I32, - ValType::I64 => ValueType::I64, - ValType::F32 => ValueType::F32, - ValType::F64 => ValueType::F64, - ValType::FuncRef => ValueType::FuncRef, - ValType::ExternRef => ValueType::ExternRef, - _ => unreachable!("not supported local type ({:?})", value), - } - } -} - -/// 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<(), TrapCode>; -} - -impl LoadInto for [u8; N] { - #[inline] - fn load_into(&mut self, memory: &[u8], address: usize) -> Result<(), TrapCode> { - let slice: &Self = memory - .get(address..) - .and_then(|slice| slice.get(..N)) - .and_then(|slice| slice.try_into().ok()) - .ok_or(TrapCode::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<(), TrapCode>; -} - -impl StoreFrom for [u8; N] { - #[inline] - fn store_from(&self, memory: &mut [u8], address: usize) -> Result<(), TrapCode> { - let slice: &mut Self = memory - .get_mut(address..) - .and_then(|slice| slice.get_mut(..N)) - .and_then(|slice| slice.try_into().ok()) - .ok_or(TrapCode::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, TrapCode> for $from { - #[inline] - fn try_truncate_into(self) -> Result<$into, TrapCode> { - if self.is_nan() { - return Err(TrapCode::BadConversionToInteger); - } - if self <= $rmin || self >= $rmax { - return Err(TrapCode::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, TrapCode> for $from { - #[inline] - fn try_truncate_into(self) -> Result<$into, TrapCode> { - $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(TrapCode::IntegerDivisionByZero); - } - match self.overflowing_div(other) { - (result, false) => Ok(result), - _ => Err(TrapCode::IntegerOverflow), - } - } - #[inline] - fn rem(self, other: Self) -> Result { - if other == 0 { - return Err(TrapCode::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) - } - } -} diff --git a/legacy/src/engine/bytecode/instr_meta.rs b/legacy/src/engine/bytecode/instr_meta.rs deleted file mode 100644 index b6cb81806..000000000 --- a/legacy/src/engine/bytecode/instr_meta.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::engine::bytecode::Instruction; - -type InstrByteLen = usize; -type CommitByteLen = usize; -type IsSigned = bool; - -impl Instruction { - pub const MAX_BYTE_LEN: usize = 8; - pub fn store_instr_meta(instr: &Instruction) -> InstrByteLen { - match instr { - Instruction::I32Store(_) => 4, - Instruction::I32Store8(_) => 1, - Instruction::I32Store16(_) => 2, - Instruction::I64Store(_) => 8, - Instruction::I64Store8(_) => 1, - Instruction::I64Store16(_) => 2, - Instruction::I64Store32(_) => 4, - Instruction::F32Store(_) => 4, - Instruction::F64Store(_) => 8, - _ => unreachable!("unsupported opcode {:?}", instr), - } - } - - pub fn load_instr_meta(instr: &Instruction) -> (InstrByteLen, CommitByteLen, IsSigned) { - match instr { - Instruction::I32Load(_) => (4, 4, false), - Instruction::I64Load(_) => (8, 8, false), - Instruction::F32Load(_) => (4, 4, false), - Instruction::F64Load(_) => (8, 8, false), - Instruction::I32Load8S(_) => (4, 1, true), - Instruction::I32Load8U(_) => (4, 1, false), - Instruction::I32Load16S(_) => (4, 2, true), - Instruction::I32Load16U(_) => (4, 2, false), - Instruction::I64Load8S(_) => (8, 1, true), - Instruction::I64Load8U(_) => (8, 1, false), - Instruction::I64Load16S(_) => (8, 2, true), - Instruction::I64Load16U(_) => (8, 2, false), - Instruction::I64Load32S(_) => (8, 4, true), - Instruction::I64Load32U(_) => (8, 4, false), - _ => unreachable!("unsupported opcode {:?}", instr), - } - } -} diff --git a/legacy/src/engine/bytecode/mod.rs b/legacy/src/engine/bytecode/mod.rs deleted file mode 100644 index b4dda3ff6..000000000 --- a/legacy/src/engine/bytecode/mod.rs +++ /dev/null @@ -1,650 +0,0 @@ -//! The instruction architecture of the `wasmi` interpreter. - -mod utils; - -mod instr_meta; -mod stack_height; -#[cfg(test)] -mod tests; - -pub use self::utils::{ - AddressOffset, - BlockFuel, - BranchOffset, - BranchTableTargets, - DataSegmentIdx, - DropKeep, - DropKeepError, - ElementSegmentIdx, - F64Const32, - FuncIdx, - GlobalIdx, - LocalDepth, - SignatureIdx, - TableIdx, -}; -use super::{const_pool::ConstRef, CompiledFunc, TranslationError}; -use crate::core::{UntypedValue, F32}; -#[cfg(feature = "std")] -use core::{ - fmt, - fmt::{Debug, Formatter}, -}; - -/// The internal `wasmi` bytecode that is stored for Wasm functions. -/// -/// # Note -/// -/// This representation slightly differs from WebAssembly instructions. -/// -/// For example the `BrTable` instruction is unrolled into separate instructions -/// each representing either the `BrTable` head or one of its branching targets. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[cfg_attr(feature = "std", derive(strum_macros::EnumIter))] -pub enum Instruction { - LocalGet(LocalDepth), - LocalSet(LocalDepth), - LocalTee(LocalDepth), - /// An unconditional branch. - Br(BranchOffset), - /// Branches if the top-most stack value is equal to zero. - BrIfEqz(BranchOffset), - /// Branches if the top-most stack value is _not_ equal to zero. - BrIfNez(BranchOffset), - /// An unconditional branch. - /// - /// This operation also adjust the underlying value stack if necessary. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by a [`Instruction::Return`] - /// which stores information about the [`DropKeep`] behavior of the - /// [`Instruction::Br`]. The [`Instruction::Return`] will never be executed - /// and only acts as parameter storage for this instruction. - BrAdjust(BranchOffset), - /// Branches if the top-most stack value is _not_ equal to zero. - /// - /// This operation also adjust the underlying value stack if necessary. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by a [`Instruction::Return`] - /// which stores information about the [`DropKeep`] behavior of the - /// [`Instruction::BrIfNez`]. The [`Instruction::Return`] will never be executed - /// and only acts as parameter storage for this instruction. - BrAdjustIfNez(BranchOffset), - /// Branch table with a set number of branching targets. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by exactly as many unconditional - /// branch instructions as determined by [`BranchTableTargets`]. Branch - /// instructions that may follow are [`Instruction::Br] and [`Instruction::Return`]. - BrTable(BranchTableTargets), - Unreachable, - ConsumeFuel(BlockFuel), - Return(DropKeep), - ReturnIfNez(DropKeep), - /// Tail calls an internal (compiled) function. - /// - /// # Note - /// - /// This instruction can be used for calls to functions that are engine internal - /// (or compiled) and acts as an optimization for those common cases. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by an [`Instruction::Return`] that - /// encodes the [`DropKeep`] parameter. Note that the [`Instruction::Return`] - /// only acts as a storage for the parameter of the [`Instruction::ReturnCall`] - /// and will never be executed by itself. - ReturnCallInternal(CompiledFunc), - /// Tail calling `func`. - /// - /// # Note - /// - /// Since [`Instruction::ReturnCallInternal`] should be used for all functions internal - /// (or compiled) to the engine this instruction should mainly be used for tail calling - /// imported functions. However, it is a general form that can technically be used - /// for both. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by an [`Instruction::Return`] that - /// encodes the [`DropKeep`] parameter. Note that the [`Instruction::Return`] - /// only acts as a storage for the parameter of the [`Instruction::ReturnCall`] - /// and will never be executed by itself. - ReturnCall(FuncIdx), - /// Tail calling a function indirectly. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by an [`Instruction::Return`] that - /// encodes the [`DropKeep`] parameter as well as an [`Instruction::TableGet`] - /// that encodes the [`TableIdx`] parameter. Note that both, [`Instruction::Return`] - /// and [`Instruction::TableGet`] only act as a storage for parameters to the - /// [`Instruction::ReturnCallIndirect`] and will never be executed by themselves. - ReturnCallIndirect(SignatureIdx), - /// Calls an internal (compiled) function. - /// - /// # Note - /// - /// This instruction can be used for calls to functions that are engine internal - /// (or compiled) and acts as an optimization for those common cases. - CallInternal(CompiledFunc), - /// Calls the function. - /// - /// # Note - /// - /// Since [`Instruction::CallInternal`] should be used for all functions internal - /// (or compiled) to the engine this instruction should mainly be used for calling - /// imported functions. However, it is a general form that can technically be used - /// for both. - Call(FuncIdx), - /// Calling a function indirectly. - /// - /// # Encoding - /// - /// This [`Instruction`] must be followed by an [`Instruction::TableGet`] - /// that encodes the [`TableIdx`] parameter. Note that the [`Instruction::TableGet`] - /// only acts as a storage for the parameter of the [`Instruction::CallIndirect`] - /// and will never be executed by itself. - CallIndirect(SignatureIdx), - SignatureCheck(SignatureIdx), - StackAlloc { - max_stack_height: u32, - }, - Drop, - Select, - GlobalGet(GlobalIdx), - GlobalSet(GlobalIdx), - I32Load(AddressOffset), - I64Load(AddressOffset), - F32Load(AddressOffset), - F64Load(AddressOffset), - I32Load8S(AddressOffset), - I32Load8U(AddressOffset), - I32Load16S(AddressOffset), - I32Load16U(AddressOffset), - I64Load8S(AddressOffset), - I64Load8U(AddressOffset), - I64Load16S(AddressOffset), - I64Load16U(AddressOffset), - I64Load32S(AddressOffset), - I64Load32U(AddressOffset), - I32Store(AddressOffset), - I64Store(AddressOffset), - F32Store(AddressOffset), - F64Store(AddressOffset), - I32Store8(AddressOffset), - I32Store16(AddressOffset), - I64Store8(AddressOffset), - I64Store16(AddressOffset), - I64Store32(AddressOffset), - MemorySize, - MemoryGrow, - MemoryFill, - MemoryCopy, - MemoryInit(DataSegmentIdx), - DataDrop(DataSegmentIdx), - TableSize(TableIdx), - TableGrow(TableIdx), - TableFill(TableIdx), - TableGet(TableIdx), - TableSet(TableIdx), - /// Copies elements from one table to another. - /// - /// # Note - /// - /// It is also possible to copy elements within the same table. - /// - /// # Encoding - /// - /// The [`TableIdx`] referred to by the [`Instruction::TableCopy`] - /// represents the `dst` (destination) table. The [`Instruction::TableCopy`] - /// must be followed by an [`Instruction::TableGet`] which stores a - /// [`TableIdx`] that refers to the `src` (source) table. - TableCopy(TableIdx), - /// Initializes a table given an [`ElementSegmentIdx`]. - /// - /// # Encoding - /// - /// The [`Instruction::TableInit`] must be followed by an - /// [`Instruction::TableGet`] which stores a [`TableIdx`] - /// that refers to the table to be initialized. - TableInit(ElementSegmentIdx), - ElemDrop(ElementSegmentIdx), - RefFunc(FuncIdx), - /// A 32/64-bit constant value. - I32Const(UntypedValue), - I64Const(UntypedValue), - /// A 64-bit float value losslessly encoded as 32-bit float. - /// - /// Upon execution the 32-bit float is promoted to the 64-bit float. - /// - /// # Note - /// - /// This is a space-optimized variant of [`Instruction::ConstRef`] but can - /// only used for certain float values that fit into a 32-bit float value. - F32Const(UntypedValue), - F64Const(UntypedValue), - /// Pushes a constant value onto the stack. - /// - /// The constant value is referred to indirectly by the [`ConstRef`]. - ConstRef(ConstRef), - I32Eqz, - I32Eq, - I32Ne, - I32LtS, - I32LtU, - I32GtS, - I32GtU, - I32LeS, - I32LeU, - I32GeS, - I32GeU, - I64Eqz, - I64Eq, - I64Ne, - I64LtS, - I64LtU, - I64GtS, - I64GtU, - I64LeS, - I64LeU, - I64GeS, - I64GeU, - F32Eq, - F32Ne, - F32Lt, - F32Gt, - F32Le, - F32Ge, - F64Eq, - F64Ne, - F64Lt, - F64Gt, - F64Le, - F64Ge, - I32Clz, - I32Ctz, - I32Popcnt, - I32Add, - I32Sub, - I32Mul, - I32DivS, - I32DivU, - I32RemS, - I32RemU, - I32And, - I32Or, - I32Xor, - I32Shl, - I32ShrS, - I32ShrU, - I32Rotl, - I32Rotr, - I64Clz, - I64Ctz, - I64Popcnt, - I64Add, - I64Sub, - I64Mul, - I64DivS, - I64DivU, - I64RemS, - I64RemU, - I64And, - I64Or, - I64Xor, - I64Shl, - I64ShrS, - I64ShrU, - I64Rotl, - I64Rotr, - F32Abs, - F32Neg, - F32Ceil, - F32Floor, - F32Trunc, - F32Nearest, - F32Sqrt, - F32Add, - F32Sub, - F32Mul, - F32Div, - F32Min, - F32Max, - F32Copysign, - F64Abs, - F64Neg, - F64Ceil, - F64Floor, - F64Trunc, - F64Nearest, - F64Sqrt, - F64Add, - F64Sub, - F64Mul, - F64Div, - F64Min, - F64Max, - F64Copysign, - I32WrapI64, - I32TruncF32S, - I32TruncF32U, - I32TruncF64S, - I32TruncF64U, - I64ExtendI32S, - I64ExtendI32U, - I64TruncF32S, - I64TruncF32U, - I64TruncF64S, - I64TruncF64U, - F32ConvertI32S, - F32ConvertI32U, - F32ConvertI64S, - F32ConvertI64U, - F32DemoteF64, - F64ConvertI32S, - F64ConvertI32U, - F64ConvertI64S, - F64ConvertI64U, - F64PromoteF32, - I32Extend8S, - I32Extend16S, - I64Extend8S, - I64Extend16S, - I64Extend32S, - I32TruncSatF32S, - I32TruncSatF32U, - I32TruncSatF64S, - I32TruncSatF64U, - I64TruncSatF32S, - I64TruncSatF32U, - I64TruncSatF64S, - I64TruncSatF64U, -} - -#[cfg(feature = "std")] -impl fmt::Display for Instruction { - fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { - let name = format!("{:?}", self); - let name: Vec<_> = name.split('(').collect(); - write!(f, "{}", name[0]) - } -} - -impl Instruction { - /// Creates an [`Instruction::Const32`] from the given `i32` constant value. - pub fn i32_const(value: i32) -> Self { - Self::I32Const(UntypedValue::from(i64::from(value))) - } - - /// Creates an [`Instruction::Const32`] from the given `f32` constant value. - pub fn f32_const(value: F32) -> Self { - Self::F32Const(UntypedValue::from(value)) - } - - /// Creates a new `local.get` instruction from the given local depth. - /// - /// # Errors - /// - /// If the `local_depth` is out of bounds as local depth index. - pub fn local_get(local_depth: u32) -> Result { - Ok(Self::LocalGet(LocalDepth::from(local_depth))) - } - - /// Creates a new `local.set` instruction from the given local depth. - /// - /// # Errors - /// - /// If the `local_depth` is out of bounds as local depth index. - pub fn local_set(local_depth: u32) -> Result { - Ok(Self::LocalSet(LocalDepth::from(local_depth))) - } - - /// Creates a new `local.tee` instruction from the given local depth. - /// - /// # Errors - /// - /// If the `local_depth` is out of bounds as local depth index. - pub fn local_tee(local_depth: u32) -> Result { - Ok(Self::LocalTee(LocalDepth::from(local_depth))) - } - - /// Convenience method to create a new `ConsumeFuel` instruction. - pub fn consume_fuel(amount: u64) -> Result { - let block_fuel = BlockFuel::try_from(amount)?; - Ok(Self::ConsumeFuel(block_fuel)) - } - - pub fn is_supported(&self) -> bool { - match self { - Instruction::LocalGet(_) - | Instruction::LocalSet(_) - | Instruction::LocalTee(_) - | Instruction::Br(_) - | Instruction::BrIfEqz(_) - | Instruction::BrIfNez(_) - | Instruction::Unreachable - | Instruction::ConsumeFuel(_) - | Instruction::Return(_) - | Instruction::ReturnIfNez(_) - | Instruction::Call(_) - | Instruction::Drop - | Instruction::Select - | Instruction::GlobalGet(_) - | Instruction::GlobalSet(_) - | Instruction::I32Load(_) - | Instruction::I64Load(_) - | Instruction::F32Load(_) - | Instruction::F64Load(_) - | Instruction::I32Load8S(_) - | Instruction::I32Load8U(_) - | Instruction::I32Load16S(_) - | Instruction::I32Load16U(_) - | Instruction::I64Load8S(_) - | Instruction::I64Load8U(_) - | Instruction::I64Load16S(_) - | Instruction::I64Load16U(_) - | Instruction::I64Load32S(_) - | Instruction::I64Load32U(_) - | Instruction::I32Store(_) - | Instruction::I64Store(_) - | Instruction::F32Store(_) - | Instruction::F64Store(_) - | Instruction::I32Store8(_) - | Instruction::I32Store16(_) - | Instruction::I64Store8(_) - | Instruction::I64Store16(_) - | Instruction::I64Store32(_) - | Instruction::MemorySize - | Instruction::MemoryGrow - | Instruction::MemoryFill - | Instruction::MemoryCopy - | Instruction::MemoryInit(_) - | Instruction::DataDrop(_) - | Instruction::TableSize(_) - | Instruction::TableGrow(_) - | Instruction::TableFill(_) - | Instruction::TableGet(_) - | Instruction::TableSet(_) - | Instruction::TableCopy(_) - | Instruction::TableInit(_) - | Instruction::ElemDrop(_) - | Instruction::RefFunc(_) - | Instruction::I32Const(_) - | Instruction::I64Const(_) - | Instruction::I32Eqz - | Instruction::I32Eq - | Instruction::I32Ne - | Instruction::I32LtS - | Instruction::I32LtU - | Instruction::I32GtS - | Instruction::I32GtU - | Instruction::I32LeS - | Instruction::I32LeU - | Instruction::I32GeS - | Instruction::I32GeU - | Instruction::I64Eqz - | Instruction::I64Eq - | Instruction::I64Ne - | Instruction::I64LtS - | Instruction::I64LtU - | Instruction::I64GtS - | Instruction::I64GtU - | Instruction::I64LeS - | Instruction::I64LeU - | Instruction::I64GeS - | Instruction::I64GeU - | Instruction::F32Eq - | Instruction::F32Ne - | Instruction::F32Lt - | Instruction::F32Gt - | Instruction::F32Le - | Instruction::F32Ge - | Instruction::F64Eq - | Instruction::F64Ne - | Instruction::F64Lt - | Instruction::F64Gt - | Instruction::F64Le - | Instruction::F64Ge - | Instruction::I32Clz - | Instruction::I32Ctz - | Instruction::I32Popcnt - | Instruction::I32Add - | Instruction::I32Sub - | Instruction::I32Mul - | Instruction::I32DivS - | Instruction::I32DivU - | Instruction::I32RemS - | Instruction::I32RemU - | Instruction::I32And - | Instruction::I32Or - | Instruction::I32Xor - | Instruction::I32Shl - | Instruction::I32ShrS - | Instruction::I32ShrU - | Instruction::I32Rotl - | Instruction::I32Rotr - | Instruction::I64Clz - | Instruction::I64Ctz - | Instruction::I64Popcnt - | Instruction::I64Add - | Instruction::I64Sub - | Instruction::I64Mul - | Instruction::I64DivS - | Instruction::I64DivU - | Instruction::I64RemS - | Instruction::I64RemU - | Instruction::I64And - | Instruction::I64Or - | Instruction::I64Xor - | Instruction::I64Shl - | Instruction::I64ShrS - | Instruction::I64ShrU - | Instruction::I64Rotl - | Instruction::I64Rotr - | Instruction::F32Abs - | Instruction::F32Neg - | Instruction::F32Ceil - | Instruction::F32Floor - | Instruction::F32Trunc - | Instruction::F32Nearest - | Instruction::F32Sqrt - | Instruction::F32Add - | Instruction::F32Sub - | Instruction::F32Mul - | Instruction::F32Div - | Instruction::F32Min - | Instruction::F32Max - | Instruction::F32Copysign - | Instruction::F64Abs - | Instruction::F64Neg - | Instruction::F64Ceil - | Instruction::F64Floor - | Instruction::F64Trunc - | Instruction::F64Nearest - | Instruction::F64Sqrt - | Instruction::F64Add - | Instruction::F64Sub - | Instruction::F64Mul - | Instruction::F64Div - | Instruction::F64Min - | Instruction::F64Max - | Instruction::F64Copysign - | Instruction::I32WrapI64 - | Instruction::I32TruncF32S - | Instruction::I32TruncF32U - | Instruction::I32TruncF64S - | Instruction::I32TruncF64U - | Instruction::I64ExtendI32S - | Instruction::I64ExtendI32U - | Instruction::I64TruncF32S - | Instruction::I64TruncF32U - | Instruction::I64TruncF64S - | Instruction::I64TruncF64U - | Instruction::F32ConvertI32S - | Instruction::F32ConvertI32U - | Instruction::F32ConvertI64S - | Instruction::F32ConvertI64U - | Instruction::F32DemoteF64 - | Instruction::F64ConvertI32S - | Instruction::F64ConvertI32U - | Instruction::F64ConvertI64S - | Instruction::F64ConvertI64U - | Instruction::F64PromoteF32 - | Instruction::I32Extend8S - | Instruction::I32Extend16S - | Instruction::I64Extend8S - | Instruction::I64Extend16S - | Instruction::I64Extend32S - | Instruction::I32TruncSatF32S - | Instruction::I32TruncSatF32U - | Instruction::I32TruncSatF64S - | Instruction::I32TruncSatF64U - | Instruction::I64TruncSatF32S - | Instruction::I64TruncSatF32U - | Instruction::I64TruncSatF64S - | Instruction::I64TruncSatF64U => true, - _ => false, - } - } - - /// Increases the fuel consumption of the [`ConsumeFuel`] instruction by `delta`. - /// - /// # Panics - /// - /// - If `self` is not a [`ConsumeFuel`] instruction. - /// - If the new fuel consumption overflows the internal `u64` value. - /// - /// [`ConsumeFuel`]: Instruction::ConsumeFuel - pub fn bump_fuel_consumption(&mut self, delta: u64) -> Result<(), TranslationError> { - match self { - Self::ConsumeFuel(block_fuel) => block_fuel.bump_by(delta), - instr => panic!("expected Instruction::ConsumeFuel but found: {:?}", instr), - } - } -} - -#[derive(Default, Debug, Copy, Clone, PartialEq, Eq, Hash)] -pub struct InstrMeta(usize, u16, pub(crate) usize); - -impl InstrMeta { - pub fn new(pos: usize, code: u16, index: usize) -> Self { - Self(pos, code, index) - } - - pub fn offset(&self) -> usize { - self.0 - } - - pub fn opcode(&self) -> u16 { - self.1 - } - - pub fn index(&self) -> usize { - self.2 - } -} diff --git a/legacy/src/engine/bytecode/stack_height.rs b/legacy/src/engine/bytecode/stack_height.rs deleted file mode 100644 index cad031a13..000000000 --- a/legacy/src/engine/bytecode/stack_height.rs +++ /dev/null @@ -1,360 +0,0 @@ -use crate::engine::bytecode::Instruction; -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 Instruction { - 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 { - Instruction::LocalGet(local_depth) => { - stack_ops.push(RwOp::StackRead(local_depth.to_usize() as u32)); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::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)); - } - } - Instruction::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)); - } - } - Instruction::Br(_) => {} - Instruction::BrIfEqz(_) | Instruction::BrIfNez(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::BrAdjust(_) => {} - Instruction::BrAdjustIfNez(_) | Instruction::BrTable(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::Unreachable | Instruction::ConsumeFuel(_) | Instruction::Return(_) => {} - Instruction::ReturnIfNez(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::ReturnCallInternal(_) | Instruction::ReturnCall(_) => {} - Instruction::ReturnCallIndirect(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::CallInternal(_) => {} - Instruction::Call(_) => {} - Instruction::CallIndirect(_) => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::Drop => { - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::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)); - } - Instruction::GlobalGet(val) => { - stack_ops.push(RwOp::GlobalRead(val.to_u32())); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::GlobalSet(val) => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::GlobalWrite(val.to_u32())); - } - 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) => { - 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)); - } - 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) => { - 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, - }); - } - Instruction::MemorySize => { - stack_ops.push(RwOp::MemorySizeRead); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::MemoryGrow => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - stack_ops.push(RwOp::MemorySizeWrite); - } - Instruction::MemoryFill | Instruction::MemoryCopy => { - // unreachable!("not implemented here") - } - Instruction::MemoryInit(_) => {} - Instruction::DataDrop(_) => {} - - Instruction::TableSize(table_idx) => { - stack_ops.push(RwOp::TableSizeRead(table_idx.to_u32())); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::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)); - } - Instruction::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())); - } - Instruction::TableGet(_) => { - panic!("custom function is used"); - } - Instruction::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())); - } - Instruction::TableCopy(_) => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - } - Instruction::TableInit(_) => {} - - Instruction::ElemDrop(_) => {} - Instruction::RefFunc(_) => { - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::ConstRef(_) => stack_ops.push(RwOp::StackWrite(0)), - - Instruction::I32Eqz - | Instruction::I32Eq - | Instruction::I64Eqz - | Instruction::I64Eq - | Instruction::I32Ne - | Instruction::I64Ne => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - Instruction::I32LtS - | Instruction::I32LtU - | Instruction::I32GtS - | Instruction::I32GtU - | Instruction::I32LeS - | Instruction::I32LeU - | Instruction::I32GeS - | Instruction::I32GeU - | Instruction::I64LtS - | Instruction::I64LtU - | Instruction::I64GtS - | Instruction::I64GtU - | Instruction::I64LeS - | Instruction::I64LeU - | Instruction::I64GeS - | Instruction::I64GeU - | Instruction::F32Eq - | Instruction::F32Lt - | Instruction::F32Gt - | Instruction::F32Le - | Instruction::F32Ge - | Instruction::F32Ne - | Instruction::F64Eq - | Instruction::F64Ne - | Instruction::F64Lt - | Instruction::F64Gt - | Instruction::F64Le - | Instruction::F64Ge => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - Instruction::I32Clz - | Instruction::I64Clz - | Instruction::I32Ctz - | Instruction::I64Ctz - | Instruction::I32Popcnt - | Instruction::I64Popcnt => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - Instruction::I32Add - | Instruction::I32Sub - | Instruction::I32Mul - | Instruction::I32DivS - | Instruction::I32DivU - | Instruction::I32RemS - | Instruction::I32RemU - | Instruction::I32And - | Instruction::I32Or - | Instruction::I32Xor - | Instruction::I32Shl - | Instruction::I32ShrS - | Instruction::I32ShrU - | Instruction::I32Rotl - | Instruction::I32Rotr - | Instruction::I64Add - | Instruction::I64Sub - | Instruction::I64Mul - | Instruction::I64DivS - | Instruction::I64DivU - | Instruction::I64RemS - | Instruction::I64RemU - | Instruction::I64And - | Instruction::I64Or - | Instruction::I64Xor - | Instruction::I64Shl - | Instruction::I64ShrS - | Instruction::I64ShrU - | Instruction::I64Rotl - | Instruction::I64Rotr - | Instruction::F32Add - | Instruction::F32Sub - | Instruction::F32Mul - | Instruction::F32Div - | Instruction::F32Min - | Instruction::F32Max - | Instruction::F32Copysign - | Instruction::F64Add - | Instruction::F64Sub - | Instruction::F64Mul - | Instruction::F64Div - | Instruction::F64Min - | Instruction::F64Max - | Instruction::F64Copysign => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - Instruction::I32WrapI64 - | Instruction::I32TruncF32S - | Instruction::I32TruncF32U - | Instruction::I32TruncF64S - | Instruction::I32TruncF64U - | Instruction::I64ExtendI32S - | Instruction::I64ExtendI32U - | Instruction::I64TruncF32S - | Instruction::I64TruncF32U - | Instruction::I64TruncF64S - | Instruction::I64TruncF64U - | Instruction::F32ConvertI32S - | Instruction::F32ConvertI32U - | Instruction::F32ConvertI64S - | Instruction::F32ConvertI64U - | Instruction::F32DemoteF64 - | Instruction::F64ConvertI32S - | Instruction::F64ConvertI32U - | Instruction::F64ConvertI64S - | Instruction::F64ConvertI64U - | Instruction::F64PromoteF32 - | Instruction::I32Extend8S - | Instruction::I32Extend16S - | Instruction::I64Extend8S - | Instruction::I64Extend16S - | Instruction::I64Extend32S - | Instruction::I32TruncSatF32S - | Instruction::I32TruncSatF32U - | Instruction::I32TruncSatF64S - | Instruction::I32TruncSatF64U - | Instruction::I64TruncSatF32S - | Instruction::I64TruncSatF32U - | Instruction::I64TruncSatF64S - | Instruction::I64TruncSatF64U => { - stack_ops.push(RwOp::StackRead(0)); - stack_ops.push(RwOp::StackWrite(0)); - } - - Instruction::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/legacy/src/engine/bytecode/tests.rs b/legacy/src/engine/bytecode/tests.rs deleted file mode 100644 index 0a3a77328..000000000 --- a/legacy/src/engine/bytecode/tests.rs +++ /dev/null @@ -1,18 +0,0 @@ -use super::*; -use core::mem::size_of; - -#[test] -fn size_of_instruction() { - assert_eq!(size_of::(), 16); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); - assert_eq!(size_of::(), 4); -} diff --git a/legacy/src/engine/bytecode/utils.rs b/legacy/src/engine/bytecode/utils.rs deleted file mode 100644 index e488417a7..000000000 --- a/legacy/src/engine/bytecode/utils.rs +++ /dev/null @@ -1,429 +0,0 @@ -use crate::engine::{func_builder::TranslationErrorInner, Instr, TranslationError}; -use core::fmt::{self, Display}; - -/// 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)] -#[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 { - pub const fn from_u32(value: u32) -> Self { - Self(value) - } - /// Returns the index value as `u32`. - pub const fn to_u32(self) -> u32 { - self.0 - } -} - -/// A table index. -#[derive(Debug, Default, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] -#[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)] -#[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)] -#[repr(transparent)] -pub struct LocalDepth(u32); - -impl From for LocalDepth { - fn from(index: u32) -> Self { - Self(index) - } -} - -impl LocalDepth { - pub const fn from_u32(value: u32) -> Self { - Self(value) - } - /// Returns the depth as `usize` index. - pub const 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)] -#[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)] -#[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)] -#[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)] -#[repr(transparent)] -pub struct BranchTableTargets(u32); - -impl TryFrom for BranchTableTargets { - type Error = TranslationError; - - fn try_from(index: usize) -> Result { - match u32::try_from(index) { - Ok(index) => Ok(Self(index)), - Err(_) => Err(TranslationError::new( - TranslationErrorInner::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 - } -} - -/// 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)] -#[repr(transparent)] -pub struct BlockFuel(u32); - -impl TryFrom for BlockFuel { - type Error = TranslationError; - - fn try_from(index: u64) -> Result { - match u32::try_from(index) { - Ok(index) => Ok(Self(index)), - Err(_) => Err(TranslationError::new( - TranslationErrorInner::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<(), TranslationError> { - let new_amount = self - .to_u64() - .checked_add(amount) - .ok_or(TranslationErrorInner::BlockFuelOutOfBounds) - .map_err(TranslationError::new)?; - self.0 = u32::try_from(new_amount) - .map_err(|_| TranslationErrorInner::BlockFuelOutOfBounds) - .map_err(TranslationError::new)?; - Ok(()) - } - - /// Returns the index value as `u64`. - pub fn to_u64(self) -> u64 { - u64::from(self.0) - } -} - -/// A linear memory access offset. -/// -/// # Note -/// -/// Used to calculate the effective address of a linear memory access. -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -#[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 - } -} - -/// 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)] -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: Instr, dst: Instr) -> Result { - fn make_err() -> TranslationError { - TranslationError::new(TranslationErrorInner::BranchOffsetOutOfBounds) - } - let src = i64::from(src.into_u32()); - let dst = i64::from(dst.into_u32()); - 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 - } -} - -/// Defines how many stack values are going to be dropped and kept after branching. -#[derive(Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -pub struct DropKeep { - drop: u16, - 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 amount of kept elements exceeds the engine's limits. - KeepOutOfBounds, - /// The amount 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, "amount of kept elements exceeds engine limits") - } - DropKeepError::DropOutOfBounds => { - write!(f, "amount of dropped elements exceeds engine limits") - } - } - } -} - -impl DropKeep { - pub fn none() -> Self { - Self { drop: 0, keep: 0 } - } - - /// Returns the amount 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 amount 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/legacy/src/engine/cache.rs b/legacy/src/engine/cache.rs deleted file mode 100644 index 757d0e58f..000000000 --- a/legacy/src/engine/cache.rs +++ /dev/null @@ -1,376 +0,0 @@ -use super::bytecode::{DataSegmentIdx, ElementSegmentIdx, FuncIdx, GlobalIdx, TableIdx}; -use crate::{ - core::UntypedValue, - instance::InstanceEntity, - memory::DataSegment, - module::DEFAULT_MEMORY_INDEX, - table::TableEntity, - ElementSegment, - ElementSegmentEntity, - Func, - Instance, - Memory, - StoreInner, - Table, -}; -use core::ptr::NonNull; - -/// A cache for frequently used entities of an [`Instance`]. -#[derive(Debug)] -#[repr(C)] -pub struct InstanceCache { - /// The bytes of a default linear memory of the currently used [`Instance`]. - default_memory_bytes: Option>, - /// The last accessed global variable value of the currently used [`Instance`]. - last_global: Option<(GlobalIdx, NonNull)>, - /// The current instance in use. - instance: Instance, - /// The default linear memory of the currently used [`Instance`]. - default_memory: Option, - /// The last accessed table of the currently used [`Instance`]. - last_table: Option<(TableIdx, Table)>, - /// The last accessed function of the currently used [`Instance`]. - last_func: Option<(FuncIdx, Func)>, -} - -impl From<&'_ Instance> for InstanceCache { - fn from(instance: &Instance) -> Self { - Self { - instance: *instance, - default_memory: None, - last_table: None, - last_func: None, - last_global: None, - default_memory_bytes: None, - } - } -} - -impl InstanceCache { - /// Resolves the instances. - #[inline] - pub fn instance(&self) -> &Instance { - &self.instance - } - - /// Updates the cached [`Instance`]. - #[cold] - #[inline] - fn set_instance(&mut self, instance: &Instance) { - self.instance = *instance; - self.default_memory = None; - self.last_table = None; - self.last_func = None; - self.last_global = None; - self.default_memory_bytes = None; - } - - /// Updates the currently used instance resetting all cached entities. - #[inline] - pub fn update_instance(&mut self, instance: &Instance) { - if instance == self.instance() { - return; - } - self.set_instance(instance); - } - - /// Loads the [`DataSegment`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If there is no [`DataSegment`] for the [`Instance`] at the `index`. - #[inline] - pub fn get_data_segment(&mut self, ctx: &StoreInner, index: u32) -> DataSegment { - let instance = self.instance(); - ctx.resolve_instance(instance) - .get_data_segment(index) - .unwrap_or_else(|| { - unreachable!("missing data segment ({index:?}) for instance: {instance:?}",) - }) - } - - /// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If there is no [`ElementSegment`] for the [`Instance`] at the `index`. - #[inline] - pub fn get_element_segment( - &mut self, - ctx: &StoreInner, - index: ElementSegmentIdx, - ) -> ElementSegment { - let instance = self.instance(); - ctx.resolve_instance(instance) - .get_element_segment(index.to_u32()) - .unwrap_or_else(|| { - unreachable!("missing element segment ({index:?}) for instance: {instance:?}",) - }) - } - - /// Loads the [`DataSegment`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If there is no [`DataSegment`] for the [`Instance`] at the `index`. - #[inline] - pub fn get_default_memory_and_data_segment<'a>( - &mut self, - ctx: &'a mut StoreInner, - segment: DataSegmentIdx, - ) -> (&'a mut [u8], &'a [u8]) { - let seg = self.get_data_segment(ctx, segment.to_u32()); - let mem = self.default_memory(ctx); - let (memory, segment) = ctx.resolve_memory_mut_and_data_segment(mem, &seg); - (memory.data_mut(), segment.bytes()) - } - - /// Loads the [`ElementSegment`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If there is no [`ElementSegment`] for the [`Instance`] at the `index`. - #[inline] - pub fn get_table_and_element_segment<'a>( - &mut self, - ctx: &'a mut StoreInner, - table: TableIdx, - segment: ElementSegmentIdx, - ) -> ( - &'a InstanceEntity, - &'a mut TableEntity, - &'a ElementSegmentEntity, - ) { - let tab = self.get_table(ctx, table); - let seg = self.get_element_segment(ctx, segment); - let inst = self.instance(); - ctx.resolve_instance_table_element(inst, &tab, &seg) - } - - /// Loads the default [`Memory`] of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a default linear memory. - #[cold] - #[inline] - fn load_default_memory(&mut self, ctx: &StoreInner) -> &Memory { - let instance = self.instance(); - let default_memory = ctx - .resolve_instance(instance) - .get_memory(DEFAULT_MEMORY_INDEX) - .unwrap_or_else(|| { - unreachable!("missing default linear memory for instance: {instance:?}") - }); - self.default_memory.insert(default_memory) - } - - /// Returns the default [`Memory`] of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a default linear memory. - #[inline] - pub fn default_memory(&mut self, ctx: &StoreInner) -> &Memory { - match self.default_memory { - Some(ref default_memory) => default_memory, - None => self.load_default_memory(ctx), - } - } - - /// Returns a cached default linear memory. - /// - /// # Note - /// - /// This avoids one indirection compared to using the `default_memory`. - #[inline] - pub fn default_memory_bytes<'ctx>(&mut self, ctx: &'ctx mut StoreInner) -> &'ctx mut [u8] { - let bytes = match self.default_memory_bytes { - Some(ref mut cached) => cached, - None => self.load_default_memory_bytes(ctx), - }; - unsafe { bytes.as_mut() } - } - - /// Loads and populates the cached default memory instance. - /// - /// Returns an exclusive reference to the cached default memory. - #[cold] - #[inline] - fn load_default_memory_bytes(&mut self, ctx: &mut StoreInner) -> &mut NonNull<[u8]> { - let memory = *self.default_memory(ctx); - self.default_memory_bytes - .insert(ctx.resolve_memory_mut(&memory).data().into()) - } - - /// Clears the cached default memory instance. - /// - /// # Note - /// - /// - This is important when operations such as `memory.grow` have occured that might have - /// invalidated the cached memory. - /// - It is equally important to reset cached default memory bytes when calling a host function - /// since it might call `memory.grow`. - #[inline] - pub fn reset_default_memory_bytes(&mut self) { - self.default_memory_bytes = None; - self.last_global = None; - } - - /// Clears the cached default memory instance and global variable. - /// - /// # Note - /// - /// - This is required for host function calls for reasons explained in - /// [`InstanceCache::reset_default_memory_bytes`]. - /// - Furthermore a called host function could introduce new global variables to the [`Store`] - /// and thus might invalidate cached global variables. So we need to reset them as well. - /// - /// [`Store`]: crate::Store - #[inline] - pub fn reset(&mut self) { - self.reset_default_memory_bytes(); - self.last_global = None; - } - - /// Returns the [`Table`] at the `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a default table. - #[inline] - pub fn get_table(&mut self, ctx: &StoreInner, index: TableIdx) -> Table { - match self.last_table { - Some((table_index, table)) if index == table_index => table, - _ => self.load_table_at(ctx, index), - } - } - - /// Loads the [`Table`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have the table. - #[cold] - #[inline] - fn load_table_at(&mut self, ctx: &StoreInner, index: TableIdx) -> Table { - let table = ctx - .resolve_instance(self.instance()) - .get_table(index.to_u32()) - .unwrap_or_else(|| { - unreachable!( - "missing table at index {index:?} for instance: {:?}", - self.instance - ) - }); - self.last_table = Some((index, table)); - table - } - - /// Loads the [`Func`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have the function. - #[cold] - #[inline] - fn load_func_at(&mut self, ctx: &StoreInner, index: FuncIdx) -> Func { - let func = ctx - .resolve_instance(self.instance()) - .get_func(index.to_u32()) - .unwrap_or_else(|| { - unreachable!( - "missing func at index {index:?} for instance: {:?}", - self.instance - ) - }); - self.last_func = Some((index, func)); - func - } - - /// Loads the [`Func`] at `index` of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a [`Func`] at the index. - #[inline] - pub fn get_func(&mut self, ctx: &StoreInner, func_idx: FuncIdx) -> Func { - match self.last_func { - Some((index, func)) if index == func_idx => func, - _ => self.load_func_at(ctx, func_idx), - } - } - - /// Loads the pointer to the value of the global variable at `index` - /// of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a default table. - #[cold] - #[inline] - fn load_global_at(&mut self, ctx: &mut StoreInner, index: GlobalIdx) -> NonNull { - let global = ctx - .resolve_instance(self.instance()) - .get_global(index.to_u32()) - .as_ref() - .map(|global| ctx.resolve_global_mut(global).get_untyped_ptr()) - .unwrap_or_else(|| { - unreachable!( - "missing global variable at index {index:?} for instance: {:?}", - self.instance - ) - }); - self.last_global = Some((index, global)); - global - } - - /// Returns a pointer to the value of the global variable at `index` - /// of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a [`Func`] at the index. - #[inline(always)] - fn get_global_mut<'ctx>( - &mut self, - ctx: &'ctx mut StoreInner, - global_index: GlobalIdx, - ) -> &'ctx mut UntypedValue { - let mut ptr = match self.last_global { - Some((index, global)) if index == global_index => global, - _ => self.load_global_at(ctx, global_index), - }; - // SAFETY: This deref is safe since we only hold this pointer - // as long as we are sure that nothing else can manipulate - // the global in a way that would invalidate the pointer. - unsafe { ptr.as_mut() } - } - - /// Returns a pointer to the value of the global variable at `index` - /// of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a [`Func`] at the index. - #[inline(always)] - pub fn get_global(&mut self, ctx: &mut StoreInner, global_index: GlobalIdx) -> UntypedValue { - *self.get_global_mut(ctx, global_index) - } - - /// Returns a pointer to the value of the global variable at `index` - /// of the currently used [`Instance`]. - /// - /// # Panics - /// - /// If the currently used [`Instance`] does not have a [`Func`] at the index. - #[inline(always)] - pub fn set_global( - &mut self, - ctx: &mut StoreInner, - global_index: GlobalIdx, - new_value: UntypedValue, - ) { - *self.get_global_mut(ctx, global_index) = new_value; - } -} diff --git a/legacy/src/engine/code_map.rs b/legacy/src/engine/code_map.rs deleted file mode 100644 index 0deeaaf51..000000000 --- a/legacy/src/engine/code_map.rs +++ /dev/null @@ -1,389 +0,0 @@ -//! Datastructure to efficiently store function bodies and their instructions. - -use super::Instruction; -use crate::{arena::ArenaIndex, engine::bytecode::InstrMeta}; -use alloc::vec::Vec; -use hashbrown::HashMap; - -/// A reference to a compiled function stored in the [`CodeMap`] of an [`Engine`](crate::Engine). -#[derive(Debug, Copy, Clone, PartialEq, Eq, Default, Hash, PartialOrd, Ord)] -pub struct CompiledFunc(u32); - -impl ArenaIndex for CompiledFunc { - fn into_usize(self) -> usize { - self.0 as usize - } - - fn from_usize(index: usize) -> Self { - let index = u32::try_from(index) - .unwrap_or_else(|_| panic!("out of bounds compiled func index: {index}")); - CompiledFunc(index) - } -} - -impl From for CompiledFunc { - fn from(value: u32) -> Self { - Self(value) - } -} - -impl CompiledFunc { - pub fn to_u32(&self) -> u32 { - self.0 - } -} - -/// A reference to the instructions of a compiled Wasm function. -#[derive(Debug, Copy, Clone)] -pub struct InstructionsRef { - /// The start index in the instructions array. - index: usize, -} - -impl InstructionsRef { - /// Creates a new valid [`InstructionsRef`] for the given `index`. - /// - /// # Note - /// - /// The `index` denotes the index of the first instruction in the sequence - /// of instructions denoted by [`InstructionsRef`]. - /// - /// # Panics - /// - /// If `index` is 0 since the zero index is reserved for uninitialized [`InstructionsRef`]. - fn new(index: usize) -> Self { - assert_ne!(index, 0, "must initialize with a proper non-zero index"); - Self { index } - } - - /// Creates a new uninitialized [`InstructionsRef`]. - pub fn uninit() -> Self { - Self { index: 0 } - } - - /// Returns `true` if the [`InstructionsRef`] refers to an uninitialized sequence of - /// instructions. - fn is_uninit(self) -> bool { - self.index == 0 - } - - /// Returns the `usize` value of the underlying index. - fn to_usize(self) -> usize { - self.index - } -} - -/// Meta information about a compiled function. -#[derive(Debug, Copy, Clone)] -pub struct FuncHeader { - /// A reference to the instructions of the function. - iref: InstructionsRef, - /// The number of local variables of the function. - len_locals: usize, - /// The maximum stack height usage of the function during execution. - max_stack_height: usize, -} - -impl FuncHeader { - /// Create a new initialized [`FuncHeader`]. - pub fn new(iref: InstructionsRef, len_locals: usize, local_stack_height: usize) -> Self { - let max_stack_height = local_stack_height - .checked_add(len_locals) - .unwrap_or_else(|| panic!("invalid maximum stack height for function")); - Self { - iref, - len_locals, - max_stack_height, - } - } - - /// Create a new uninitialized [`FuncHeader`]. - pub fn uninit() -> Self { - Self { - iref: InstructionsRef::uninit(), - len_locals: 0, - max_stack_height: 0, - } - } - - /// Returns `true` if the [`FuncHeader`] is uninitialized. - pub fn is_uninit(&self) -> bool { - self.iref.is_uninit() - } - - /// Returns a reference to the instructions of the function. - pub fn iref(&self) -> InstructionsRef { - self.iref - } - - /// Returns the amount of local variable of the function. - pub fn len_locals(&self) -> usize { - self.len_locals - } - - /// Returns the amount of stack values required by the function. - /// - /// # Note - /// - /// This amount includes the amount of local variables but does - /// _not_ include the amount of input parameters to the function. - pub fn max_stack_height(&self) -> usize { - self.max_stack_height - } -} - -/// Datastructure to efficiently store Wasm function bodies. -#[derive(Debug)] -pub struct CodeMap { - /// The headers of all compiled functions. - headers: Vec, - index_by_offset: HashMap, - /// The instructions of all allocated function bodies. - /// - /// By storing all `wasmi` bytecode instructions in a single - /// allocation we avoid an indirection when calling a function - /// compared to a solution that stores instructions of different - /// function bodies in different allocations. - /// - /// Also, this improves efficiency of deallocating the [`CodeMap`] - /// and generally improves data locality. - instrs: Vec, - metas: Vec, -} - -impl Default for CodeMap { - fn default() -> Self { - Self { - headers: Vec::new(), - index_by_offset: Default::default(), - // The first instruction always is a simple trapping instruction - // so that we safely can use `InstructionsRef(0)` as an uninitialized - // index value for compiled functions that have yet to be - // initialized with their actual function bodies. - instrs: vec![Instruction::Unreachable], - metas: vec![InstrMeta::default()], - } - } -} - -impl CodeMap { - /// Allocates a new uninitialized [`CompiledFunc`] to the [`CodeMap`]. - /// - /// # Note - /// - /// The uninitialized [`CompiledFunc`] must be initialized using - /// [`CodeMap::init_func`] before it is executed. - pub fn alloc_func(&mut self) -> CompiledFunc { - let header_index = self.headers.len(); - self.headers.push(FuncHeader::uninit()); - CompiledFunc::from_usize(header_index) - } - - /// Initializes the [`CompiledFunc`]. - /// - /// # Panics - /// - /// - If `func` is an invalid [`CompiledFunc`] reference for this [`CodeMap`]. - /// - If `func` refers to an already initialized [`CompiledFunc`]. - pub fn init_func( - &mut self, - func: CompiledFunc, - len_locals: usize, - local_stack_height: usize, - instrs: I, - metas: M, - ) where - I: IntoIterator, - M: IntoIterator, - { - assert!( - self.header(func).is_uninit(), - "func {func:?} is already initialized" - ); - let start = self.instrs.len(); - self.instrs.extend(instrs); - self.metas.extend(metas); - let iref = InstructionsRef::new(start); - self.headers[func.into_usize()] = FuncHeader::new(iref, len_locals, local_stack_height); - } - - pub fn mark_func( - &mut self, - func: CompiledFunc, - len_locals: usize, - local_stack_height: usize, - start: usize, - ) { - // first byte is reserved for unreachable - let start = start + 1; - assert!( - self.header(func).is_uninit(), - "func {func:?} is already initialized" - ); - let iref = InstructionsRef::new(start); - assert!( - start < self.instrs.len(), - "instruction overflow ({} > {})", - start, - self.instrs.len() - ); - self.headers[func.into_usize()] = FuncHeader::new(iref, len_locals, local_stack_height); - assert!( - !self.index_by_offset.contains_key(&start), - "function with such offset already exists" - ); - self.index_by_offset.insert(start - 1, func); - } - - pub fn resolve_function_by_offset(&self, offset: usize) -> Option { - self.index_by_offset.get(&offset).copied() - } - - /// Returns an [`InstructionPtr`] to the instruction at [`InstructionsRef`]. - #[inline] - pub fn instr_ptr(&self, iref: InstructionsRef) -> InstructionPtr { - InstructionPtr::new( - self.instrs[iref.to_usize()..].as_ptr(), - self.metas[iref.to_usize()..].as_ptr(), - ) - } - - /// Returns an [`InstructionPtr`] to the instruction at [`InstructionsRef`]. - #[inline] - pub fn instr_ptr_with_end(&self, func_body: CompiledFunc) -> (InstructionPtr, InstructionPtr) { - let header = self.header(func_body); - let start = header.iref.to_usize(); - let end = self.instr_end(func_body); - let start_ptr = InstructionPtr::new( - self.instrs[start..end].as_ptr(), - self.metas[start..end].as_ptr(), - ); - let mut end_ptr = start_ptr; - end_ptr.add(end - start); - (start_ptr, end_ptr) - } - - /// Returns the [`FuncHeader`] of the [`CompiledFunc`]. - pub fn header(&self, func_body: CompiledFunc) -> &FuncHeader { - &self.headers[func_body.into_usize()] - } - - /// Resolves the instruction at `index` of the compiled [`CompiledFunc`]. - pub fn get_instr(&self, func_body: CompiledFunc, index: usize) -> Option<&Instruction> { - let header = self.header(func_body); - let start = header.iref.to_usize(); - let end = self.instr_end(func_body); - let instrs = &self.instrs[start..end]; - instrs.get(index) - } - - pub fn instr_vec(&self, func_body: CompiledFunc) -> Vec { - let header = self.header(func_body); - let start = header.iref.index; - let end = self.instr_end(func_body); - self.instrs[start..end].to_vec() - } - - pub fn num_locals(&self, func_body: CompiledFunc) -> u32 { - let header = self.header(func_body); - header.len_locals as u32 - } - - /// Returns the `end` index of the instructions of [`CompiledFunc`]. - /// - /// This is important to synthesize how many instructions there are in - /// the function referred to by [`CompiledFunc`]. - pub fn instr_end(&self, func_body: CompiledFunc) -> usize { - self.headers - .get(func_body.into_usize() + 1) - .map(|header| header.iref.to_usize()) - .unwrap_or(self.instrs.len()) - } -} - -/// 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, - /// The pointer to metas - pub(crate) meta: *const InstrMeta, -} - -/// It is safe to send an [`InstructionPtr`] to another thread. -/// -/// The access to the pointed-to [`Instruction`] is read-only and -/// [`Instruction`] itself is [`Send`]. -/// -/// However, it is not safe to share an [`InstructionPtr`] between threads -/// due to their [`InstructionPtr::offset`] method which relinks the -/// internal pointer and is not synchronized. -unsafe impl Send for InstructionPtr {} - -impl InstructionPtr { - /// Creates a new [`InstructionPtr`] for `instr`. - #[inline] - pub fn new(ptr: *const Instruction, meta: *const InstrMeta) -> Self { - Self { - ptr, - src: ptr, - meta, - } - } - - #[inline(always)] - pub fn pc(&self) -> u32 { - let size = core::mem::size_of::() as u32; - let diff = self.ptr as u32 - self.src as u32; - diff / size - } - - /// Offset the [`InstructionPtr`] by the given value. - /// - /// # Safety - /// - /// The caller is responsible for calling this method only with valid - /// offset values so that the [`InstructionPtr`] never points out of valid - /// bounds of the instructions of the same compiled Wasm function. - #[inline(always)] - pub fn offset(&mut self, by: 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. - self.ptr = unsafe { self.ptr.offset(by) }; - self.meta = unsafe { self.meta.offset(by) }; - } - - #[inline(always)] - pub fn add(&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) }; - self.meta = unsafe { self.meta.add(delta) }; - } - - /// Returns a shared reference to the currently pointed at [`Instruction`]. - /// - /// # Safety - /// - /// The caller is responsible for calling this method only when it is - /// guaranteed that the [`InstructionPtr`] is validly pointing inside - /// the boundaries of its associated compiled Wasm function. - #[inline(always)] - pub fn get(&self) -> &Instruction { - // 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 } - } - - #[inline(always)] - pub fn meta(&self) -> &InstrMeta { - // 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.meta } - } -} diff --git a/legacy/src/engine/const_pool.rs b/legacy/src/engine/const_pool.rs deleted file mode 100644 index fb895dfc1..000000000 --- a/legacy/src/engine/const_pool.rs +++ /dev/null @@ -1,115 +0,0 @@ -use super::{func_builder::TranslationErrorInner, TranslationError}; -use crate::core::UntypedValue; -use alloc::{ - collections::{btree_map, BTreeMap}, - vec::Vec, -}; - -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Hash)] -pub struct ConstRef(u32); - -impl TryFrom for ConstRef { - type Error = TranslationError; - - fn try_from(index: usize) -> Result { - match u32::try_from(index) { - Ok(index) => Ok(Self(index)), - Err(_) => Err(TranslationError::new( - TranslationErrorInner::ConstRefOutOfBounds, - )), - } - } -} - -impl From for ConstRef { - fn from(value: u32) -> Self { - ConstRef(value) - } -} - -impl ConstRef { - /// Returns the index of the [`ConstRef`] as `usize` value. - pub fn to_usize(self) -> usize { - self.0 as usize - } -} - -/// A pool of deduplicated reusable constant values. -/// -/// - Those constant values are identified by their associated [`ConstRef`]. This type exists so -/// that the `wasmi` bytecode can extract large constant values to this pool instead of storing -/// their values inline. -/// - All constant values are also deduplicated so that no duplicates are stored in a single -/// [`ConstPool`]. This also means that deciding if two [`ConstRef`] values refer to the equal -/// constant values can be efficiently done by comparing the [`ConstRef`] indices without -/// resolving to their underlying constant values. -#[derive(Debug, Default)] -pub struct ConstPool { - /// Mapping from constant [`UntypedValue`] values to [`ConstRef`] indices. - const2idx: BTreeMap, - /// Mapping from [`ConstRef`] indices to constant [`UntypedValue`] values. - idx2const: Vec, -} - -impl ConstPool { - /// Allocates a new constant `value` on the [`ConstPool`] and returns its identifier. - /// - /// # Note - /// - /// If the constant `value` already exists in this [`ConstPool`] no new value is - /// allocated and the identifier of the existing constant `value` returned instead. - /// - /// # Errors - /// - /// If too many constant values have been allocated for this [`ConstPool`]. - pub fn alloc(&mut self, value: UntypedValue) -> Result { - match self.const2idx.entry(value) { - btree_map::Entry::Occupied(entry) => Ok(*entry.get()), - btree_map::Entry::Vacant(entry) => { - let idx = self.idx2const.len(); - let cref = ConstRef::try_from(idx)?; - entry.insert(cref); - self.idx2const.push(value); - Ok(cref) - } - } - } - - /// Returns the [`UntypedValue`] for the given [`ConstRef`] if existing. - /// - /// Returns `None` is the [`ConstPool`] does not store a value for the [`ConstRef`]. - /// - /// # Note - /// - /// This API is mainly used and useful in testing code. - #[allow(dead_code)] - pub fn get(&self, cref: ConstRef) -> Option { - self.idx2const.get(cref.to_usize()).copied() - } - - /// Returns the read-only [`ConstPoolView`] of this [`ConstPool`]. - pub fn view(&self) -> ConstPoolView { - ConstPoolView { - idx2const: &self.idx2const, - } - } -} - -/// A read-only view of a [`ConstPool`]. -/// -/// This allows for a more efficient access to the underlying constant -/// [`UntypedValue`] values given their associated [`ConstRef`] indices. -#[derive(Debug)] -pub struct ConstPoolView<'a> { - /// Mapping from [`ConstRef`] indices to constant [`UntypedValue`] values. - idx2const: &'a [UntypedValue], -} - -impl ConstPoolView<'_> { - /// Returns the [`UntypedValue`] for the given [`ConstRef`] if existing. - /// - /// Returns `None` is the [`ConstPool`] does not store a value for the [`ConstRef`]. - pub fn get(&self, cref: ConstRef) -> Option { - self.idx2const.get(cref.to_usize()).copied() - } -} diff --git a/legacy/src/engine/executor.rs b/legacy/src/engine/executor.rs deleted file mode 100644 index 4e7e9c6a9..000000000 --- a/legacy/src/engine/executor.rs +++ /dev/null @@ -1,1852 +0,0 @@ -use super::{bytecode::BranchOffset, const_pool::ConstRef, CompiledFunc, ConstPoolView}; -use crate::{ - arena::ArenaIndex, - core::{Pages, TrapCode, UntypedValue}, - engine::{ - bytecode::{ - AddressOffset, - BlockFuel, - BranchTableTargets, - DataSegmentIdx, - ElementSegmentIdx, - FuncIdx, - GlobalIdx, - Instruction, - LocalDepth, - SignatureIdx, - TableIdx, - }, - cache::InstanceCache, - code_map::{CodeMap, InstructionPtr}, - config::FuelCosts, - stack::{CallStack, ValueStackPtr}, - tracer::Tracer, - DropKeep, - FuncFrame, - ValueStack, - }, - func::FuncEntity, - module::DEFAULT_MEMORY_INDEX, - store::ResourceLimiterRef, - table::{ElementSegmentEntity, TableEntity}, - FuelConsumptionMode, - Func, - FuncRef, - Instance, - StoreInner, - Table, -}; -use alloc::string::String; -use core::cmp::{self}; - -/// The outcome of a Wasm execution. -/// -/// # Note -/// -/// A Wasm execution includes everything but host calls. -/// In other words: Everything in between host calls is a Wasm execution. -#[derive(Debug, Copy, Clone)] -pub enum WasmOutcome { - /// The Wasm execution has ended and returns to the host side. - Return, - /// The Wasm execution calls a host function. - Call { host_func: Func, instance: Instance }, -} - -/// The outcome of a Wasm execution. -/// -/// # Note -/// -/// A Wasm execution includes everything but host calls. -/// In other words: Everything in between host calls is a Wasm execution. -#[derive(Debug, Copy, Clone)] -pub enum CallOutcome { - /// The Wasm execution continues in Wasm. - Continue, - /// The Wasm execution calls a host function. - Call { host_func: Func, instance: Instance }, -} - -/// The kind of a function call. -#[derive(Debug, Copy, Clone)] -pub enum CallKind { - /// A nested function call. - Nested, - /// A tailing function call. - Tail, -} - -/// The outcome of a Wasm return statement. -#[derive(Debug, Copy, Clone)] -pub enum ReturnOutcome { - /// The call returns to a nested Wasm caller. - Wasm, - /// The call returns back to the host. - Host, -} - -/// Executes the given function `frame`. -/// -/// # Note -/// -/// This executes Wasm instructions until either the execution calls -/// into a host function or the Wasm execution has come to an end. -/// -/// # Errors -/// -/// If the Wasm execution traps. -#[inline(never)] -pub fn execute_wasm<'ctx, 'engine>( - ctx: &'ctx mut StoreInner, - cache: &'engine mut InstanceCache, - value_stack: &'engine mut ValueStack, - call_stack: &'engine mut CallStack, - code_map: &'engine CodeMap, - const_pool: ConstPoolView<'engine>, - resource_limiter: &'ctx mut ResourceLimiterRef<'ctx>, - tracer: Option<&'engine mut Tracer>, -) -> Result { - Executor::new( - ctx, - cache, - value_stack, - call_stack, - code_map, - const_pool, - tracer, - ) - .execute(resource_limiter) -} - -/// The function signature of Wasm load operations. -type WasmLoadOp = - fn(memory: &[u8], address: UntypedValue, offset: u32) -> Result; - -/// The function signature of Wasm store operations. -type WasmStoreOp = fn( - memory: &mut [u8], - address: UntypedValue, - offset: u32, - value: UntypedValue, -) -> Result<(), TrapCode>; - -/// An error that can occur upon `memory.grow` or `table.grow`. -#[derive(Copy, Clone)] -pub enum EntityGrowError { - /// Usually a [`TrapCode::OutOfFuel`] trap. - TrapCode(TrapCode), - /// Encountered when `memory.grow` or `table.grow` fails. - InvalidGrow, -} - -impl From for EntityGrowError { - fn from(trap_code: TrapCode) -> Self { - Self::TrapCode(trap_code) - } -} - -/// The WebAssembly specification demands to return this value -/// if the `memory.grow` or `table.grow` operations fail. -const INVALID_GROWTH_ERRCODE: u32 = u32::MAX; - -/// An execution context for executing a `wasmi` function frame. -#[derive(Debug)] -struct Executor<'ctx, 'engine> { - /// Stores the value stack of live values on the Wasm stack. - sp: ValueStackPtr, - /// The pointer to the currently executed instruction. - ip: InstructionPtr, - /// Stores frequently used instance related data. - cache: &'engine mut InstanceCache, - /// A mutable [`StoreInner`] context. - /// - /// [`StoreInner`]: [`crate::StoreInner`] - ctx: &'ctx mut StoreInner, - /// The value stack. - /// - /// # Note - /// - /// This reference is mainly used to synchronize back state - /// after manipulations to the value stack via `sp`. - value_stack: &'engine mut ValueStack, - /// The call stack. - /// - /// # Note - /// - /// This is used to store the stack of nested function calls. - call_stack: &'engine mut CallStack, - /// The Wasm function code map. - /// - /// # Note - /// - /// This is used to lookup Wasm function information. - code_map: &'engine CodeMap, - /// A read-only view to a pool of constant values. - const_pool: ConstPoolView<'engine>, - /// A tracer that stores execution info - tracer: Option<&'engine mut Tracer>, - /// Store an information about last signature used by IndirectCall - last_signature: Option, -} - -macro_rules! forward_call { - ($expr:expr) => {{ - if let CallOutcome::Call { - host_func, - instance, - } = $expr? - { - return Ok(WasmOutcome::Call { - host_func, - instance, - }); - } - }}; -} - -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - /// Creates a new [`Executor`] for executing a `wasmi` function frame. - #[inline(always)] - pub fn new( - ctx: &'ctx mut StoreInner, - cache: &'engine mut InstanceCache, - value_stack: &'engine mut ValueStack, - call_stack: &'engine mut CallStack, - code_map: &'engine CodeMap, - const_pool: ConstPoolView<'engine>, - tracer: Option<&'engine mut Tracer>, - ) -> Self { - let frame = call_stack.pop().expect("must have frame on the call stack"); - let sp = value_stack.stack_ptr(); - let ip = frame.ip(); - Self { - sp, - ip, - cache, - ctx, - value_stack, - call_stack, - code_map, - const_pool, - tracer, - last_signature: None, - } - } - - /// Executes the function frame until it returns or traps. - #[inline(always)] - fn execute( - mut self, - resource_limiter: &'ctx mut ResourceLimiterRef<'ctx>, - ) -> Result { - use Instruction as Instr; - loop { - let instr = *self.ip.get(); - let meta = *self.ip.meta(); - - // TODO: Need to add recursive check while call function - // TODO: Create more optimized check for stack overflowed - if self.value_stack.has_stack_overflowed(self.sp) { - return Err(TrapCode::StackOverflow.into()); - } - - #[cfg(feature = "print-trace")] - { - let stack = self.value_stack.dump_stack(self.sp); - println!( - "{}:\t {:?} \tstack({}):{:?}", - self.ip.pc(), - instr, - stack.len(), - stack - .iter() - .rev() - .take(10) - .map(|v| v.as_usize()) - .collect::>() - ); - } - - // handle pre-instruction state - if let Some(tracer) = self.tracer.as_mut() { - let has_default_memory = { - let instance = self.cache.instance(); - self.ctx - .resolve_instance(instance) - .get_memory(DEFAULT_MEMORY_INDEX) - .is_some() - }; - let memory_size: u32 = if has_default_memory { - self.ctx - .resolve_memory(self.cache.default_memory(self.ctx)) - .current_pages() - .into() - } else { - 0 - }; - let consumed_fuel = self.ctx.fuel().fuel_consumed(); - let stack = self.value_stack.dump_stack(self.sp); - tracer.pre_opcode_state( - self.ip.pc(), - instr, - stack, - &meta, - memory_size, - consumed_fuel, - ); - } - - match instr { - Instr::LocalGet(local_depth) => self.visit_local_get(local_depth), - Instr::LocalSet(local_depth) => self.visit_local_set(local_depth), - Instr::LocalTee(local_depth) => self.visit_local_tee(local_depth), - Instr::Br(offset) => self.visit_br(offset), - Instr::BrIfEqz(offset) => self.visit_br_if_eqz(offset), - Instr::BrIfNez(offset) => self.visit_br_if_nez(offset), - Instr::BrAdjust(offset) => self.visit_br_adjust(offset), - Instr::BrAdjustIfNez(offset) => self.visit_br_adjust_if_nez(offset), - Instr::BrTable(targets) => self.visit_br_table(targets), - Instr::Unreachable => self.visit_unreachable()?, - Instr::ConsumeFuel(block_fuel) => self.visit_consume_fuel(block_fuel)?, - Instr::Return(drop_keep) => { - if let ReturnOutcome::Host = self.visit_ret(drop_keep) { - return Ok(WasmOutcome::Return); - } - } - Instr::ReturnIfNez(drop_keep) => { - if let ReturnOutcome::Host = self.visit_return_if_nez(drop_keep) { - return Ok(WasmOutcome::Return); - } - } - Instr::ReturnCallInternal(compiled_func) => { - self.visit_return_call_internal(compiled_func)? - } - Instr::ReturnCall(func) => { - forward_call!(self.visit_return_call(func)) - } - Instr::ReturnCallIndirect(func_type) => { - forward_call!(self.visit_return_call_indirect(func_type)) - } - Instr::CallInternal(compiled_func) => self.visit_call_internal(compiled_func)?, - Instr::Call(func) => forward_call!(self.visit_call(func)), - Instr::CallIndirect(func_type) => { - forward_call!(self.visit_call_indirect(func_type)) - } - Instr::SignatureCheck(func_type) => self.visit_signature_check(func_type)?, - Instr::Drop => self.visit_drop(), - Instr::Select => self.visit_select(), - Instr::GlobalGet(global_idx) => self.visit_global_get(global_idx), - Instr::GlobalSet(global_idx) => self.visit_global_set(global_idx), - Instr::I32Load(offset) => self.visit_i32_load(offset)?, - Instr::I64Load(offset) => self.visit_i64_load(offset)?, - Instr::F32Load(offset) => self.visit_f32_load(offset)?, - Instr::F64Load(offset) => self.visit_f64_load(offset)?, - Instr::I32Load8S(offset) => self.visit_i32_load_i8_s(offset)?, - Instr::I32Load8U(offset) => self.visit_i32_load_i8_u(offset)?, - Instr::I32Load16S(offset) => self.visit_i32_load_i16_s(offset)?, - Instr::I32Load16U(offset) => self.visit_i32_load_i16_u(offset)?, - Instr::I64Load8S(offset) => self.visit_i64_load_i8_s(offset)?, - Instr::I64Load8U(offset) => self.visit_i64_load_i8_u(offset)?, - Instr::I64Load16S(offset) => self.visit_i64_load_i16_s(offset)?, - Instr::I64Load16U(offset) => self.visit_i64_load_i16_u(offset)?, - Instr::I64Load32S(offset) => self.visit_i64_load_i32_s(offset)?, - Instr::I64Load32U(offset) => self.visit_i64_load_i32_u(offset)?, - Instr::I32Store(offset) => self.visit_i32_store(offset)?, - Instr::I64Store(offset) => self.visit_i64_store(offset)?, - Instr::F32Store(offset) => self.visit_f32_store(offset)?, - Instr::F64Store(offset) => self.visit_f64_store(offset)?, - Instr::I32Store8(offset) => self.visit_i32_store_8(offset)?, - Instr::I32Store16(offset) => self.visit_i32_store_16(offset)?, - Instr::I64Store8(offset) => self.visit_i64_store_8(offset)?, - Instr::I64Store16(offset) => self.visit_i64_store_16(offset)?, - Instr::I64Store32(offset) => self.visit_i64_store_32(offset)?, - Instr::MemorySize => self.visit_memory_size(), - Instr::MemoryGrow => self.visit_memory_grow(&mut *resource_limiter)?, - Instr::MemoryFill => self.visit_memory_fill()?, - Instr::MemoryCopy => self.visit_memory_copy()?, - Instr::MemoryInit(segment) => self.visit_memory_init(segment)?, - Instr::DataDrop(segment) => self.visit_data_drop(segment), - Instr::TableSize(table) => self.visit_table_size(table), - Instr::TableGrow(table) => self.visit_table_grow(table, &mut *resource_limiter)?, - Instr::TableFill(table) => self.visit_table_fill(table)?, - Instr::TableGet(table) => self.visit_table_get(table)?, - Instr::TableSet(table) => self.visit_table_set(table)?, - Instr::TableCopy(dst) => self.visit_table_copy(dst)?, - Instr::TableInit(elem) => self.visit_table_init(elem)?, - Instr::ElemDrop(segment) => self.visit_element_drop(segment), - Instr::RefFunc(func_index) => self.visit_ref_func(func_index)?, - Instr::I32Const(value) => self.visit_i32_const(value), - Instr::I64Const(value) => self.visit_i64_const(value), - Instr::F32Const(value) => self.visit_f32_const(value), - Instr::F64Const(value) => self.visit_f64_const(value), - Instr::ConstRef(cref) => self.visit_const(cref), - Instr::I32Eqz => self.visit_i32_eqz(), - Instr::I32Eq => self.visit_i32_eq(), - Instr::I32Ne => self.visit_i32_ne(), - Instr::I32LtS => self.visit_i32_lt_s(), - Instr::I32LtU => self.visit_i32_lt_u(), - Instr::I32GtS => self.visit_i32_gt_s(), - Instr::I32GtU => self.visit_i32_gt_u(), - Instr::I32LeS => self.visit_i32_le_s(), - Instr::I32LeU => self.visit_i32_le_u(), - Instr::I32GeS => self.visit_i32_ge_s(), - Instr::I32GeU => self.visit_i32_ge_u(), - Instr::I64Eqz => self.visit_i64_eqz(), - Instr::I64Eq => self.visit_i64_eq(), - Instr::I64Ne => self.visit_i64_ne(), - Instr::I64LtS => self.visit_i64_lt_s(), - Instr::I64LtU => self.visit_i64_lt_u(), - Instr::I64GtS => self.visit_i64_gt_s(), - Instr::I64GtU => self.visit_i64_gt_u(), - Instr::I64LeS => self.visit_i64_le_s(), - Instr::I64LeU => self.visit_i64_le_u(), - Instr::I64GeS => self.visit_i64_ge_s(), - Instr::I64GeU => self.visit_i64_ge_u(), - Instr::F32Eq => self.visit_f32_eq(), - Instr::F32Ne => self.visit_f32_ne(), - Instr::F32Lt => self.visit_f32_lt(), - Instr::F32Gt => self.visit_f32_gt(), - Instr::F32Le => self.visit_f32_le(), - Instr::F32Ge => self.visit_f32_ge(), - Instr::F64Eq => self.visit_f64_eq(), - Instr::F64Ne => self.visit_f64_ne(), - Instr::F64Lt => self.visit_f64_lt(), - Instr::F64Gt => self.visit_f64_gt(), - Instr::F64Le => self.visit_f64_le(), - Instr::F64Ge => self.visit_f64_ge(), - Instr::I32Clz => self.visit_i32_clz(), - Instr::I32Ctz => self.visit_i32_ctz(), - Instr::I32Popcnt => self.visit_i32_popcnt(), - Instr::I32Add => self.visit_i32_add(), - Instr::I32Sub => self.visit_i32_sub(), - Instr::I32Mul => self.visit_i32_mul(), - Instr::I32DivS => self.visit_i32_div_s()?, - Instr::I32DivU => self.visit_i32_div_u()?, - Instr::I32RemS => self.visit_i32_rem_s()?, - Instr::I32RemU => self.visit_i32_rem_u()?, - Instr::I32And => self.visit_i32_and(), - Instr::I32Or => self.visit_i32_or(), - Instr::I32Xor => self.visit_i32_xor(), - Instr::I32Shl => self.visit_i32_shl(), - Instr::I32ShrS => self.visit_i32_shr_s(), - Instr::I32ShrU => self.visit_i32_shr_u(), - Instr::I32Rotl => self.visit_i32_rotl(), - Instr::I32Rotr => self.visit_i32_rotr(), - Instr::I64Clz => self.visit_i64_clz(), - Instr::I64Ctz => self.visit_i64_ctz(), - Instr::I64Popcnt => self.visit_i64_popcnt(), - Instr::I64Add => self.visit_i64_add(), - Instr::I64Sub => self.visit_i64_sub(), - Instr::I64Mul => self.visit_i64_mul(), - Instr::I64DivS => self.visit_i64_div_s()?, - Instr::I64DivU => self.visit_i64_div_u()?, - Instr::I64RemS => self.visit_i64_rem_s()?, - Instr::I64RemU => self.visit_i64_rem_u()?, - Instr::I64And => self.visit_i64_and(), - Instr::I64Or => self.visit_i64_or(), - Instr::I64Xor => self.visit_i64_xor(), - Instr::I64Shl => self.visit_i64_shl(), - Instr::I64ShrS => self.visit_i64_shr_s(), - Instr::I64ShrU => self.visit_i64_shr_u(), - Instr::I64Rotl => self.visit_i64_rotl(), - Instr::I64Rotr => self.visit_i64_rotr(), - Instr::F32Abs => self.visit_f32_abs(), - Instr::F32Neg => self.visit_f32_neg(), - Instr::F32Ceil => self.visit_f32_ceil(), - Instr::F32Floor => self.visit_f32_floor(), - Instr::F32Trunc => self.visit_f32_trunc(), - Instr::F32Nearest => self.visit_f32_nearest(), - Instr::F32Sqrt => self.visit_f32_sqrt(), - Instr::F32Add => self.visit_f32_add(), - Instr::F32Sub => self.visit_f32_sub(), - Instr::F32Mul => self.visit_f32_mul(), - Instr::F32Div => self.visit_f32_div(), - Instr::F32Min => self.visit_f32_min(), - Instr::F32Max => self.visit_f32_max(), - Instr::F32Copysign => self.visit_f32_copysign(), - Instr::F64Abs => self.visit_f64_abs(), - Instr::F64Neg => self.visit_f64_neg(), - Instr::F64Ceil => self.visit_f64_ceil(), - Instr::F64Floor => self.visit_f64_floor(), - Instr::F64Trunc => self.visit_f64_trunc(), - Instr::F64Nearest => self.visit_f64_nearest(), - Instr::F64Sqrt => self.visit_f64_sqrt(), - Instr::F64Add => self.visit_f64_add(), - Instr::F64Sub => self.visit_f64_sub(), - Instr::F64Mul => self.visit_f64_mul(), - Instr::F64Div => self.visit_f64_div(), - Instr::F64Min => self.visit_f64_min(), - Instr::F64Max => self.visit_f64_max(), - Instr::F64Copysign => self.visit_f64_copysign(), - Instr::I32WrapI64 => self.visit_i32_wrap_i64(), - Instr::I32TruncF32S => self.visit_i32_trunc_f32_s()?, - Instr::I32TruncF32U => self.visit_i32_trunc_f32_u()?, - Instr::I32TruncF64S => self.visit_i32_trunc_f64_s()?, - Instr::I32TruncF64U => self.visit_i32_trunc_f64_u()?, - Instr::I64ExtendI32S => self.visit_i64_extend_i32_s(), - Instr::I64ExtendI32U => self.visit_i64_extend_i32_u(), - Instr::I64TruncF32S => self.visit_i64_trunc_f32_s()?, - Instr::I64TruncF32U => self.visit_i64_trunc_f32_u()?, - Instr::I64TruncF64S => self.visit_i64_trunc_f64_s()?, - Instr::I64TruncF64U => self.visit_i64_trunc_f64_u()?, - Instr::F32ConvertI32S => self.visit_f32_convert_i32_s(), - Instr::F32ConvertI32U => self.visit_f32_convert_i32_u(), - Instr::F32ConvertI64S => self.visit_f32_convert_i64_s(), - Instr::F32ConvertI64U => self.visit_f32_convert_i64_u(), - Instr::F32DemoteF64 => self.visit_f32_demote_f64(), - Instr::F64ConvertI32S => self.visit_f64_convert_i32_s(), - Instr::F64ConvertI32U => self.visit_f64_convert_i32_u(), - Instr::F64ConvertI64S => self.visit_f64_convert_i64_s(), - Instr::F64ConvertI64U => self.visit_f64_convert_i64_u(), - Instr::F64PromoteF32 => self.visit_f64_promote_f32(), - Instr::I32TruncSatF32S => self.visit_i32_trunc_sat_f32_s(), - Instr::I32TruncSatF32U => self.visit_i32_trunc_sat_f32_u(), - Instr::I32TruncSatF64S => self.visit_i32_trunc_sat_f64_s(), - Instr::I32TruncSatF64U => self.visit_i32_trunc_sat_f64_u(), - Instr::I64TruncSatF32S => self.visit_i64_trunc_sat_f32_s(), - Instr::I64TruncSatF32U => self.visit_i64_trunc_sat_f32_u(), - Instr::I64TruncSatF64S => self.visit_i64_trunc_sat_f64_s(), - Instr::I64TruncSatF64U => self.visit_i64_trunc_sat_f64_u(), - Instr::I32Extend8S => self.visit_i32_extend8_s(), - Instr::I32Extend16S => self.visit_i32_extend16_s(), - Instr::I64Extend8S => self.visit_i64_extend8_s(), - Instr::I64Extend16S => self.visit_i64_extend16_s(), - Instr::I64Extend32S => self.visit_i64_extend32_s(), - Instr::StackAlloc { max_stack_height } => { - self.value_stack.reserve(max_stack_height as usize)?; - self.next_instr(); - } - } - } - } - - /// Executes a generic Wasm `store[N_{s|u}]` operation. - /// - /// # Note - /// - /// This can be used to emulate the following Wasm operands: - /// - /// - `{i32, i64, f32, f64}.load` - /// - `{i32, i64}.load8_s` - /// - `{i32, i64}.load8_u` - /// - `{i32, i64}.load16_s` - /// - `{i32, i64}.load16_u` - /// - `i64.load32_s` - /// - `i64.load32_u` - #[inline(always)] - fn execute_load_extend( - &mut self, - offset: AddressOffset, - load_extend: WasmLoadOp, - ) -> Result<(), TrapCode> { - self.sp.try_eval_top(|address| { - let memory = self.cache.default_memory_bytes(self.ctx); - let value = load_extend(memory, address, offset.into_inner())?; - Ok(value) - })?; - self.try_next_instr() - } - - /// Executes a generic Wasm `store[N]` operation. - /// - /// # Note - /// - /// This can be used to emulate the following Wasm operands: - /// - /// - `{i32, i64, f32, f64}.store` - /// - `{i32, i64}.store8` - /// - `{i32, i64}.store16` - /// - `i64.store32` - #[inline(always)] - fn execute_store_wrap( - &mut self, - offset: AddressOffset, - store_wrap: WasmStoreOp, - len: u32, - ) -> Result<(), TrapCode> { - let (address, value) = self.sp.pop2(); - let memory = self.cache.default_memory_bytes(self.ctx); - store_wrap(memory, address, offset.into_inner(), value)?; - self.ip.offset(0); - let address = u32::from(address); - let base_address = offset.into_inner() + address; - if let Some(tracer) = self.tracer.as_mut() { - tracer.memory_change( - base_address, - len, - &memory[base_address as usize..(base_address + len) as usize], - ); - } - self.try_next_instr() - } - - /// Executes an infallible unary `wasmi` instruction. - #[inline(always)] - fn execute_unary(&mut self, f: fn(UntypedValue) -> UntypedValue) { - self.sp.eval_top(f); - self.next_instr() - } - - /// Executes a fallible unary `wasmi` instruction. - #[inline(always)] - fn try_execute_unary( - &mut self, - f: fn(UntypedValue) -> Result, - ) -> Result<(), TrapCode> { - self.sp.try_eval_top(f)?; - self.try_next_instr() - } - - /// Executes an infallible binary `wasmi` instruction. - #[inline(always)] - fn execute_binary(&mut self, f: fn(UntypedValue, UntypedValue) -> UntypedValue) { - self.sp.eval_top2(f); - self.next_instr() - } - - /// Executes a fallible binary `wasmi` instruction. - #[inline(always)] - fn try_execute_binary( - &mut self, - f: fn(UntypedValue, UntypedValue) -> Result, - ) -> Result<(), TrapCode> { - self.sp.try_eval_top2(f)?; - self.try_next_instr() - } - - /// Shifts the instruction pointer to the next instruction. - #[inline(always)] - fn next_instr(&mut self) { - self.ip.add(1) - } - - /// Shifts the instruction pointer to the next instruction. - /// - /// Has a parameter `skip` to denote how many instruction words - /// to skip to reach the next actual instruction. - /// - /// # Note - /// - /// This is used by `wasmi` instructions that have a fixed - /// encoding size of two instruction words such as [`Instruction::Br`]. - #[inline(always)] - fn next_instr_at(&mut self, skip: usize) { - self.ip.add(skip) - } - - /// Shifts the instruction pointer to the next instruction and returns `Ok(())`. - /// - /// # Note - /// - /// This is a convenience function for fallible instructions. - #[inline(always)] - fn try_next_instr(&mut self) -> Result<(), TrapCode> { - self.next_instr(); - Ok(()) - } - - /// Shifts the instruction pointer to the next instruction and returns `Ok(())`. - /// - /// Has a parameter `skip` to denote how many instruction words - /// to skip to reach the next actual instruction. - /// - /// # Note - /// - /// This is a convenience function for fallible instructions. - #[inline(always)] - fn try_next_instr_at(&mut self, skip: usize) -> Result<(), TrapCode> { - self.next_instr_at(skip); - Ok(()) - } - - /// Branches and adjusts the value stack. - /// - /// # Note - /// - /// Offsets the instruction pointer using the given [`BranchOffset`] and - /// adjusts the value stack using the [`DropKeep`]. - #[inline(always)] - fn branch_to(&mut self, offset: BranchOffset) { - self.ip.offset(offset.to_i32() as isize) - } - - /// Branches and adjusts the value stack. - /// - /// # Note - /// - /// Offsets the instruction pointer using the given [`BranchOffset`] and - /// adjusts the value stack using the [`DropKeep`]. - #[inline(always)] - fn branch_to_and_adjust(&mut self, offset: BranchOffset, drop_keep: DropKeep) { - self.sp.drop_keep(drop_keep); - self.branch_to(offset) - } - - /// Synchronizes the current stack pointer with the [`ValueStack`]. - /// - /// # Note - /// - /// For performance reasons we detach the stack pointer form the [`ValueStack`]. - /// Therefore it is necessary to synchronize the [`ValueStack`] upon finishing - /// execution of a sequence of non control flow instructions. - #[inline(always)] - fn sync_stack_ptr(&mut self) { - self.value_stack.sync_stack_ptr(self.sp); - } - - /// Calls the given [`Func`]. - /// - /// This also prepares the instruction pointer and stack pointer for - /// the function call so that the stack and execution state is synchronized - /// with the outer structures. - #[inline(always)] - fn call_func( - &mut self, - skip: usize, - func: &Func, - kind: CallKind, - func_index: u32, - ) -> Result { - self.next_instr_at(skip); - self.sync_stack_ptr(); - if matches!(kind, CallKind::Nested) { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - } - match self.ctx.resolve_func(func) { - FuncEntity::Wasm(wasm_func) => { - let header = self.code_map.header(wasm_func.func_body()); - if let Some(tracer) = self.tracer.as_mut() { - tracer.function_call( - func_index, - header.max_stack_height(), - header.len_locals(), - String::new(), - ); - } - self.value_stack.prepare_wasm_call(header)?; - self.sp = self.value_stack.stack_ptr(); - self.cache.update_instance(wasm_func.instance()); - self.ip = self.code_map.instr_ptr(header.iref()); - Ok(CallOutcome::Continue) - } - FuncEntity::Host(_host_func) => { - self.cache.reset(); - Ok(CallOutcome::Call { - host_func: *func, - instance: *self.cache.instance(), - }) - } - } - } - - /// Calls the given internal [`CompiledFunc`]. - /// - /// This also prepares the instruction pointer and stack pointer for - /// the function call so that the stack and execution state is synchronized - /// with the outer structures. - #[inline(always)] - fn call_func_internal(&mut self, func: CompiledFunc, kind: CallKind) -> Result<(), TrapCode> { - self.next_instr_at(match kind { - CallKind::Nested => 1, - CallKind::Tail => 2, - }); - self.sync_stack_ptr(); - if matches!(kind, CallKind::Nested) { - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - } - let header = self.code_map.header(func); - self.value_stack.prepare_wasm_call(header)?; - self.sp = self.value_stack.stack_ptr(); - self.ip = self.code_map.instr_ptr(header.iref()); - Ok(()) - } - - /// Returns to the caller. - /// - /// This also modifies the stack as the caller would expect it - /// and synchronizes the execution state with the outer structures. - #[inline(always)] - fn ret(&mut self, drop_keep: DropKeep) -> ReturnOutcome { - self.sp.drop_keep(drop_keep); - self.sync_stack_ptr(); - match self.call_stack.pop() { - Some(caller) => { - self.ip = caller.ip(); - self.cache.update_instance(caller.instance()); - ReturnOutcome::Wasm - } - None => ReturnOutcome::Host, - } - } - - /// Consume an amount of fuel specified by `delta` if `exec` succeeds. - /// - /// # Note - /// - /// - `delta` is only evaluated if fuel metering is enabled. - /// - `exec` is only evaluated if the remaining fuel is sufficient for amount of required fuel - /// determined by `delta` or if fuel metering is disabled. - /// - /// # Errors - /// - /// - If the [`StoreInner`] ran out of fuel. - /// - If the `exec` closure traps. - #[inline(always)] - fn consume_fuel_with( - &mut self, - delta: impl FnOnce(&FuelCosts) -> u64, - exec: impl FnOnce(&mut Self) -> Result, - ) -> Result - where - E: From, - { - match self.get_fuel_consumption_mode() { - None => exec(self), - Some(mode) => self.consume_fuel_with_mode(mode, delta, exec), - } - } - - /// Consume an amount of fuel specified by `delta` and executes `exec`. - /// - /// The `mode` determines when and if the fuel determined by `delta` is charged. - /// - /// # Errors - /// - /// - If the [`StoreInner`] ran out of fuel. - /// - If the `exec` closure traps. - #[inline(always)] - fn consume_fuel_with_mode( - &mut self, - mode: FuelConsumptionMode, - delta: impl FnOnce(&FuelCosts) -> u64, - exec: impl FnOnce(&mut Self) -> Result, - ) -> Result - where - E: From, - { - let delta = delta(self.fuel_costs()); - match mode { - FuelConsumptionMode::Lazy => self.consume_fuel_with_lazy(delta, exec), - FuelConsumptionMode::Eager => self.consume_fuel_with_eager(delta, exec), - } - } - - /// Consume an amount of fuel specified by `delta` if `exec` succeeds. - /// - /// Prior to executing `exec` it is checked if enough fuel is remaining - /// determined by `delta`. The fuel is charged only after `exec` has been - /// finished successfully. - /// - /// # Errors - /// - /// - If the [`StoreInner`] ran out of fuel. - /// - If the `exec` closure traps. - #[inline(always)] - fn consume_fuel_with_lazy( - &mut self, - delta: u64, - exec: impl FnOnce(&mut Self) -> Result, - ) -> Result - where - E: From, - { - self.ctx.fuel().sufficient_fuel(delta)?; - let result = exec(self)?; - self.ctx - .fuel_mut() - .consume_fuel(delta) - .expect("remaining fuel has already been approved prior"); - Ok(result) - } - - /// Consume an amount of fuel specified by `delta` and executes `exec`. - /// - /// # Errors - /// - /// - If the [`StoreInner`] ran out of fuel. - /// - If the `exec` closure traps. - #[inline(always)] - fn consume_fuel_with_eager( - &mut self, - delta: u64, - exec: impl FnOnce(&mut Self) -> Result, - ) -> Result - where - E: From, - { - self.ctx.fuel_mut().consume_fuel(delta)?; - exec(self) - } - - /// Returns a shared reference to the [`FuelCosts`] of the [`Engine`]. - /// - /// [`Engine`]: crate::Engine - #[inline] - fn fuel_costs(&self) -> &FuelCosts { - self.ctx.engine().config().fuel_costs() - } - - /// Returns the [`FuelConsumptionMode`] of the [`Engine`]. - /// - /// [`Engine`]: crate::Engine - #[inline] - fn get_fuel_consumption_mode(&self) -> Option { - self.ctx.engine().config().get_fuel_consumption_mode() - } - - /// Executes a `call_indirect` or `return_call_indirect` instruction. - #[inline(always)] - fn execute_call_indirect( - &mut self, - skip: usize, - table: TableIdx, - func_index: u32, - func_type: SignatureIdx, - kind: CallKind, - ) -> Result { - let table = self.cache.get_table(self.ctx, table); - let funcref = self - .ctx - .resolve_table(&table) - .get_untyped(func_index) - .map(FuncRef::from) - .ok_or(TrapCode::TableOutOfBounds)?; - let func = funcref.func().ok_or(TrapCode::IndirectCallToNull)?; - // for rWASM we do signature check using special additional opcode - let is_rwasm = self.ctx.engine().config().get_rwasm_config().is_some(); - if !is_rwasm { - let actual_signature = self.ctx.resolve_func(func).ty_dedup(); - let expected_signature = self - .ctx - .resolve_instance(self.cache.instance()) - .get_signature(func_type.to_u32()) - .unwrap_or_else(|| { - panic!("missing signature for call_indirect at index: {func_type:?}") - }); - if actual_signature != expected_signature { - return Err(TrapCode::BadSignature).map_err(Into::into); - } - } - self.call_func(skip, func, kind, func_index) - } -} - -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - #[inline(always)] - fn visit_unreachable(&mut self) -> Result<(), TrapCode> { - Err(TrapCode::UnreachableCodeReached).map_err(Into::into) - } - - #[inline(always)] - fn visit_consume_fuel(&mut self, block_fuel: BlockFuel) -> Result<(), TrapCode> { - // We do not have to check if fuel metering is enabled since - // these `wasmi` instructions are only generated if fuel metering - // is enabled to begin with. - if self.ctx.engine().config().get_consume_fuel() { - // We need to do the check for rWASM, because there is a mode where we don't have - // fuel even if application is compiled with fuel support - self.ctx.fuel_mut().consume_fuel(block_fuel.to_u64())?; - } - self.try_next_instr() - } - - /// Fetches the [`DropKeep`] parameter for an instruction. - /// - /// # Note - /// - /// - This is done by encoding an [`Instruction::Return`] instruction word following the actual - /// instruction where the [`DropKeep`] paremeter belongs to. - /// - This is required for some instructions that do not fit into a single instruction word and - /// store a [`DropKeep`] value in another instruction word. - fn fetch_drop_keep(&self, offset: usize) -> DropKeep { - let mut addr: InstructionPtr = self.ip; - addr.add(offset); - match addr.get() { - Instruction::Return(drop_keep) => *drop_keep, - _ => unreachable!("expected Return instruction word at this point"), - } - } - - /// Fetches the [`TableIdx`] parameter for an instruction. - /// - /// # Note - /// - /// - This is done by encoding an [`Instruction::TableGet`] instruction word following the - /// actual instruction where the [`TableIdx`] paremeter belongs to. - /// - This is required for some instructions that do not fit into a single instruction word and - /// store a [`TableIdx`] value in another instruction word. - fn fetch_table_idx(&mut self, offset: usize) -> TableIdx { - let mut addr: InstructionPtr = self.ip; - addr.add(offset); - let table_idx = match addr.get() { - Instruction::TableGet(table_idx) => *table_idx, - _ => unreachable!("expected TableGet instruction word at this point"), - }; - if let Some(tracer) = self.tracer.as_mut() { - tracer.remember_next_table(table_idx); - } - table_idx - } - - #[inline(always)] - fn visit_br(&mut self, offset: BranchOffset) { - self.branch_to(offset) - } - - #[inline(always)] - fn visit_br_if_eqz(&mut self, offset: BranchOffset) { - let condition = self.sp.pop_as(); - if condition { - self.next_instr() - } else { - self.branch_to(offset) - } - } - - #[inline(always)] - fn visit_br_if_nez(&mut self, offset: BranchOffset) { - let condition = self.sp.pop_as(); - if condition { - self.branch_to(offset) - } else { - self.next_instr() - } - } - - #[inline(always)] - fn visit_br_adjust(&mut self, offset: BranchOffset) { - let drop_keep = self.fetch_drop_keep(1); - self.branch_to_and_adjust(offset, drop_keep) - } - - #[inline(always)] - fn visit_br_adjust_if_nez(&mut self, offset: BranchOffset) { - let condition = self.sp.pop_as(); - if condition { - let drop_keep = self.fetch_drop_keep(1); - self.branch_to_and_adjust(offset, drop_keep) - } else { - self.next_instr_at(2) - } - } - - #[inline(always)] - fn visit_return_if_nez(&mut self, drop_keep: DropKeep) -> ReturnOutcome { - let condition = self.sp.pop_as(); - if condition { - self.ret(drop_keep) - } else { - self.next_instr(); - ReturnOutcome::Wasm - } - } - - #[inline(always)] - fn visit_br_table(&mut self, targets: BranchTableTargets) { - let index: u32 = self.sp.pop_as(); - // The index of the default target which is the last target of the slice. - let max_index = targets.to_usize() - 1; - // A normalized index will always yield a target without panicking. - let normalized_index = cmp::min(index as usize, max_index); - // Update `pc`: - self.ip.add(2 * normalized_index + 1); - } - - #[inline(always)] - fn visit_ret(&mut self, drop_keep: DropKeep) -> ReturnOutcome { - self.ret(drop_keep) - } - - #[inline(always)] - fn visit_local_get(&mut self, local_depth: LocalDepth) { - let value = self.sp.nth_back(local_depth.to_usize()); - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_local_set(&mut self, local_depth: LocalDepth) { - let new_value = self.sp.pop(); - self.sp.set_nth_back(local_depth.to_usize(), new_value); - self.next_instr() - } - - #[inline(always)] - fn visit_local_tee(&mut self, local_depth: LocalDepth) { - let new_value = self.sp.last(); - self.sp.set_nth_back(local_depth.to_usize(), new_value); - self.next_instr() - } - - #[inline(always)] - fn visit_global_get(&mut self, global_index: GlobalIdx) { - let global_value = self.cache.get_global(self.ctx, global_index); - self.sp.push(global_value); - self.next_instr() - } - - #[inline(always)] - fn visit_global_set(&mut self, global_index: GlobalIdx) { - let new_value = self.sp.pop(); - self.cache.set_global(self.ctx, global_index, new_value); - self.next_instr() - } - - #[inline(always)] - fn visit_return_call_internal(&mut self, compiled_func: CompiledFunc) -> Result<(), TrapCode> { - let drop_keep = self.fetch_drop_keep(1); - self.sp.drop_keep(drop_keep); - self.call_func_internal(compiled_func, CallKind::Tail) - } - - #[inline(always)] - fn visit_return_call(&mut self, func_index: FuncIdx) -> Result { - let drop_keep = self.fetch_drop_keep(1); - self.sp.drop_keep(drop_keep); - let callee = self.cache.get_func(self.ctx, func_index); - self.call_func(2, &callee, CallKind::Tail, func_index.to_u32()) - } - - #[inline(always)] - fn visit_return_call_indirect( - &mut self, - func_type: SignatureIdx, - ) -> Result { - let drop_keep = self.fetch_drop_keep(1); - let table = self.fetch_table_idx(2); - let func_index: u32 = self.sp.pop_as(); - self.sp.drop_keep(drop_keep); - // for rWASM, let's store func type on the stack - if self.ctx.engine().config().get_rwasm_config().is_some() { - self.last_signature = Some(func_type); - } - self.execute_call_indirect(3, table, func_index, func_type, CallKind::Tail) - } - - #[inline(always)] - fn visit_call_internal(&mut self, compiled_func: CompiledFunc) -> Result<(), TrapCode> { - self.call_func_internal(compiled_func, CallKind::Nested) - } - - #[inline(always)] - fn visit_call(&mut self, func_index: FuncIdx) -> Result { - if self.ctx.engine().config().get_rwasm_wrap_import_funcs() { - let wrapped_func_index = self.ctx.wrap_stored(func_index); - let func_entity = self - .ctx - .engine() - .resolve_trampoline(wrapped_func_index) - .ok_or(TrapCode::UnresolvedFunction)?; - self.next_instr_at(1); - self.sync_stack_ptr(); - self.call_stack - .push(FuncFrame::new(self.ip, self.cache.instance()))?; - self.cache.reset(); - Ok(CallOutcome::Call { - host_func: func_entity, - instance: *self.cache.instance(), - }) - } else { - let callee = self.cache.get_func(self.ctx, func_index); - self.call_func(1, &callee, CallKind::Nested, func_index.to_u32()) - } - } - - #[inline(always)] - fn visit_call_indirect(&mut self, func_type: SignatureIdx) -> Result { - let table = self.fetch_table_idx(1); - let func_index: u32 = self.sp.pop_as(); - // for rWASM, let's store func type on the stack - if self.ctx.engine().config().get_rwasm_config().is_some() { - self.last_signature = Some(func_type); - } - self.execute_call_indirect(2, table, func_index, func_type, CallKind::Nested) - } - - #[inline(always)] - fn visit_signature_check(&mut self, expected_signature: SignatureIdx) -> Result<(), TrapCode> { - debug_assert!( - self.ctx.engine().config().get_rwasm_config().is_some(), - "this instruction can be used only in rWASM mode" - ); - if let Some(actual_signature) = self.last_signature.take() { - if actual_signature != expected_signature { - return Err(TrapCode::BadSignature).map_err(Into::into); - } - } - self.next_instr(); - Ok(()) - } - - #[inline(always)] - fn visit_i32_const(&mut self, value: UntypedValue) { - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_i64_const(&mut self, value: UntypedValue) { - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_f32_const(&mut self, value: UntypedValue) { - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_f64_const(&mut self, value: UntypedValue) { - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_const(&mut self, cref: ConstRef) { - let value = self - .const_pool - .get(cref) - .unwrap_or_else(|| unreachable!("missing constant value for const reference")); - self.sp.push(value); - self.next_instr() - } - - #[inline(always)] - fn visit_drop(&mut self) { - self.sp.drop(); - self.next_instr() - } - - #[inline(always)] - fn visit_select(&mut self) { - self.sp.eval_top3(|e1, e2, e3| { - let condition = >::from(e3); - if condition { - e1 - } else { - e2 - } - }); - self.next_instr() - } - - #[inline(always)] - fn visit_memory_size(&mut self) { - let memory = self.cache.default_memory(self.ctx); - let result: u32 = self.ctx.resolve_memory(memory).current_pages().into(); - self.sp.push_as(result); - self.next_instr() - } - - #[inline(always)] - fn visit_memory_grow( - &mut self, - resource_limiter: &mut ResourceLimiterRef<'ctx>, - ) -> Result<(), TrapCode> { - let delta: u32 = self.sp.pop_as(); - let delta = match Pages::new(delta) { - Some(pages) => pages, - None => { - // Cannot grow memory so we push the expected error value. - self.sp.push_as(INVALID_GROWTH_ERRCODE); - return self.try_next_instr(); - } - }; - let result = self.consume_fuel_with( - |costs| { - let delta_in_bytes = delta.to_bytes().unwrap_or(0) as u64; - costs.fuel_for_bytes(delta_in_bytes) - }, - |this| { - let memory = this.cache.default_memory(this.ctx); - let new_pages = this - .ctx - .resolve_memory_mut(memory) - .grow(delta, resource_limiter) - .map(u32::from)?; - // The `memory.grow` operation might have invalidated the cached - // linear memory so we need to reset it in order for the cache to - // reload in case it is used again. - this.cache.reset_default_memory_bytes(); - Ok(new_pages) - }, - ); - let result = match result { - Ok(result) => result, - Err(EntityGrowError::InvalidGrow) => INVALID_GROWTH_ERRCODE, - Err(EntityGrowError::TrapCode(trap_code)) => return Err(trap_code), - }; - self.sp.push_as(result); - self.try_next_instr() - } - - #[inline(always)] - fn visit_memory_fill(&mut self) -> Result<(), TrapCode> { - // The `n`, `val` and `d` variable bindings are extracted from the Wasm specification. - let (d, val, n) = self.sp.pop3(); - let n = i32::from(n) as usize; - let offset = i32::from(d) as usize; - let byte = u8::from(val); - self.consume_fuel_with( - |costs| costs.fuel_for_bytes(n as u64), - |this| { - let memory = this - .cache - .default_memory_bytes(this.ctx) - .get_mut(offset..) - .and_then(|memory| memory.get_mut(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - memory.fill(byte); - if let Some(tracer) = this.tracer.as_mut() { - tracer.memory_change(offset as u32, n as u32, memory); - } - Ok(()) - }, - )?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_memory_copy(&mut self) -> Result<(), TrapCode> { - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (d, s, n) = self.sp.pop3(); - let n = i32::from(n) as usize; - let src_offset = i32::from(s) as usize; - let dst_offset = i32::from(d) as usize; - self.consume_fuel_with( - |costs| costs.fuel_for_bytes(n as u64), - |this| { - let data = this.cache.default_memory_bytes(this.ctx); - // These accesses just perform the bounds checks required by the Wasm spec. - data.get(src_offset..) - .and_then(|memory| memory.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - data.get(dst_offset..) - .and_then(|memory| memory.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - data.copy_within(src_offset..src_offset.wrapping_add(n), dst_offset); - if let Some(tracer) = this.tracer.as_mut() { - tracer.memory_change( - dst_offset as u32, - n as u32, - &data[dst_offset..(dst_offset + n)], - ); - } - Ok(()) - }, - )?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_memory_init(&mut self, mut segment: DataSegmentIdx) -> Result<(), TrapCode> { - // we use some tricky structure for rWASM to determine what data segments dropped - let is_empty_segment = if self.ctx.engine().config().get_rwasm_config().is_some() { - // increase segment index, because the first index is used for the global data section - let (_, data) = self - .cache - .get_default_memory_and_data_segment(self.ctx, segment); - // since we have only one data segment then rewrite index with 0 - segment = DataSegmentIdx::from(0); - data.len() == 0 - } else { - false - }; - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (d, s, n) = self.sp.pop3(); - let n = i32::from(n) as usize; - let src_offset = i32::from(s) as usize; - let dst_offset = i32::from(d) as usize; - self.consume_fuel_with( - |costs| costs.fuel_for_bytes(n as u64), - |this| { - let (memory, mut data) = this - .cache - .get_default_memory_and_data_segment(this.ctx, segment); - if is_empty_segment { - data = &[] - } - let memory = memory - .get_mut(dst_offset..) - .and_then(|memory| memory.get_mut(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - let data = data - .get(src_offset..) - .and_then(|data| data.get(..n)) - .ok_or(TrapCode::MemoryOutOfBounds)?; - memory.copy_from_slice(data); - if let Some(tracer) = this.tracer.as_mut() { - tracer.global_memory(dst_offset as u32, n as u32, memory); - } - Ok(()) - }, - )?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_data_drop(&mut self, segment_index: DataSegmentIdx) { - let segment = self - .cache - .get_data_segment(self.ctx, segment_index.to_u32()); - self.ctx.resolve_data_segment_mut(&segment).drop_bytes(); - self.next_instr(); - } - - #[inline(always)] - fn visit_table_size(&mut self, table_index: TableIdx) { - let table = self.cache.get_table(self.ctx, table_index); - let size = self.ctx.resolve_table(&table).size(); - self.sp.push_as(size); - self.next_instr() - } - - #[inline(always)] - fn visit_table_grow( - &mut self, - table_index: TableIdx, - resource_limiter: &mut ResourceLimiterRef<'ctx>, - ) -> Result<(), TrapCode> { - let (init, delta) = self.sp.pop2(); - let delta: u32 = delta.into(); - let result = self.consume_fuel_with( - |costs| costs.fuel_for_elements(u64::from(delta)), - |this| { - let table = this.cache.get_table(this.ctx, table_index); - this.ctx - .resolve_table_mut(&table) - .grow_untyped(delta, init, resource_limiter) - }, - ); - let result = match result { - Ok(result) => result, - Err(EntityGrowError::InvalidGrow) => INVALID_GROWTH_ERRCODE, - Err(EntityGrowError::TrapCode(trap_code)) => return Err(trap_code), - }; - self.sp.push_as(result); - if let Some(tracer) = self.tracer.as_mut() { - tracer.table_size_change(table_index.to_u32(), init.as_u32(), delta); - } - self.try_next_instr() - } - - #[inline(always)] - fn visit_table_fill(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (i, val, n) = self.sp.pop3(); - let dst: u32 = i.into(); - let len: u32 = n.into(); - self.consume_fuel_with( - |costs| costs.fuel_for_elements(u64::from(len)), - |this| { - let table = this.cache.get_table(this.ctx, table_index); - this.ctx - .resolve_table_mut(&table) - .fill_untyped(dst, val, len)?; - Ok(()) - }, - )?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_table_get(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { - self.sp.try_eval_top(|index| { - let index: u32 = index.into(); - let table = self.cache.get_table(self.ctx, table_index); - self.ctx - .resolve_table(&table) - .get_untyped(index) - .ok_or(TrapCode::TableOutOfBounds) - })?; - self.try_next_instr() - } - - #[inline(always)] - fn visit_table_set(&mut self, table_index: TableIdx) -> Result<(), TrapCode> { - let (index, value) = self.sp.pop2(); - let index: u32 = index.into(); - let table = self.cache.get_table(self.ctx, table_index); - self.ctx - .resolve_table_mut(&table) - .set_untyped(index, value) - .map_err(|_| TrapCode::TableOutOfBounds)?; - if let Some(tracer) = self.tracer.as_mut() { - tracer.table_change(table_index.to_u32(), index, value); - } - self.try_next_instr() - } - - #[inline(always)] - fn visit_table_copy(&mut self, dst: TableIdx) -> Result<(), TrapCode> { - let src = self.fetch_table_idx(1); - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (d, s, n) = self.sp.pop3(); - let len = u32::from(n); - let src_index = u32::from(s); - let dst_index = u32::from(d); - self.consume_fuel_with( - |costs| costs.fuel_for_elements(u64::from(len)), - |this| { - // Query both tables and check if they are the same: - let dst = this.cache.get_table(this.ctx, dst); - let src = this.cache.get_table(this.ctx, src); - if Table::eq(&dst, &src) { - // Copy within the same table: - let table = this.ctx.resolve_table_mut(&dst); - table.copy_within(dst_index, src_index, len)?; - } else { - // Copy from one table to another table: - let (dst, src) = this.ctx.resolve_table_pair_mut(&dst, &src); - TableEntity::copy(dst, dst_index, src, src_index, len)?; - } - Ok(()) - }, - )?; - self.try_next_instr_at(2) - } - - #[inline(always)] - fn visit_table_init(&mut self, mut elem: ElementSegmentIdx) -> Result<(), TrapCode> { - let table_idx = self.fetch_table_idx(1); - // we use some tricky structure for rWASM to determine what element segments dropped - let is_empty_segment = if self.ctx.engine().config().get_rwasm_config().is_some() { - // increase segment index, because the first index is used for the global element - // segment - let (_, _, element) = self - .cache - .get_table_and_element_segment(self.ctx, table_idx, elem); - // since we have only one element segment then rewrite index with 0 - elem = ElementSegmentIdx::from(0); - element.items.is_none() - } else { - false - }; - // The `n`, `s` and `d` variable bindings are extracted from the Wasm specification. - let (d, s, n) = self.sp.pop3(); - let len = u32::from(n); - let src_index = u32::from(s); - let dst_index = u32::from(d); - self.consume_fuel_with( - |costs| costs.fuel_for_elements(u64::from(len)), - |this| { - let (instance, table, mut element) = this - .cache - .get_table_and_element_segment(this.ctx, table_idx, elem); - let empty_element_segment = ElementSegmentEntity::empty(element.ty()); - if is_empty_segment { - element = &empty_element_segment; - } - table.init(dst_index, element, src_index, len, |func_index| { - let func_index = self - .code_map - .resolve_function_by_offset(func_index as usize) - .map(|v| v.into_usize() as u32) - .unwrap_or(func_index); - let func = instance - .get_func(func_index) - .unwrap_or_else(|| panic!("missing function at index {func_index}")); - Some(func) - })?; - Ok(()) - }, - )?; - self.try_next_instr_at(2) - } - - #[inline(always)] - fn visit_element_drop(&mut self, segment_index: ElementSegmentIdx) { - let segment = self.cache.get_element_segment(self.ctx, segment_index); - self.ctx.resolve_element_segment_mut(&segment).drop_items(); - self.next_instr(); - } - - #[inline(always)] - fn visit_ref_func(&mut self, func_index: FuncIdx) -> Result<(), TrapCode> { - let func_index: FuncIdx = self - .code_map - .resolve_function_by_offset(func_index.to_u32() as usize) - .map(|v| v.to_u32().into()) - .unwrap_or(func_index); - let func = self.cache.get_func(self.ctx, func_index); - let funcref = FuncRef::new(func); - self.sp.push_as(funcref); - self.next_instr(); - Ok(()) - } -} - -macro_rules! impl_visit_load { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident( - &mut self, - offset: AddressOffset, - ) -> Result<(), TrapCode> { - self.execute_load_extend(offset, UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_load! { - fn visit_i32_load(i32_load); - fn visit_i64_load(i64_load); - fn visit_f32_load(f32_load); - fn visit_f64_load(f64_load); - - fn visit_i32_load_i8_s(i32_load8_s); - fn visit_i32_load_i8_u(i32_load8_u); - fn visit_i32_load_i16_s(i32_load16_s); - fn visit_i32_load_i16_u(i32_load16_u); - - fn visit_i64_load_i8_s(i64_load8_s); - fn visit_i64_load_i8_u(i64_load8_u); - fn visit_i64_load_i16_s(i64_load16_s); - fn visit_i64_load_i16_u(i64_load16_u); - fn visit_i64_load_i32_s(i64_load32_s); - 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)] - fn $visit_ident( - &mut self, - offset: AddressOffset, - ) -> Result<(), TrapCode> { - self.execute_store_wrap(offset, UntypedValue::$untyped_ident, $type_size) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_store! { - fn visit_i32_store(i32_store, 4); - fn visit_i64_store(i64_store, 8); - fn visit_f32_store(f32_store, 4); - fn visit_f64_store(f64_store, 8); - - fn visit_i32_store_8(i32_store8, 1); - fn visit_i32_store_16(i32_store16, 2); - - fn visit_i64_store_8(i64_store8, 1); - fn visit_i64_store_16(i64_store16, 2); - fn visit_i64_store_32(i64_store32, 4); - } -} - -macro_rules! impl_visit_unary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident(&mut self) { - self.execute_unary(UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_unary! { - fn visit_i32_eqz(i32_eqz); - fn visit_i64_eqz(i64_eqz); - - fn visit_i32_clz(i32_clz); - fn visit_i32_ctz(i32_ctz); - fn visit_i32_popcnt(i32_popcnt); - - fn visit_i64_clz(i64_clz); - fn visit_i64_ctz(i64_ctz); - fn visit_i64_popcnt(i64_popcnt); - - fn visit_f32_abs(f32_abs); - fn visit_f32_neg(f32_neg); - fn visit_f32_ceil(f32_ceil); - fn visit_f32_floor(f32_floor); - fn visit_f32_trunc(f32_trunc); - fn visit_f32_nearest(f32_nearest); - fn visit_f32_sqrt(f32_sqrt); - - fn visit_f64_abs(f64_abs); - fn visit_f64_neg(f64_neg); - fn visit_f64_ceil(f64_ceil); - fn visit_f64_floor(f64_floor); - fn visit_f64_trunc(f64_trunc); - fn visit_f64_nearest(f64_nearest); - fn visit_f64_sqrt(f64_sqrt); - - fn visit_i32_wrap_i64(i32_wrap_i64); - fn visit_i64_extend_i32_s(i64_extend_i32_s); - fn visit_i64_extend_i32_u(i64_extend_i32_u); - - fn visit_f32_convert_i32_s(f32_convert_i32_s); - fn visit_f32_convert_i32_u(f32_convert_i32_u); - fn visit_f32_convert_i64_s(f32_convert_i64_s); - fn visit_f32_convert_i64_u(f32_convert_i64_u); - fn visit_f32_demote_f64(f32_demote_f64); - fn visit_f64_convert_i32_s(f64_convert_i32_s); - fn visit_f64_convert_i32_u(f64_convert_i32_u); - fn visit_f64_convert_i64_s(f64_convert_i64_s); - fn visit_f64_convert_i64_u(f64_convert_i64_u); - fn visit_f64_promote_f32(f64_promote_f32); - - fn visit_i32_extend8_s(i32_extend8_s); - fn visit_i32_extend16_s(i32_extend16_s); - fn visit_i64_extend8_s(i64_extend8_s); - fn visit_i64_extend16_s(i64_extend16_s); - fn visit_i64_extend32_s(i64_extend32_s); - - fn visit_i32_trunc_sat_f32_s(i32_trunc_sat_f32_s); - fn visit_i32_trunc_sat_f32_u(i32_trunc_sat_f32_u); - fn visit_i32_trunc_sat_f64_s(i32_trunc_sat_f64_s); - fn visit_i32_trunc_sat_f64_u(i32_trunc_sat_f64_u); - fn visit_i64_trunc_sat_f32_s(i64_trunc_sat_f32_s); - fn visit_i64_trunc_sat_f32_u(i64_trunc_sat_f32_u); - fn visit_i64_trunc_sat_f64_s(i64_trunc_sat_f64_s); - 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)] - fn $visit_ident(&mut self) -> Result<(), TrapCode> { - self.try_execute_unary(UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_fallible_unary! { - fn visit_i32_trunc_f32_s(i32_trunc_f32_s); - fn visit_i32_trunc_f32_u(i32_trunc_f32_u); - fn visit_i32_trunc_f64_s(i32_trunc_f64_s); - fn visit_i32_trunc_f64_u(i32_trunc_f64_u); - - fn visit_i64_trunc_f32_s(i64_trunc_f32_s); - fn visit_i64_trunc_f32_u(i64_trunc_f32_u); - fn visit_i64_trunc_f64_s(i64_trunc_f64_s); - fn visit_i64_trunc_f64_u(i64_trunc_f64_u); - } -} - -macro_rules! impl_visit_binary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident(&mut self) { - self.execute_binary(UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_binary! { - fn visit_i32_eq(i32_eq); - fn visit_i32_ne(i32_ne); - fn visit_i32_lt_s(i32_lt_s); - fn visit_i32_lt_u(i32_lt_u); - fn visit_i32_gt_s(i32_gt_s); - fn visit_i32_gt_u(i32_gt_u); - fn visit_i32_le_s(i32_le_s); - fn visit_i32_le_u(i32_le_u); - fn visit_i32_ge_s(i32_ge_s); - fn visit_i32_ge_u(i32_ge_u); - - fn visit_i64_eq(i64_eq); - fn visit_i64_ne(i64_ne); - fn visit_i64_lt_s(i64_lt_s); - fn visit_i64_lt_u(i64_lt_u); - fn visit_i64_gt_s(i64_gt_s); - fn visit_i64_gt_u(i64_gt_u); - fn visit_i64_le_s(i64_le_s); - fn visit_i64_le_u(i64_le_u); - fn visit_i64_ge_s(i64_ge_s); - fn visit_i64_ge_u(i64_ge_u); - - fn visit_f32_eq(f32_eq); - fn visit_f32_ne(f32_ne); - fn visit_f32_lt(f32_lt); - fn visit_f32_gt(f32_gt); - fn visit_f32_le(f32_le); - fn visit_f32_ge(f32_ge); - - fn visit_f64_eq(f64_eq); - fn visit_f64_ne(f64_ne); - fn visit_f64_lt(f64_lt); - fn visit_f64_gt(f64_gt); - fn visit_f64_le(f64_le); - fn visit_f64_ge(f64_ge); - - fn visit_i32_add(i32_add); - fn visit_i32_sub(i32_sub); - fn visit_i32_mul(i32_mul); - fn visit_i32_and(i32_and); - fn visit_i32_or(i32_or); - fn visit_i32_xor(i32_xor); - fn visit_i32_shl(i32_shl); - fn visit_i32_shr_s(i32_shr_s); - fn visit_i32_shr_u(i32_shr_u); - fn visit_i32_rotl(i32_rotl); - fn visit_i32_rotr(i32_rotr); - - fn visit_i64_add(i64_add); - fn visit_i64_sub(i64_sub); - fn visit_i64_mul(i64_mul); - fn visit_i64_and(i64_and); - fn visit_i64_or(i64_or); - fn visit_i64_xor(i64_xor); - fn visit_i64_shl(i64_shl); - fn visit_i64_shr_s(i64_shr_s); - fn visit_i64_shr_u(i64_shr_u); - fn visit_i64_rotl(i64_rotl); - fn visit_i64_rotr(i64_rotr); - - fn visit_f32_add(f32_add); - fn visit_f32_sub(f32_sub); - fn visit_f32_mul(f32_mul); - fn visit_f32_div(f32_div); - fn visit_f32_min(f32_min); - fn visit_f32_max(f32_max); - fn visit_f32_copysign(f32_copysign); - - fn visit_f64_add(f64_add); - fn visit_f64_sub(f64_sub); - fn visit_f64_mul(f64_mul); - fn visit_f64_div(f64_div); - fn visit_f64_min(f64_min); - fn visit_f64_max(f64_max); - fn visit_f64_copysign(f64_copysign); - } -} - -macro_rules! impl_visit_fallible_binary { - ( $( fn $visit_ident:ident($untyped_ident:ident); )* ) => { - $( - #[inline(always)] - fn $visit_ident(&mut self) -> Result<(), TrapCode> { - self.try_execute_binary(UntypedValue::$untyped_ident) - } - )* - } -} -impl<'ctx, 'engine> Executor<'ctx, 'engine> { - impl_visit_fallible_binary! { - fn visit_i32_div_s(i32_div_s); - fn visit_i32_div_u(i32_div_u); - fn visit_i32_rem_s(i32_rem_s); - fn visit_i32_rem_u(i32_rem_u); - - fn visit_i64_div_s(i64_div_s); - fn visit_i64_div_u(i64_div_u); - fn visit_i64_rem_s(i64_rem_s); - fn visit_i64_rem_u(i64_rem_u); - } -} diff --git a/legacy/src/engine/func_builder/control_frame.rs b/legacy/src/engine/func_builder/control_frame.rs deleted file mode 100644 index b80ce4fe8..000000000 --- a/legacy/src/engine/func_builder/control_frame.rs +++ /dev/null @@ -1,425 +0,0 @@ -use super::{labels::LabelRef, Instr}; -use crate::module::BlockType; - -/// 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: Instr) { - assert!( - self.consume_fuel.is_some(), - "can only update the consume 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 a 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`]. - /// In order 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/legacy/src/engine/func_builder/control_stack.rs b/legacy/src/engine/func_builder/control_stack.rs deleted file mode 100644 index d63bfafda..000000000 --- a/legacy/src/engine/func_builder/control_stack.rs +++ /dev/null @@ -1,78 +0,0 @@ -use super::ControlFrame; -use alloc::vec::Vec; - -/// 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 control flow frame from 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", - ) - }) - } -} diff --git a/legacy/src/engine/func_builder/error.rs b/legacy/src/engine/func_builder/error.rs deleted file mode 100644 index be30b78b2..000000000 --- a/legacy/src/engine/func_builder/error.rs +++ /dev/null @@ -1,108 +0,0 @@ -use crate::engine::bytecode::DropKeepError; -use alloc::boxed::Box; -use core::fmt::{self, Display}; - -/// An error that may occur upon parsing, validating and translating Wasm. -#[derive(Debug)] -pub struct TranslationError { - /// The inner error type encapsulating internal error state. - inner: Box, -} - -impl TranslationError { - /// Create a new [`TranslationError`] from the inner variant. - #[cold] - #[inline] - pub fn new(inner: TranslationErrorInner) -> Self { - Self { - inner: Box::new(inner), - } - } - - /// Creates a new error indicating an unsupported Wasm block type. - pub fn unsupported_block_type(block_type: wasmparser::BlockType) -> Self { - Self { - inner: Box::new(TranslationErrorInner::UnsupportedBlockType(block_type)), - } - } - - /// Creates a new error indicating an unsupported Wasm value type. - pub fn unsupported_value_type(value_type: wasmparser::ValType) -> Self { - Self { - inner: Box::new(TranslationErrorInner::UnsupportedValueType(value_type)), - } - } -} - -impl From for TranslationError { - fn from(error: wasmparser::BinaryReaderError) -> Self { - Self { - inner: Box::new(TranslationErrorInner::Validate(error)), - } - } -} - -impl From for TranslationError { - fn from(error: DropKeepError) -> Self { - Self { - inner: Box::new(TranslationErrorInner::DropKeep(error)), - } - } -} - -impl Display for TranslationError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match &*self.inner { - TranslationErrorInner::Validate(error) => error.fmt(f), - TranslationErrorInner::UnsupportedBlockType(error) => { - write!(f, "encountered unsupported Wasm block type: {error:?}") - } - TranslationErrorInner::UnsupportedValueType(error) => { - write!(f, "encountered unsupported Wasm value type: {error:?}") - } - TranslationErrorInner::DropKeep(error) => error.fmt(f), - TranslationErrorInner::BranchTableTargetsOutOfBounds => { - write!( - f, - "branch table targets are out of bounds for wasmi bytecode" - ) - } - TranslationErrorInner::ConstRefOutOfBounds => { - write!( - f, - "constant reference index is out of bounds for wasmi bytecode" - ) - } - TranslationErrorInner::BranchOffsetOutOfBounds => { - write!(f, "branching offset is out of bounds for wasmi bytecode") - } - TranslationErrorInner::BlockFuelOutOfBounds => { - write!( - f, - "fuel required to execute a block is out of bounds for wasmi bytecode" - ) - } - } - } -} - -/// The inner error type encapsulating internal [`TranslationError`] state. -#[derive(Debug)] -pub enum TranslationErrorInner { - /// There was either a problem parsing a Wasm input OR validating a Wasm input. - Validate(wasmparser::BinaryReaderError), - /// Encountered an unsupported Wasm block type. - UnsupportedBlockType(wasmparser::BlockType), - /// Encountered an unsupported Wasm value type. - UnsupportedValueType(wasmparser::ValType), - /// An error with limitations of `DropKeep`. - DropKeep(DropKeepError), - /// When using too many branch table targets. - BranchTableTargetsOutOfBounds, - /// Branching offset out of bounds. - BranchOffsetOutOfBounds, - /// Fuel required for a block is out of bounds. - BlockFuelOutOfBounds, - /// The constant reference index is out of bounds. - ConstRefOutOfBounds, -} diff --git a/legacy/src/engine/func_builder/inst_builder.rs b/legacy/src/engine/func_builder/inst_builder.rs deleted file mode 100644 index ea3c2b2ac..000000000 --- a/legacy/src/engine/func_builder/inst_builder.rs +++ /dev/null @@ -1,336 +0,0 @@ -//! Abstractions to build up instructions forming Wasm function bodies. - -use super::{ - labels::{LabelRef, LabelRegistry}, - TranslationError, -}; -use crate::engine::{ - bytecode::{BranchOffset, FuncIdx, InstrMeta, Instruction}, - CompiledFunc, - DropKeep, - Engine, -}; -use alloc::vec::Vec; - -/// A reference to an instruction of the partially -/// constructed function body of the [`InstructionsBuilder`]. -#[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct Instr(u32); - -impl Instr { - /// Creates an [`Instr`] from the given `usize` value. - /// - /// # Note - /// - /// This intentionally is an API intended for test purposes only. - /// - /// # Panics - /// - /// If the `value` exceeds limitations for [`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 an `usize` representation of the instruction index. - pub fn into_usize(self) -> usize { - self.0 as usize - } - - /// Creates an [`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 - } -} - -/// The relative depth of a Wasm branching target. -#[derive(Debug, Copy, Clone)] -pub struct RelativeDepth(u32); - -impl RelativeDepth { - /// Returns the relative depth as `u32`. - pub fn into_u32(self) -> u32 { - self.0 - } - - /// Creates a relative depth from the given `u32` value. - pub fn from_u32(relative_depth: u32) -> Self { - Self(relative_depth) - } -} - -/// An instruction builder. -/// -/// Allows to incrementally and efficiently build up the instructions -/// of a Wasm function body. -/// Can be reused to build multiple functions consecutively. -#[derive(Debug, Default)] -pub struct InstructionsBuilder { - /// The instructions of the partially constructed function body. - insts: Vec, - metas: Vec, - /// All labels and their uses. - labels: LabelRegistry, - /// Instruction meta state (pc and opcode number) - temp_meta: InstrMeta, -} - -impl InstructionsBuilder { - /// Resets the [`InstructionsBuilder`] to allow for reuse. - pub fn reset(&mut self) { - self.insts.clear(); - self.labels.reset(); - } - - /// Returns the current instruction pointer as index. - pub fn current_pc(&self) -> Instr { - Instr::from_usize(self.insts.len()) - } - - /// Creates a new unresolved label and returns an index to it. - pub fn new_label(&mut self) -> LabelRef { - self.labels.new_label() - } - - /// Resolve the label at the current instruction position. - /// - /// Does nothing if the label has already been resolved. - /// - /// # Note - /// - /// This is used at a position of the Wasm bytecode where it is clear that - /// the given label can be resolved properly. - /// This usually takes place when encountering the Wasm `End` operand for example. - pub fn pin_label_if_unpinned(&mut self, label: LabelRef) { - self.labels.try_pin_label(label, self.current_pc()) - } - - /// Resolve the label at the current instruction position. - /// - /// # Note - /// - /// This is used at a position of the Wasm bytecode where it is clear that - /// the given label can be resolved properly. - /// This usually takes place when encountering the Wasm `End` operand for example. - /// - /// # Panics - /// - /// If the label has already been resolved. - pub fn pin_label(&mut self, label: LabelRef) { - self.labels - .pin_label(label, self.current_pc()) - .unwrap_or_else(|err| panic!("failed to pin label: {err}")); - } - - /// Pushes the internal instruction bytecode to the [`InstructionsBuilder`]. - /// - /// Returns an [`Instr`] to refer to the pushed instruction. - pub fn push_inst(&mut self, inst: Instruction) -> Instr { - let idx = self.current_pc(); - self.insts.push(inst); - self.metas.push(self.temp_meta); - idx - } - - /// Pushes an [`Instruction::BrAdjust`] to the [`InstructionsBuilder`]. - /// - /// Returns an [`Instr`] to refer to the pushed instruction. - pub fn push_br_adjust_instr( - &mut self, - branch_offset: BranchOffset, - drop_keep: DropKeep, - ) -> Instr { - let idx = self.push_inst(Instruction::BrAdjust(branch_offset)); - self.push_inst(Instruction::Return(drop_keep)); - idx - } - - /// Pushes an [`Instruction::BrAdjustIfNez`] to the [`InstructionsBuilder`]. - /// - /// Returns an [`Instr`] to refer to the pushed instruction. - pub fn push_br_adjust_nez_instr( - &mut self, - branch_offset: BranchOffset, - drop_keep: DropKeep, - ) -> Instr { - let idx = self.push_inst(Instruction::BrAdjustIfNez(branch_offset)); - self.push_inst(Instruction::Return(drop_keep)); - idx - } - - /// Try resolving the `label` for the currently constructed instruction. - /// - /// Returns an uninitialized [`BranchOffset`] if the `label` cannot yet - /// be resolved and defers resolution to later. - pub fn try_resolve_label(&mut self, label: LabelRef) -> Result { - let user = self.current_pc(); - self.try_resolve_label_for(label, user) - } - - pub fn register_meta(&mut self, pc: usize, opcode: u16) { - self.temp_meta = InstrMeta::new(pc, opcode, self.metas.len()); - } - - /// Try resolving the `label` for the given `instr`. - /// - /// Returns an uninitialized [`BranchOffset`] if the `label` cannot yet - /// be resolved and defers resolution to later. - pub fn try_resolve_label_for( - &mut self, - label: LabelRef, - instr: Instr, - ) -> Result { - self.labels.try_resolve_label(label, instr) - } - - /// Finishes construction of the function body instructions. - /// - /// # Note - /// - /// This feeds the built-up instructions of the function body - /// into the [`Engine`] so that the [`Engine`] is - /// aware of the Wasm function existence. Returns a [`CompiledFunc`] - /// reference that allows to retrieve the instructions. - pub fn finish( - &mut self, - engine: &Engine, - func: CompiledFunc, - len_locals: usize, - local_stack_height: usize, - ) -> Result<(), TranslationError> { - self.update_branch_offsets()?; - if engine.config().get_rwasm_config().is_some() { - self.update_max_stack_height(local_stack_height, len_locals); - } - assert_eq!( - self.insts.len(), - self.metas.len(), - "instr and meta length mismatch" - ); - engine.init_func( - func, - len_locals, - local_stack_height, - self.insts.drain(..), - self.metas.drain(..), - ); - Ok(()) - } - - pub fn finalize(mut self) -> Result<(Vec, Vec), TranslationError> { - self.update_branch_offsets()?; - assert_eq!( - self.insts.len(), - self.metas.len(), - "instr and meta length mismatch" - ); - Ok((self.insts, self.metas)) - } - - pub fn last(&self) -> Option<&Instruction> { - self.insts.last() - } - - pub fn last_nth_mut(&mut self, n: usize) -> Option<&mut Instruction> { - self.insts.iter_mut().rev().nth(n) - } - - pub fn len(&self) -> usize { - self.insts.len() - } - - pub fn instrs(&self) -> &Vec { - &self.insts - } - - /// Updates the branch offsets of all branch instructions inplace. - /// - /// # Panics - /// - /// If this is used before all branching labels have been pinned. - fn update_branch_offsets(&mut self) -> Result<(), TranslationError> { - for (user, offset) in self.labels.resolved_users() { - self.insts[user.into_usize()].update_branch_offset(offset?); - } - Ok(()) - } - - fn update_max_stack_height(&mut self, max_stack_height_value: usize, _num_locals_value: usize) { - let mut iter = self.insts.iter_mut().take(3); - loop { - let opcode = iter.next().unwrap(); - match opcode { - Instruction::ConsumeFuel(_) | Instruction::SignatureCheck(_) => {} - Instruction::StackAlloc { max_stack_height } => { - *max_stack_height = max_stack_height_value as u32; - return; - } - _ => unreachable!("rwasm: not allowed opcode"), - } - } - } - - /// Adds the given `delta` amount of fuel to the [`ConsumeFuel`] instruction `instr`. - /// - /// # Panics - /// - /// - If `instr` does not resolve to a [`ConsumeFuel`] instruction. - /// - If the amount of consumed fuel for `instr` overflows. - /// - /// [`ConsumeFuel`]: enum.Instruction.html#variant.ConsumeFuel - pub fn bump_fuel_consumption( - &mut self, - instr: Instr, - delta: u64, - ) -> Result<(), TranslationError> { - self.insts[instr.into_usize()].bump_fuel_consumption(delta) - } -} - -impl Instruction { - pub fn get_jump_offset(&self) -> Option { - match self { - Instruction::Br(offset) => Some(*offset), - Instruction::BrIfEqz(offset) => Some(*offset), - Instruction::BrIfNez(offset) => Some(*offset), - Instruction::BrAdjust(offset) => Some(*offset), - Instruction::BrAdjustIfNez(offset) => Some(*offset), - _ => None, - } - } - - pub fn update_call_index(&mut self, new_index: u32) { - match self { - Instruction::ReturnCall(func) => *func = FuncIdx::from(new_index), - Instruction::Call(func) => *func = FuncIdx::from(new_index), - Instruction::ReturnCallInternal(func) => *func = CompiledFunc::from(new_index), - Instruction::CallInternal(func) => *func = CompiledFunc::from(new_index), - Instruction::RefFunc(func) => *func = FuncIdx::from(new_index), - _ => panic!("tried to update call index of a non-call instruction: {self:?}"), - } - } - - /// Updates the [`BranchOffset`] for the branch [`Instruction]. - /// - /// # Panics - /// - /// If `self` is not a branch [`Instruction`]. - pub fn update_branch_offset>(&mut self, new_offset: I) { - let new_offset: BranchOffset = new_offset.into(); - match self { - Instruction::Br(offset) - | Instruction::BrIfEqz(offset) - | Instruction::BrIfNez(offset) - | Instruction::BrAdjust(offset) - | Instruction::BrAdjustIfNez(offset) => *offset = new_offset, - _ => panic!("tried to update branch offset of a non-branch instruction: {self:?}"), - } - } -} diff --git a/legacy/src/engine/func_builder/labels.rs b/legacy/src/engine/func_builder/labels.rs deleted file mode 100644 index 02b8802c8..000000000 --- a/legacy/src/engine/func_builder/labels.rs +++ /dev/null @@ -1,212 +0,0 @@ -use super::{Instr, TranslationError}; -use crate::engine::bytecode::BranchOffset; -use alloc::vec::Vec; -use core::{ - fmt::{self, Display}, - slice::Iter as SliceIter, -}; - -/// A label during the `wasmi` compilation process. -#[derive(Debug, Copy, Clone)] -pub enum Label { - /// The label has already been pinned to a particular [`Instr`]. - Pinned(Instr), - /// 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 to allocate new labels pin them and resolve pinned ones. -#[derive(Debug, Default)] -pub struct LabelRegistry { - labels: Vec