diff --git a/.cargo/config.toml b/.cargo/config.toml index 743421a..3e15584 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -2,6 +2,6 @@ version-dev="workspaces version --no-git-commit --force tinywasm*" dev="run -- -l debug run" -test-mvp="test --package tinywasm --test test-mvp" -test-wast="test --package tinywasm --test test-wast --" -generate-charts="test --package tinywasm --test generate-charts" +test-mvp="test --package tinywasm --test test-mvp --release -- --enable " +test-wast="test --package tinywasm --test test-wast -- --enable " +generate-charts="test --package tinywasm --test generate-charts -- --enable " diff --git a/.github/workflows/test.yaml b/.github/workflows/test.yaml index c4ae296..0baaba9 100644 --- a/.github/workflows/test.yaml +++ b/.github/workflows/test.yaml @@ -13,6 +13,8 @@ jobs: steps: - uses: actions/checkout@v4 + with: + submodules: true - name: Install stable Rust toolchain run: | @@ -20,10 +22,10 @@ jobs: rustup default stable - name: Build (stable) - run: cargo +stable build --verbose + run: cargo +stable build --workspace --exclude wasm-testsuite - name: Run tests (stable) - run: cargo +stable test --verbose + run: cargo +stable test --workspace --exclude wasm-testsuite test-no-std: name: Test without default features on nightly Rust @@ -37,7 +39,7 @@ jobs: rustup default nightly - name: Build (nightly, no default features) - run: cargo +nightly build --verbose --no-default-features + run: cargo +nightly build --workspace --exclude wasm-testsuite --no-default-features - name: Run tests (nightly, no default features) - run: cargo +nightly test --verbose --no-default-features + run: cargo +nightly test --workspace --exclude wasm-testsuite --no-default-features diff --git a/.gitignore b/.gitignore index 1e56606..7bb669d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ /target notes.md examples/tinywasm.wat -examples/wast/* \ No newline at end of file +examples/wast/* diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4f9768f --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "search.exclude": { + "**/wasm-testsuite/data": true + } +} \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index acee43d..0bbe617 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -71,7 +71,7 @@ dependencies = [ "argh_shared", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -139,9 +139,9 @@ dependencies = [ [[package]] name = "bstr" -version = "1.8.0" +version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "542f33a8835a0884b006a0c3df3dadd99c0c3f296ed26c2fdc8028e01ad6230c" +checksum = "c48f0051a4b4c5e0b6d365cd04af53aeaa209e3cc15ec2cdb69e73cc87fbd0dc" dependencies = [ "memchr", "serde", @@ -310,9 +310,9 @@ dependencies = [ [[package]] name = "cpufeatures" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0" +checksum = "53fe5e26ff1b7aef8bca9c6080520cfb8d9333c7568e1829cef191a9723e5504" dependencies = [ "libc", ] @@ -525,9 +525,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.11" +version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe9006bed769170c11f845cf00c7c1e9092aeb3f268e007c3e760ac68008070f" +checksum = "190092ea657667030ac6a35e305e62fc4dd69fd98ac98631e5d3a2b1575a12b5" dependencies = [ "cfg-if", "libc", @@ -586,9 +586,9 @@ checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "iana-time-zone" -version = "0.1.58" +version = "0.1.59" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8326b86b6cff230b97d0d312a6c40a60726df3332e721f72a1b035f451663b20" +checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539" dependencies = [ "android_system_properties", "core-foundation-sys", @@ -636,13 +636,13 @@ checksum = "8e04e2fd2b8188ea827b32ef11de88377086d690286ab35747ef7f9bf3ccb590" [[package]] name = "is-terminal" -version = "0.4.9" +version = "0.4.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb0889898416213fab133e1d33a0e5858a48177452750691bde3666d0fdbaf8b" +checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455" dependencies = [ "hermit-abi", "rustix", - "windows-sys 0.48.0", + "windows-sys 0.52.0", ] [[package]] @@ -680,9 +680,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.151" +version = "0.2.152" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "302d7ab3130588088d277783b1e2d2e10c9e9e4a16dd9050e6ec93fb3e7048f4" +checksum = "13e3bf6590cbc649f4d1a3eefc9d5d6eb746f5200ffb04e5e142700b8faa56e7" [[package]] name = "libloading" @@ -719,9 +719,9 @@ checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" [[package]] name = "memchr" -version = "2.6.4" +version = "2.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f665ee40bc4a3c5590afb1e9677db74a508659dfd71e126420da8274909a0167" +checksum = "523dc4f511e55ab87b694dc30d0f820d60906ef06413f93d4d7a1385599cc149" [[package]] name = "miniz_oxide" @@ -886,9 +886,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.71" +version = "1.0.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +checksum = "95fc56cda0b5c3325f5fbbd7ff9fda9e02bb00bb3dac51252d2f1bfa1cb8cc8c" dependencies = [ "unicode-ident", ] @@ -915,9 +915,9 @@ dependencies = [ [[package]] name = "quote" -version = "1.0.33" +version = "1.0.35" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "291ec9ab5efd934aaf503a6466c5d5251535d108ee747472c3977cc5acc868ef" dependencies = [ "proc-macro2", ] @@ -1017,9 +1017,9 @@ dependencies = [ [[package]] name = "rust-embed" -version = "8.1.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "810294a8a4a0853d4118e3b94bb079905f2107c7fe979d8f0faae98765eb6378" +checksum = "a82c0bbc10308ed323529fd3c1dce8badda635aa319a5ff0e6466f33b8101e3f" dependencies = [ "rust-embed-impl", "rust-embed-utils", @@ -1028,22 +1028,22 @@ dependencies = [ [[package]] name = "rust-embed-impl" -version = "8.1.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfc144a1273124a67b8c1d7cd19f5695d1878b31569c0512f6086f0f4676604e" +checksum = "6227c01b1783cdfee1bcf844eb44594cd16ec71c35305bf1c9fb5aade2735e16" dependencies = [ "proc-macro2", "quote", "rust-embed-utils", - "syn 2.0.43", + "syn 2.0.48", "walkdir", ] [[package]] name = "rust-embed-utils" -version = "8.1.0" +version = "8.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "816ccd4875431253d6bb54b804bcff4369cbde9bae33defde25fdf6c2ef91d40" +checksum = "8cb0a25bfbb2d4b4402179c2cf030387d9990857ce08a32592c6238db9fa8665" dependencies = [ "globset", "sha2", @@ -1101,35 +1101,35 @@ checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" [[package]] name = "semver" -version = "1.0.20" +version = "1.0.21" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +checksum = "b97ed7a9823b74f99c7742f5336af7be5ecd3eeafcb1507d1fa93347b1d589b0" [[package]] name = "serde" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" +checksum = "63261df402c67811e9ac6def069e4786148c4563f4b50fd4bf30aa370d626b02" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.193" +version = "1.0.195" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" +checksum = "46fe8f8603d81ba86327b23a2e9cdf49e1255fb94a4c5f297f6ee0547178ea2c" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] name = "serde_json" -version = "1.0.108" +version = "1.0.111" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" +checksum = "176e46fa42316f18edd598015a5166857fc835ec732f5215eac6b7bdbf0a84f4" dependencies = [ "itoa", "ryu", @@ -1172,9 +1172,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.43" +version = "2.0.48" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +checksum = "0f3531638e407dfc0814761abb7c00a5b54992b849452a0646b7f65c9f770f3f" dependencies = [ "proc-macro2", "quote", @@ -1198,22 +1198,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +checksum = "d54378c645627613241d077a3a79db965db602882668f9136ac42af9ecb730ad" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.52" +version = "1.0.56" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +checksum = "fa0faa943b50f3db30a20aa7e265dbc66076993efed8463e8de414e5d06d3471" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", ] [[package]] @@ -1233,7 +1233,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tinywasm" -version = "0.1.0" +version = "0.2.0" dependencies = [ "eyre", "log", @@ -1250,7 +1250,7 @@ dependencies = [ [[package]] name = "tinywasm-cli" -version = "0.1.0" +version = "0.2.0" dependencies = [ "argh", "color-eyre", @@ -1262,7 +1262,7 @@ dependencies = [ [[package]] name = "tinywasm-parser" -version = "0.1.0" +version = "0.2.0" dependencies = [ "log", "tinywasm-types", @@ -1271,7 +1271,7 @@ dependencies = [ [[package]] name = "tinywasm-types" -version = "0.1.0" +version = "0.2.0" dependencies = [ "log", "rkyv", @@ -1350,7 +1350,7 @@ dependencies = [ "once_cell", "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "wasm-bindgen-shared", ] @@ -1372,7 +1372,7 @@ checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" dependencies = [ "proc-macro2", "quote", - "syn 2.0.43", + "syn 2.0.48", "wasm-bindgen-backend", "wasm-bindgen-shared", ] @@ -1385,9 +1385,9 @@ checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" [[package]] name = "wasm-encoder" -version = "0.38.1" +version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ad2b51884de9c7f4fe2fd1043fccb8dcad4b1e29558146ee57a144d15779f3f" +checksum = "111495d6204760238512f57a9af162f45086504da332af210f2f75dd80b34f1d" dependencies = [ "leb128", ] @@ -1410,9 +1410,9 @@ dependencies = [ [[package]] name = "wast" -version = "69.0.1" +version = "70.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ee37317321afde358e4d7593745942c48d6d17e0e6e943704de9bbee121e7a" +checksum = "2ee4bc54bbe1c6924160b9f75e374a1d07532e7580eb632c0ee6cdd109bb217e" dependencies = [ "leb128", "memchr", @@ -1469,11 +1469,11 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.51.1" +version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1f8cf84f35d2db49a46868f947758c7a1138116f7fac3bc844f43ade1292e64" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ - "windows-targets 0.48.5", + "windows-targets 0.52.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index bf078c5..90f4e32 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,7 @@ default-members=["crates/cli"] resolver="2" [workspace.package] -version="0.1.0" +version="0.2.0" edition="2021" license="MIT OR Apache-2.0" authors=["Henry Gressmann "] diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index 1a23d16..65a9e72 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -14,12 +14,12 @@ path="src/bin.rs" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -tinywasm={version="0.1.0", path="../tinywasm", features=["std", "parser"]} +tinywasm={version="0.2.0-alpha.0", path="../tinywasm", features=["std", "parser"]} argh="0.1" color-eyre={version="0.6", default-features=false} log="0.4" pretty_env_logger="0.5" -wast={version="69.0", optional=true} +wast={version="70.0", optional=true} [features] default=["wat"] diff --git a/crates/cli/src/bin.rs b/crates/cli/src/bin.rs index 2712751..c8ab379 100644 --- a/crates/cli/src/bin.rs +++ b/crates/cli/src/bin.rs @@ -114,10 +114,10 @@ fn main() -> Result<()> { fn run(module: Module, func: Option, args: Vec) -> Result<()> { let mut store = tinywasm::Store::default(); - let instance = module.instantiate(&mut store)?; + let instance = module.instantiate(&mut store, None)?; if let Some(func) = func { - let func = instance.get_func(&store, &func)?; + let func = instance.exported_func_by_name(&store, &func)?; let res = func.call(&mut store, &args)?; info!("{res:?}"); } diff --git a/crates/parser/Cargo.toml b/crates/parser/Cargo.toml index 29908b6..9e6dc71 100644 --- a/crates/parser/Cargo.toml +++ b/crates/parser/Cargo.toml @@ -9,10 +9,9 @@ repository.workspace=true [dependencies] # fork of wasmparser with no_std support, see https://github.com/bytecodealliance/wasmtime/issues/3495 -# TODO: create dependency free parser wasmparser={version="0.100", package="wasmparser-nostd", default-features=false} log={version="0.4", optional=true} -tinywasm-types={version="0.1.0", path="../types"} +tinywasm-types={version="0.2.0-alpha.0", path="../types"} [features] default=["std", "logging"] diff --git a/crates/parser/src/conversion.rs b/crates/parser/src/conversion.rs index b962ff4..b9e674e 100644 --- a/crates/parser/src/conversion.rs +++ b/crates/parser/src/conversion.rs @@ -280,6 +280,8 @@ pub(crate) fn convert_memarg(memarg: wasmparser::MemArg) -> MemArg { MemArg { offset: memarg.offset, align: memarg.align, + align_max: memarg.max_align, + mem_addr: memarg.memory, } } @@ -296,6 +298,9 @@ pub(crate) fn process_const_operators(ops: OperatorsReader) -> Result Result { match op { + wasmparser::Operator::RefNull { ty } => Ok(ConstInstruction::RefNull(convert_valtype(&ty))), + wasmparser::Operator::RefFunc { function_index } => Ok(ConstInstruction::RefFunc(function_index)), + wasmparser::Operator::I32Const { value } => Ok(ConstInstruction::I32Const(value)), wasmparser::Operator::I64Const { value } => Ok(ConstInstruction::I64Const(value)), wasmparser::Operator::F32Const { value } => Ok(ConstInstruction::F32Const(f32::from_bits(value.bits()))), // TODO: check if this is correct @@ -414,12 +419,15 @@ pub fn process_operators<'a>( LocalTee { local_index } => Instruction::LocalTee(local_index), GlobalGet { global_index } => Instruction::GlobalGet(global_index), GlobalSet { global_index } => Instruction::GlobalSet(global_index), - MemorySize { .. } => Instruction::MemorySize, - MemoryGrow { .. } => Instruction::MemoryGrow, + MemorySize { mem, mem_byte } => Instruction::MemorySize(mem, mem_byte), + MemoryGrow { mem, mem_byte } => Instruction::MemoryGrow(mem, mem_byte), I32Const { value } => Instruction::I32Const(value), I64Const { value } => Instruction::I64Const(value), - F32Const { value } => Instruction::F32Const(f32::from_bits(value.bits())), // TODO: check if this is correct - F64Const { value } => Instruction::F64Const(f64::from_bits(value.bits())), // TODO: check if this is correct + F32Const { value } => Instruction::F32Const(f32::from_bits(value.bits())), + F64Const { value } => Instruction::F64Const(f64::from_bits(value.bits())), + RefNull { ty } => Instruction::RefNull(convert_valtype(&ty)), + RefIsNull => Instruction::RefIsNull, + RefFunc { function_index } => Instruction::RefFunc(function_index), I32Load { memarg } => Instruction::I32Load(convert_memarg(memarg)), I64Load { memarg } => Instruction::I64Load(convert_memarg(memarg)), F32Load { memarg } => Instruction::F32Load(convert_memarg(memarg)), @@ -580,10 +588,11 @@ pub fn process_operators<'a>( I64TruncSatF64S => Instruction::I64TruncSatF64S, I64TruncSatF64U => Instruction::I64TruncSatF64U, op => { + log::error!("Unsupported instruction: {:?}", op); return Err(crate::ParseError::UnsupportedOperator(format!( "Unsupported instruction: {:?}", op - ))) + ))); } }; diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 22e2661..561d5c6 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -10,6 +10,7 @@ extern crate alloc; #[allow(clippy::single_component_path_imports)] use log; +// noop fallback if logging is disabled. #[cfg(not(feature = "logging"))] mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); diff --git a/crates/tinywasm/Cargo.toml b/crates/tinywasm/Cargo.toml index 3f97364..931bb45 100644 --- a/crates/tinywasm/Cargo.toml +++ b/crates/tinywasm/Cargo.toml @@ -14,12 +14,12 @@ path="src/lib.rs" [dependencies] log={version="0.4", optional=true} -tinywasm-parser={version="0.1.0", path="../parser", default-features=false, optional=true} -tinywasm-types={version="0.1.0", path="../types", default-features=false} +tinywasm-parser={version="0.2.0-alpha.0", path="../parser", default-features=false, optional=true} +tinywasm-types={version="0.2.0-alpha.0", path="../types", default-features=false} [dev-dependencies] wasm-testsuite={path="../wasm-testsuite"} -wast={version="69.0"} +wast={version="70.0"} owo-colors={version="4.0"} eyre={version="0.6"} serde_json={version="1.0"} diff --git a/crates/tinywasm/src/error.rs b/crates/tinywasm/src/error.rs index 208dab1..a7dac81 100644 --- a/crates/tinywasm/src/error.rs +++ b/crates/tinywasm/src/error.rs @@ -13,7 +13,14 @@ pub enum Trap { Unreachable, /// An out-of-bounds memory access occurred - MemoryOutOfBounds, + MemoryOutOfBounds { + /// The offset of the access + offset: usize, + /// The size of the access + len: usize, + /// The maximum size of the memory + max: usize, + }, /// A division by zero occurred DivisionByZero, @@ -54,6 +61,9 @@ pub enum Error { /// The label stack is empty LabelStackUnderflow, + /// An invalid label type was encountered + InvalidLabelType, + /// The call stack is empty CallStackEmpty, @@ -72,6 +82,7 @@ impl Display for Error { Self::Trap(trap) => write!(f, "trap: {:?}", trap), + Self::InvalidLabelType => write!(f, "invalid label type"), Self::Other(message) => write!(f, "unknown error: {}", message), Self::UnsupportedFeature(feature) => write!(f, "unsupported feature: {}", feature), Self::FuncDidNotReturn => write!(f, "function did not return"), diff --git a/crates/tinywasm/src/func.rs b/crates/tinywasm/src/func.rs index 58890f6..b0b359d 100644 --- a/crates/tinywasm/src/func.rs +++ b/crates/tinywasm/src/func.rs @@ -19,7 +19,7 @@ pub struct FuncHandle { } impl FuncHandle { - /// Call a function + /// Call a function (Invocation) /// /// See pub fn call(&self, store: &mut Store, params: &[WasmValue]) -> Result> { @@ -57,18 +57,23 @@ impl FuncHandle { let call_frame = CallFrame::new(self.addr as usize, params, func_inst.locals().to_vec()); // 7. Push the frame f to the call stack + // & 8. Push the values to the stack (Not needed since the call frame owns the values) stack.call_stack.push(call_frame); - // 8. Push the values to the stack (Not needed since the call frame owns the values) - // 9. Invoke the function instance let runtime = store.runtime(); runtime.exec(store, &mut stack, self.module.clone())?; // Once the function returns: let result_m = func_ty.results.len(); - let res = stack.values.pop_n(result_m)?; + // 1. Assert: m values are on the top of the stack (Ensured by validation) + debug_assert!(stack.values.len() >= result_m); + + // 2. Pop m values from the stack + let res = stack.values.last_n(result_m)?; + + // The values are returned as the results of the invocation. Ok(res .iter() .zip(func_ty.results.iter()) diff --git a/crates/tinywasm/src/imports.rs b/crates/tinywasm/src/imports.rs new file mode 100644 index 0000000..1b3a9cf --- /dev/null +++ b/crates/tinywasm/src/imports.rs @@ -0,0 +1,92 @@ +use crate::Result; +use alloc::{ + collections::BTreeMap, + string::{String, ToString}, +}; +use tinywasm_types::{Global, GlobalType, ModuleInstanceAddr, WasmValue}; + +#[derive(Debug)] +#[non_exhaustive] +/// An external value +pub enum Extern { + /// A global value + Global(Global), + // Func(HostFunc), + // Table(Table), +} + +impl Extern { + /// Create a new global import + pub fn global(val: WasmValue, mutable: bool) -> Self { + Self::Global(Global { + ty: GlobalType { + ty: val.val_type(), + mutable, + }, + init: val.const_instr(), + }) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] +/// Name of an import +pub struct ExternName { + module: String, + name: String, +} + +#[derive(Debug, Default)] +/// Imports for a module instance +pub struct Imports { + values: BTreeMap, + modules: BTreeMap, +} + +pub(crate) struct LinkedImports { + pub(crate) values: BTreeMap, +} + +impl LinkedImports { + pub(crate) fn get(&self, module: &str, name: &str) -> Option<&Extern> { + self.values.get(&ExternName { + module: module.to_string(), + name: name.to_string(), + }) + } +} + +impl Imports { + /// Create a new empty import set + pub fn new() -> Self { + Imports { + values: BTreeMap::new(), + modules: BTreeMap::new(), + } + } + + /// Link a module + /// + /// This will automatically link all imported values + pub fn link_module(&mut self, name: &str, addr: ModuleInstanceAddr) -> Result<&mut Self> { + self.modules.insert(name.to_string(), addr); + Ok(self) + } + + /// Define an import + pub fn define(&mut self, module: &str, name: &str, value: Extern) -> Result<&mut Self> { + self.values.insert( + ExternName { + module: module.to_string(), + name: name.to_string(), + }, + value, + ); + Ok(self) + } + + pub(crate) fn link(self, _store: &mut crate::Store, _module: &crate::Module) -> Result { + // TODO: link to other modules (currently only direct imports are supported) + let values = self.values; + Ok(LinkedImports { values }) + } +} diff --git a/crates/tinywasm/src/instance.rs b/crates/tinywasm/src/instance.rs index 2fa9cf3..ad5e937 100644 --- a/crates/tinywasm/src/instance.rs +++ b/crates/tinywasm/src/instance.rs @@ -1,9 +1,11 @@ use alloc::{boxed::Box, string::ToString, sync::Arc, vec::Vec}; -use tinywasm_types::{Export, ExternalKind, FuncAddr, FuncType, ModuleInstanceAddr}; +use tinywasm_types::{ + DataAddr, ElmAddr, ExternalKind, FuncAddr, FuncType, GlobalAddr, Import, MemAddr, ModuleInstanceAddr, TableAddr, +}; use crate::{ func::{FromWasmValueTuple, IntoWasmValueTuple}, - Error, ExportInstance, FuncHandle, Result, Store, TypedFuncHandle, + Error, ExportInstance, FuncHandle, Imports, Module, Result, Store, TypedFuncHandle, }; /// A WebAssembly Module Instance @@ -14,67 +16,136 @@ use crate::{ #[derive(Debug, Clone)] pub struct ModuleInstance(Arc); +#[allow(dead_code)] #[derive(Debug)] -struct ModuleInstanceInner { +pub(crate) struct ModuleInstanceInner { pub(crate) store_id: usize, - pub(crate) _idx: ModuleInstanceAddr, - pub(crate) func_start: Option, - pub(crate) types: Box<[FuncType]>, - pub(crate) exports: ExportInstance, + pub(crate) idx: ModuleInstanceAddr, + pub(crate) types: Box<[FuncType]>, pub(crate) func_addrs: Vec, - // pub table_addrs: Vec, - // pub mem_addrs: Vec, - // pub global_addrs: Vec, - // pub elem_addrs: Vec, - // pub data_addrs: Vec, + pub(crate) table_addrs: Vec, + pub(crate) mem_addrs: Vec, + pub(crate) global_addrs: Vec, + pub(crate) elem_addrs: Vec, + pub(crate) data_addrs: Vec, + + pub(crate) func_start: Option, + pub(crate) imports: Box<[Import]>, + pub(crate) exports: ExportInstance, } impl ModuleInstance { + /// Get the module instance's address + pub fn id(&self) -> ModuleInstanceAddr { + self.0.idx + } + + /// Instantiate the module in the given store + /// + /// See + pub fn instantiate(store: &mut Store, module: Module, imports: Option) -> Result { + // This doesn't completely follow the steps in the spec, but the end result is the same + // Constant expressions are evaluated directly where they are used, so we + // don't need to create a auxiliary frame etc. + + let idx = store.next_module_instance_idx(); + let imports = imports.unwrap_or_default(); + + // TODO: doesn't link other modules yet + let linked_imports = imports.link(store, &module)?; + let global_addrs = store.add_globals(module.data.globals.into(), &module.data.imports, &linked_imports, idx)?; + + // TODO: imported functions missing + let func_addrs = store.add_funcs(module.data.funcs.into(), idx); + + let table_addrs = store.add_tables(module.data.table_types.into(), idx); + let mem_addrs = store.add_mems(module.data.memory_types.into(), idx)?; + + // TODO: active/declared elems need to be initialized + let elem_addrs = store.add_elems(module.data.elements.into(), idx)?; + + // TODO: active data segments need to be initialized + let data_addrs = store.add_datas(module.data.data.into(), idx); + + let instance = ModuleInstanceInner { + store_id: store.id(), + idx, + + types: module.data.func_types, + func_addrs, + table_addrs, + mem_addrs, + global_addrs, + elem_addrs, + data_addrs, + + func_start: module.data.start_func, + imports: module.data.imports, + exports: crate::ExportInstance(module.data.exports), + }; + + let instance = ModuleInstance::new(instance); + store.add_instance(instance.clone())?; + + Ok(instance) + } + /// Get the module's exports pub fn exports(&self) -> &ExportInstance { &self.0.exports } - pub(crate) fn new( - types: Box<[FuncType]>, - func_start: Option, - exports: Box<[Export]>, - func_addrs: Vec, - idx: ModuleInstanceAddr, - store_id: usize, - ) -> Self { - Self(Arc::new(ModuleInstanceInner { - store_id, - _idx: idx, - types, - func_start, - func_addrs, - exports: ExportInstance(exports), - })) + pub(crate) fn func_addrs(&self) -> &[FuncAddr] { + &self.0.func_addrs + } + + pub(crate) fn _global_addrs(&self) -> &[GlobalAddr] { + &self.0.global_addrs + } + + pub(crate) fn func_ty_addrs(&self) -> &[FuncType] { + &self.0.types + } + + pub(crate) fn new(inner: ModuleInstanceInner) -> Self { + Self(Arc::new(inner)) } pub(crate) fn func_ty(&self, addr: FuncAddr) -> &FuncType { &self.0.types[addr as usize] } + // resolve a function address to the global store address + pub(crate) fn resolve_func_addr(&self, addr: FuncAddr) -> FuncAddr { + self.0.func_addrs[addr as usize] + } + + // resolve a table address to the global store address + pub(crate) fn _resolve_table_addr(&self, addr: TableAddr) -> TableAddr { + self.0.table_addrs[addr as usize] + } + + // resolve a memory address to the global store address + pub(crate) fn resolve_mem_addr(&self, addr: MemAddr) -> MemAddr { + self.0.mem_addrs[addr as usize] + } + + // resolve a global address to the global store address + pub(crate) fn resolve_global_addr(&self, addr: GlobalAddr) -> GlobalAddr { + self.0.global_addrs[addr as usize] + } + /// Get an exported function by name - pub fn get_func(&self, store: &Store, name: &str) -> Result { + pub fn exported_func_by_name(&self, store: &Store, name: &str) -> Result { if self.0.store_id != store.id() { return Err(Error::InvalidStore); } let export = self.0.exports.get(name, ExternalKind::Func)?; - log::debug!("get_func: export: {:?}", export); - - log::debug!("{:?}", self.0.func_addrs); let func_addr = self.0.func_addrs[export.index as usize]; - log::debug!("get_func: func index: {}", export.index); let func = store.get_func(func_addr as usize)?; - log::debug!("get_func: func_addr: {}, func: {:?}", func_addr, func); let ty = self.0.types[func.ty_addr() as usize].clone(); - log::debug!("get_func: ty: {:?}", ty); - log::debug!("types: {:?}", self.0.types); Ok(FuncHandle { addr: export.index, @@ -85,12 +156,12 @@ impl ModuleInstance { } /// Get a typed exported function by name - pub fn get_typed_func(&self, store: &Store, name: &str) -> Result> + pub fn typed_func(&self, store: &Store, name: &str) -> Result> where P: IntoWasmValueTuple, R: FromWasmValueTuple, { - let func = self.get_func(store, name)?; + let func = self.exported_func_by_name(store, name)?; Ok(TypedFuncHandle { func, marker: core::marker::PhantomData, @@ -104,7 +175,7 @@ impl ModuleInstance { /// (which is not part of the spec, but used by llvm) /// /// See - pub fn get_start_func(&mut self, store: &Store) -> Result> { + pub fn start_func(&self, store: &Store) -> Result> { if self.0.store_id != store.id() { return Err(Error::InvalidStore); } @@ -121,13 +192,18 @@ impl ModuleInstance { } }; - let func_addr = self.0.func_addrs[func_index as usize]; - let func = store.get_func(func_addr as usize)?; + let func_addr = self + .0 + .func_addrs + .get(func_index as usize) + .expect("No func addr for start func, this is a bug"); + + let func = store.get_func(*func_addr as usize)?; let ty = self.0.types[func.ty_addr() as usize].clone(); Ok(Some(FuncHandle { module: self.clone(), - addr: func_addr, + addr: *func_addr, ty, name: None, })) @@ -138,8 +214,8 @@ impl ModuleInstance { /// Returns None if the module has no start function /// /// See - pub fn start(&mut self, store: &mut Store) -> Result> { - let Some(func) = self.get_start_func(store)? else { + pub fn start(&self, store: &mut Store) -> Result> { + let Some(func) = self.start_func(store)? else { return Ok(None); }; diff --git a/crates/tinywasm/src/lib.rs b/crates/tinywasm/src/lib.rs index ec48acf..0c1863f 100644 --- a/crates/tinywasm/src/lib.rs +++ b/crates/tinywasm/src/lib.rs @@ -38,12 +38,12 @@ //! // This will allocate the module and its globals into the store //! // and execute the module's start function. //! // Every ModuleInstance has its own ID space for functions, globals, etc. -//! let instance = module.instantiate(&mut store)?; +//! let instance = module.instantiate(&mut store, None)?; //! //! // Get a typed handle to the exported "add" function //! // Alternatively, you can use `instance.get_func` to get an untyped handle //! // that takes and returns WasmValue types -//! let func = instance.get_typed_func::<(i32, i32), (i32,)>(&mut store, "add")?; +//! let func = instance.typed_func::<(i32, i32), (i32,)>(&mut store, "add")?; //! let res = func.call(&mut store, (1, 2))?; //! //! assert_eq!(res, (3,)); @@ -60,7 +60,7 @@ //! a custom allocator. This removes support for parsing from files and streams, //! but otherwise the API is the same. //! -//! Additionally, if you want proper error types, you must use a `nightly` compiler. +//! Additionally, if you want proper error types, you must use a `nightly` compiler to have the error trait in core. mod std; extern crate alloc; @@ -70,6 +70,7 @@ extern crate alloc; #[allow(clippy::single_component_path_imports)] use log; +// noop fallback if logging is disabled. #[cfg(not(feature = "logging"))] pub(crate) mod log { macro_rules! debug ( ($($tt:tt)*) => {{}} ); @@ -94,6 +95,9 @@ pub use export::ExportInstance; mod func; pub use func::{FuncHandle, TypedFuncHandle}; +mod imports; +pub use imports::*; + mod runtime; pub use runtime::*; diff --git a/crates/tinywasm/src/module.rs b/crates/tinywasm/src/module.rs index 4f3575d..5a2c4f7 100644 --- a/crates/tinywasm/src/module.rs +++ b/crates/tinywasm/src/module.rs @@ -1,13 +1,13 @@ use tinywasm_types::TinyWasmModule; -use crate::{ModuleInstance, Result, Store}; +use crate::{Imports, ModuleInstance, Result, Store}; #[derive(Debug)] /// A WebAssembly Module /// /// See pub struct Module { - data: TinyWasmModule, + pub(crate) data: TinyWasmModule, } impl From<&TinyWasmModule> for Module { @@ -50,28 +50,12 @@ impl Module { /// Instantiate the module in the given store /// /// Runs the start function if it exists - /// If you want to run the start function yourself, use `ModuleInstance::new` + /// If you want to run the start function yourself, use `ModuleInstance::instantiate` /// /// See - pub fn instantiate( - self, - store: &mut Store, - // imports: Option<()>, - ) -> Result { - let idx = store.next_module_instance_idx(); - let func_addrs = store.add_funcs(self.data.funcs.into(), idx); - - let instance = ModuleInstance::new( - self.data.func_types, - self.data.start_func, - self.data.exports, - func_addrs, - idx, - store.id(), - ); - - store.add_instance(instance.clone())?; - // let _ = instance.start(store)?; + pub fn instantiate(self, store: &mut Store, imports: Option) -> Result { + let instance = ModuleInstance::instantiate(store, self, imports)?; + let _ = instance.start(store)?; Ok(instance) } } diff --git a/crates/tinywasm/src/runtime/executor/macros.rs b/crates/tinywasm/src/runtime/executor/macros.rs index 61678f2..8a92ac2 100644 --- a/crates/tinywasm/src/runtime/executor/macros.rs +++ b/crates/tinywasm/src/runtime/executor/macros.rs @@ -1,15 +1,65 @@ //! More generic macros for various instructions //! //! These macros are used to generate the actual instruction implementations. +//! In some basic tests this generated better assembly than using generic functions, even when inlined. +//! (Something to revisit in the future) -/// Convert the top value on the stack to a specific type -macro_rules! conv_1 { - ($from:ty, $to:ty, $stack:ident) => {{ - let a: $from = $stack.values.pop()?.into(); - $stack.values.push((a as $to).into()); +/// Load a value from memory +macro_rules! mem_load { + ($type:ty, $arg:ident, $stack:ident, $store:ident, $module:ident) => {{ + mem_load!($type, $type, $arg, $stack, $store, $module) + }}; + + ($load_type:ty, $target_type:ty, $arg:ident, $stack:ident, $store:ident, $module:ident) => {{ + // TODO: there could be a lot of performance improvements here + let mem_idx = $module.resolve_mem_addr($arg.mem_addr); + let mem = $store.get_mem(mem_idx as usize)?; + + let addr = $stack.values.pop()?.raw_value(); + + let val: [u8; core::mem::size_of::<$load_type>()] = { + let mem = mem.borrow_mut(); + let val = mem.load( + ($arg.offset + addr) as usize, + $arg.align as usize, + core::mem::size_of::<$load_type>(), + )?; + val.try_into().expect("slice with incorrect length") + }; + + let loaded_value = <$load_type>::from_le_bytes(val); + $stack.values.push((loaded_value as $target_type).into()); }}; } +/// Store a value to memory +macro_rules! mem_store { + ($type:ty, $arg:ident, $stack:ident, $store:ident, $module:ident) => {{ + log::debug!("mem_store!({}, {:?})", stringify!($type), $arg); + + mem_store!($type, $type, $arg, $stack, $store, $module) + }}; + + ($store_type:ty, $target_type:ty, $arg:ident, $stack:ident, $store:ident, $module:ident) => {{ + // likewise, there could be a lot of performance improvements here + let mem_idx = $module.resolve_mem_addr($arg.mem_addr); + let mem = $store.get_mem(mem_idx as usize)?; + + let val = $stack.values.pop_t::<$store_type>()?; + let addr = $stack.values.pop()?.raw_value(); + + let val = val as $store_type; + let val = val.to_le_bytes(); + + mem.borrow_mut() + .store(($arg.offset + addr) as usize, $arg.align as usize, &val)?; + }}; +} + +/// Doing the actual conversion from float to int is a bit tricky, because +/// we need to check for overflow. This macro generates the min/max values +/// for a specific conversion, which are then used in the actual conversion. +/// Rust sadly doesn't have wrapping casts for floats (yet) macro_rules! float_min_max { (f32, i32) => { (-2147483904.0_f32, 2147483648.0_f32) @@ -18,22 +68,22 @@ macro_rules! float_min_max { (-2147483649.0_f64, 2147483648.0_f64) }; (f32, u32) => { - (-1.0_f32, 4294967296.0_f32) + (-1.0_f32, 4294967296.0_f32) // 2^32 }; (f64, u32) => { - (-1.0_f64, 4294967296.0_f64) + (-1.0_f64, 4294967296.0_f64) // 2^32 }; (f32, i64) => { - (-9223373136366403584.0_f32, 9223372036854775808.0_f32) + (-9223373136366403584.0_f32, 9223372036854775808.0_f32) // 2^63 + 2^40 | 2^63 }; (f64, i64) => { - (-9223372036854777856.0_f64, 9223372036854775808.0_f64) + (-9223372036854777856.0_f64, 9223372036854775808.0_f64) // 2^63 + 2^40 | 2^63 }; (f32, u64) => { - (-1.0_f32, 18446744073709551616.0_f32) + (-1.0_f32, 18446744073709551616.0_f32) // 2^64 }; (f64, u64) => { - (-1.0_f64, 18446744073709551616.0_f64) + (-1.0_f64, 18446744073709551616.0_f64) // 2^64 }; // other conversions are not allowed ($from:ty, $to:ty) => { @@ -41,75 +91,56 @@ macro_rules! float_min_max { }; } -// Convert a float to an int, checking for overflow -macro_rules! checked_float_conv_1 { - ($from:tt, $to:tt, $stack:ident) => {{ - let (min, max) = float_min_max!($from, $to); +/// Convert a value on the stack +macro_rules! conv { + ($from:ty, $intermediate:ty, $to:ty, $stack:ident) => {{ + let a: $from = $stack.values.pop()?.into(); + $stack.values.push((a as $intermediate as $to).into()); + }}; + ($from:ty, $to:ty, $stack:ident) => {{ let a: $from = $stack.values.pop()?.into(); - - if a.is_nan() { - return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); - } - - if a <= min || a >= max { - return Err(Error::Trap(crate::Trap::IntegerOverflow)); - } - $stack.values.push((a as $to).into()); }}; } -// Convert a float to an int, checking for overflow -macro_rules! checked_float_conv_2 { - ($from:tt, $uty:tt, $to:tt, $stack:ident) => {{ - let (min, max) = float_min_max!($from, $uty); +/// Convert a value on the stack with error checking +macro_rules! checked_conv_float { + // Direct conversion with error checking (two types) + ($from:tt, $to:tt, $stack:ident) => {{ + checked_conv_float!($from, $to, $to, $stack) + }}; + // Conversion with an intermediate unsigned type and error checking (three types) + ($from:tt, $intermediate:tt, $to:tt, $stack:ident) => {{ + let (min, max) = float_min_max!($from, $intermediate); let a: $from = $stack.values.pop()?.into(); if a.is_nan() { return Err(Error::Trap(crate::Trap::InvalidConversionToInt)); } - log::info!("a: {}", a); - log::info!("min: {}", min); - log::info!("max: {}", max); - if a <= min || a >= max { return Err(Error::Trap(crate::Trap::IntegerOverflow)); } - $stack.values.push((a as $uty as $to).into()); - }}; -} - -/// Convert the unsigned value on the top of the stack to a specific type -macro_rules! conv_2 { - ($ty:ty, $uty:ty, $to:ty, $stack:ident) => {{ - let a: $ty = $stack.values.pop()?.into(); - $stack.values.push((a as $uty as $to).into()); + $stack.values.push((a as $intermediate as $to).into()); }}; } /// Compare two values on the stack macro_rules! comp { ($op:tt, $ty:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); - $stack.values.push(((a $op b) as i32).into()); + comp!($op, $ty, $ty, $stack) }}; -} -/// Compare two values on the stack (cast to ty2 before comparison) -macro_rules! comp_cast { - ($op:tt, $ty:ty, $ty2:ty, $stack:ident) => {{ + ($op:tt, $intermediate:ty, $to:ty, $stack:ident) => {{ let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); + let a: $intermediate = a.into(); + let b: $intermediate = b.into(); // Cast to unsigned type before comparison - let a_unsigned: $ty2 = a as $ty2; - let b_unsigned: $ty2 = b as $ty2; - $stack.values.push(((a_unsigned $op b_unsigned) as i32).into()); + let a = a as $to; + let b = b as $to; + $stack.values.push(((a $op b) as i32).into()); }}; } @@ -121,27 +152,35 @@ macro_rules! comp_zero { }}; } -/// Apply an arithmetic operation to two values on the stack -macro_rules! arithmetic_op { +/// Apply an arithmetic method to two values on the stack +macro_rules! arithmetic { + ($op:ident, $ty:ty, $stack:ident) => {{ + arithmetic!($op, $ty, $ty, $stack) + }}; + + // also allow operators such as +, - ($op:tt, $ty:ty, $stack:ident) => {{ let [a, b] = $stack.values.pop_n_const::<2>()?; let a: $ty = a.into(); let b: $ty = b.into(); $stack.values.push((a $op b).into()); }}; -} -macro_rules! arithmetic_method { - ($op:ident, $ty:ty, $stack:ident) => {{ + ($op:ident, $intermediate:ty, $to:ty, $stack:ident) => {{ let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); + let a: $to = a.into(); + let b: $to = b.into(); + + let a = a as $intermediate; + let b = b as $intermediate; + let result = a.$op(b); - $stack.values.push(result.into()); + $stack.values.push((result as $to).into()); }}; } -macro_rules! arithmetic_method_self { +/// Apply an arithmetic method to a single value on the stack +macro_rules! arithmetic_single { ($op:ident, $ty:ty, $stack:ident) => {{ let a: $ty = $stack.values.pop()?.into(); let result = a.$op(); @@ -149,66 +188,35 @@ macro_rules! arithmetic_method_self { }}; } -macro_rules! arithmetic_method_cast { - ($op:ident, $ty:ty, $ty2:ty, $stack:ident) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); - - // Cast to unsigned type before operation - let a_unsigned: $ty2 = a as $ty2; - let b_unsigned: $ty2 = b as $ty2; - - let result = a_unsigned.$op(b_unsigned); - $stack.values.push((result as $ty).into()); +/// Apply an arithmetic operation to two values on the stack with error checking +macro_rules! checked_arithmetic { + // Direct conversion with error checking (two types) + ($from:tt, $to:tt, $stack:ident, $trap:expr) => {{ + checked_arithmetic!($from, $to, $to, $stack, $trap) }}; -} -/// Apply an arithmetic operation to two values on the stack -macro_rules! checked_arithmetic_method { - ($op:ident, $ty:ty, $stack:ident, $trap:expr) => {{ + ($op:ident, $from:ty, $to:ty, $stack:ident, $trap:expr) => {{ let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); - let result = a.$op(b).ok_or_else(|| Error::Trap($trap))?; - debug!( - "checked_arithmetic_method: {}, a: {}, b: {}, res: {}", - stringify!($op), - a, - b, - result - ); - $stack.values.push(result.into()); - }}; -} + let a: $from = a.into(); + let b: $from = b.into(); -/// Apply an arithmetic operation to two values on the stack (cast to ty2 before operation) -macro_rules! checked_arithmetic_method_cast { - ($op:ident, $ty:ty, $ty2:ty, $stack:ident, $trap:expr) => {{ - let [a, b] = $stack.values.pop_n_const::<2>()?; - let a: $ty = a.into(); - let b: $ty = b.into(); + let a_casted: $to = a as $to; + let b_casted: $to = b as $to; - // Cast to unsigned type before operation - let a_unsigned: $ty2 = a as $ty2; - let b_unsigned: $ty2 = b as $ty2; + let result = a_casted.$op(b_casted).ok_or_else(|| Error::Trap($trap))?; - let result = a_unsigned.$op(b_unsigned).ok_or_else(|| Error::Trap($trap))?; - $stack.values.push((result as $ty).into()); + // Cast back to original type if different + $stack.values.push((result as $from).into()); }}; } -pub(super) use arithmetic_method; -pub(super) use arithmetic_method_cast; -pub(super) use arithmetic_method_self; -pub(super) use arithmetic_op; -pub(super) use checked_arithmetic_method; -pub(super) use checked_arithmetic_method_cast; -pub(super) use checked_float_conv_1; -pub(super) use checked_float_conv_2; +pub(super) use arithmetic; +pub(super) use arithmetic_single; +pub(super) use checked_arithmetic; +pub(super) use checked_conv_float; pub(super) use comp; -pub(super) use comp_cast; pub(super) use comp_zero; -pub(super) use conv_1; -pub(super) use conv_2; +pub(super) use conv; pub(super) use float_min_max; +pub(super) use mem_load; +pub(super) use mem_store; diff --git a/crates/tinywasm/src/runtime/executor/mod.rs b/crates/tinywasm/src/runtime/executor/mod.rs index 6486b38..541c0a7 100644 --- a/crates/tinywasm/src/runtime/executor/mod.rs +++ b/crates/tinywasm/src/runtime/executor/mod.rs @@ -2,10 +2,9 @@ use core::ops::{BitAnd, BitOr, BitXor, Neg}; use super::{DefaultRuntime, Stack}; use crate::{ - get_label_args, log::debug, runtime::{BlockType, LabelFrame}, - CallFrame, Error, ModuleInstance, Result, Store, + CallFrame, Error, LabelArgs, ModuleInstance, Result, Store, }; use alloc::vec::Vec; use log::info; @@ -18,6 +17,11 @@ use traits::*; impl DefaultRuntime { pub(crate) fn exec(&self, store: &mut Store, stack: &mut Stack, module: ModuleInstance) -> Result<()> { + log::info!("exports: {:?}", module.exports()); + log::info!("func_addrs: {:?}", module.func_addrs()); + log::info!("func_ty_addrs: {:?}", module.func_ty_addrs().len()); + log::info!("store funcs: {:?}", store.data.funcs.len()); + // The current call frame, gets updated inside of exec_one let mut cf = stack.call_stack.pop()?; @@ -31,6 +35,7 @@ impl DefaultRuntime { match exec_one(&mut cf, instr, instrs, stack, store, &module)? { // Continue execution at the new top of the call stack ExecResult::Call => { + cf = stack.call_stack.pop()?; func = store.get_func(cf.func_ptr)?.clone(); instrs = func.instructions(); continue; @@ -70,6 +75,23 @@ enum ExecResult { Trap(crate::Trap), } +// Break to a block at the given index (relative to the current frame) +// If there is no block at the given index, return or call the parent function +// +// This is a bit hard to see from the spec, but it's vaild to use breaks to return +// from a function, so we need to check if the label stack is empty +macro_rules! break_to { + ($cf:ident, $stack:ident, $break_to_relative:ident) => {{ + if $cf.break_to(*$break_to_relative, &mut $stack.values).is_none() { + if $stack.call_stack.is_empty() { + return Ok(ExecResult::Return); + } else { + return Ok(ExecResult::Call); + } + } + }}; +} + /// Run a single step of the interpreter /// A seperate function is used so later, we can more easily implement /// a step-by-step debugger (using generators once they're stable?) @@ -106,8 +128,9 @@ fn exec_one( Call(v) => { debug!("start call"); // prepare the call frame - let func = store.get_func(*v as usize)?; - let func_ty = module.func_ty(*v); + let func_idx = module.resolve_func_addr(*v); + let func = store.get_func(func_idx as usize)?; + let func_ty = module.func_ty(func.ty_addr()); debug!("params: {:?}", func_ty.params); debug!("stack: {:?}", stack.values); @@ -120,36 +143,36 @@ fn exec_one( stack.call_stack.push(call_frame); // call the function - *cf = stack.call_stack.pop()?; - debug!("calling: {:?}", func); return Ok(ExecResult::Call); } If(args, else_offset, end_offset) => { - let end_instr_ptr = cf.instr_ptr + *end_offset; - - info!( - "if it's true, we'll jump to the next instruction (@{})", - cf.instr_ptr + 1 - ); - - if let Some(else_offset) = else_offset { - info!( - "else: {:?} (@{})", - instrs[cf.instr_ptr + else_offset], - cf.instr_ptr + else_offset - ); - }; - - info!("end: {:?} (@{})", instrs[end_instr_ptr], end_instr_ptr); - - if stack.values.pop_t::()? != 0 { + if stack.values.pop_t::()? == 0 { + if let Some(else_offset) = else_offset { + log::info!("entering else at {}", cf.instr_ptr + *else_offset); + cf.enter_label( + LabelFrame { + instr_ptr: cf.instr_ptr + *else_offset, + end_instr_ptr: cf.instr_ptr + *end_offset, + stack_ptr: stack.values.len(), // - params, + args: crate::LabelArgs::new(*args, module)?, + ty: BlockType::Else, + }, + &mut stack.values, + ); + cf.instr_ptr += *else_offset; + } else { + log::info!("skipping if"); + cf.instr_ptr += *end_offset + } + } else { + log::info!("entering then"); cf.enter_label( LabelFrame { instr_ptr: cf.instr_ptr, end_instr_ptr: cf.instr_ptr + *end_offset, stack_ptr: stack.values.len(), // - params, - args: get_label_args(*args, module)?, + args: LabelArgs::new(*args, module)?, ty: BlockType::If, }, &mut stack.values, @@ -164,7 +187,7 @@ fn exec_one( instr_ptr: cf.instr_ptr, end_instr_ptr: cf.instr_ptr + *end_offset, stack_ptr: stack.values.len(), // - params, - args: get_label_args(*args, module)?, + args: LabelArgs::new(*args, module)?, ty: BlockType::Loop, }, &mut stack.values, @@ -177,14 +200,14 @@ fn exec_one( instr_ptr: cf.instr_ptr, end_instr_ptr: cf.instr_ptr + *end_offset, stack_ptr: stack.values.len(), //- params, - args: get_label_args(*args, module)?, + args: LabelArgs::new(*args, module)?, ty: BlockType::Block, }, &mut stack.values, ); } - BrTable(_default, len) => { + BrTable(default, len) => { let instr = instrs[cf.instr_ptr + 1..cf.instr_ptr + 1 + *len] .iter() .map(|i| match i { @@ -197,64 +220,132 @@ fn exec_one( panic!("Expected {} BrLabel instructions, got {}", len, instr.len()); } - todo!() + let idx = stack.values.pop_t::()? as usize; + if let Some(label) = instr.get(idx) { + break_to!(cf, stack, label); + } else { + break_to!(cf, stack, default); + } } - Br(v) => cf.break_to(*v, &mut stack.values)?, + Br(v) => break_to!(cf, stack, v), BrIf(v) => { if stack.values.pop_t::()? > 0 { - cf.break_to(*v, &mut stack.values)? - }; + break_to!(cf, stack, v); + } } Return => match stack.call_stack.is_empty() { true => return Ok(ExecResult::Return), - false => { - *cf = stack.call_stack.pop()?; - return Ok(ExecResult::Call); - } + false => return Ok(ExecResult::Call), }, EndFunc => { - if cf.labels.len() > 0 { - panic!("endfunc: block frames not empty, this should have been validated by the parser"); - } + debug_assert!( + cf.labels.len() == 0, + "endfunc: block frames not empty, this should have been validated by the parser" + ); match stack.call_stack.is_empty() { true => return Ok(ExecResult::Return), - false => { - *cf = stack.call_stack.pop()?; - return Ok(ExecResult::Call); - } + false => return Ok(ExecResult::Call), } } - EndBlockFrame => { - let blocks = &mut cf.labels; - - // remove the label from the label stack - let Some(block) = blocks.pop() else { - panic!("end: no label to end, this should have been validated by the parser"); + // We're essentially using else as a EndBlockFrame instruction for if blocks + Else(end_offset) => { + let Some(block) = cf.labels.pop() else { + panic!("else: no label to end, this should have been validated by the parser"); }; let res_count = block.args.results; - info!("we want to keep {} values on the stack", res_count); - info!("current block stack ptr: {}", block.stack_ptr); - info!("stack: {:?}", stack.values); + stack.values.truncate_keep(block.stack_ptr, res_count); + cf.instr_ptr += *end_offset; + } - // trim the lable's stack from the stack - stack.values.truncate_keep(block.stack_ptr, res_count) + EndBlockFrame => { + // remove the label from the label stack + let Some(block) = cf.labels.pop() else { + panic!("end: no label to end, this should have been validated by the parser"); + }; + stack.values.truncate_keep(block.stack_ptr, block.args.results) } LocalGet(local_index) => stack.values.push(cf.get_local(*local_index as usize)), LocalSet(local_index) => cf.set_local(*local_index as usize, stack.values.pop()?), LocalTee(local_index) => cf.set_local(*local_index as usize, *stack.values.last()?), + GlobalGet(global_index) => { + let idx = module.resolve_global_addr(*global_index); + let global = store.get_global_val(idx as usize)?; + stack.values.push(global); + } + + GlobalSet(global_index) => { + let idx = module.resolve_global_addr(*global_index); + store.set_global_val(idx as usize, stack.values.pop()?)?; + } + I32Const(val) => stack.values.push((*val).into()), I64Const(val) => stack.values.push((*val).into()), F32Const(val) => stack.values.push((*val).into()), F64Const(val) => stack.values.push((*val).into()), + MemorySize(addr, byte) => { + if *byte != 0 { + unimplemented!("memory.size with byte != 0"); + } + + let mem_idx = module.resolve_mem_addr(*addr); + let mem = store.get_mem(mem_idx as usize)?; + stack.values.push(mem.borrow().size().into()); + } + + MemoryGrow(addr, byte) => { + if *byte != 0 { + unimplemented!("memory.grow with byte != 0"); + } + + let mem_idx = module.resolve_mem_addr(*addr); + let mem = store.get_mem(mem_idx as usize)?; + + let (res, prev_size) = { + let mut mem = mem.borrow_mut(); + let prev_size = mem.size(); + (mem.grow(stack.values.pop_t::()?), prev_size) + }; + + match res { + Ok(_) => stack.values.push(prev_size.into()), + Err(_) => stack.values.push((-1).into()), + } + } + + I32Store(arg) => mem_store!(i32, arg, stack, store, module), + I64Store(arg) => mem_store!(i64, arg, stack, store, module), + F32Store(arg) => mem_store!(f32, arg, stack, store, module), + F64Store(arg) => mem_store!(f64, arg, stack, store, module), + I32Store8(arg) => mem_store!(i8, i32, arg, stack, store, module), + I32Store16(arg) => mem_store!(i16, i32, arg, stack, store, module), + I64Store8(arg) => mem_store!(i8, i64, arg, stack, store, module), + I64Store16(arg) => mem_store!(i16, i64, arg, stack, store, module), + I64Store32(arg) => mem_store!(i32, i64, arg, stack, store, module), + + I32Load(arg) => mem_load!(i32, arg, stack, store, module), + I64Load(arg) => mem_load!(i64, arg, stack, store, module), + F32Load(arg) => mem_load!(f32, arg, stack, store, module), + F64Load(arg) => mem_load!(f64, arg, stack, store, module), + I32Load8S(arg) => mem_load!(i8, i32, arg, stack, store, module), + I32Load8U(arg) => mem_load!(u8, i32, arg, stack, store, module), + I32Load16S(arg) => mem_load!(i16, i32, arg, stack, store, module), + I32Load16U(arg) => mem_load!(u16, i32, arg, stack, store, module), + I64Load8S(arg) => mem_load!(i8, i64, arg, stack, store, module), + I64Load8U(arg) => mem_load!(u8, i64, arg, stack, store, module), + I64Load16S(arg) => mem_load!(i16, i64, arg, stack, store, module), + I64Load16U(arg) => mem_load!(u16, i64, arg, stack, store, module), + I64Load32S(arg) => mem_load!(i32, i64, arg, stack, store, module), + I64Load32U(arg) => mem_load!(u32, i64, arg, stack, store, module), + I64Eqz => comp_zero!(==, i64, stack), I32Eqz => comp_zero!(==, i32, stack), @@ -270,122 +361,125 @@ fn exec_one( I32LtS => comp!(<, i32, stack), I64LtS => comp!(<, i64, stack), - I32LtU => comp_cast!(<, i32, u32, stack), - I64LtU => comp_cast!(<, i64, u64, stack), + I32LtU => comp!(<, i32, u32, stack), + I64LtU => comp!(<, i64, u64, stack), F32Lt => comp!(<, f32, stack), F64Lt => comp!(<, f64, stack), I32LeS => comp!(<=, i32, stack), I64LeS => comp!(<=, i64, stack), - I32LeU => comp_cast!(<=, i32, u32, stack), - I64LeU => comp_cast!(<=, i64, u64, stack), + I32LeU => comp!(<=, i32, u32, stack), + I64LeU => comp!(<=, i64, u64, stack), F32Le => comp!(<=, f32, stack), F64Le => comp!(<=, f64, stack), I32GeS => comp!(>=, i32, stack), I64GeS => comp!(>=, i64, stack), - I32GeU => comp_cast!(>=, i32, u32, stack), - I64GeU => comp_cast!(>=, i64, u64, stack), + I32GeU => comp!(>=, i32, u32, stack), + I64GeU => comp!(>=, i64, u64, stack), F32Ge => comp!(>=, f32, stack), F64Ge => comp!(>=, f64, stack), I32GtS => comp!(>, i32, stack), I64GtS => comp!(>, i64, stack), - I32GtU => comp_cast!(>, i32, u32, stack), - I64GtU => comp_cast!(>, i64, u64, stack), + I32GtU => comp!(>, i32, u32, stack), + I64GtU => comp!(>, i64, u64, stack), F32Gt => comp!(>, f32, stack), F64Gt => comp!(>, f64, stack), - I64Add => arithmetic_method!(wrapping_add, i64, stack), - I32Add => arithmetic_method!(wrapping_add, i32, stack), - F32Add => arithmetic_op!(+, f32, stack), - F64Add => arithmetic_op!(+, f64, stack), + I64Add => arithmetic!(wrapping_add, i64, stack), + I32Add => arithmetic!(wrapping_add, i32, stack), + F32Add => arithmetic!(+, f32, stack), + F64Add => arithmetic!(+, f64, stack), - I32Sub => arithmetic_method!(wrapping_sub, i32, stack), - I64Sub => arithmetic_method!(wrapping_sub, i64, stack), - F32Sub => arithmetic_op!(-, f32, stack), - F64Sub => arithmetic_op!(-, f64, stack), + I32Sub => arithmetic!(wrapping_sub, i32, stack), + I64Sub => arithmetic!(wrapping_sub, i64, stack), + F32Sub => arithmetic!(-, f32, stack), + F64Sub => arithmetic!(-, f64, stack), - F32Div => arithmetic_op!(/, f32, stack), - F64Div => arithmetic_op!(/, f64, stack), + F32Div => arithmetic!(/, f32, stack), + F64Div => arithmetic!(/, f64, stack), - I32Mul => arithmetic_method!(wrapping_mul, i32, stack), - I64Mul => arithmetic_method!(wrapping_mul, i64, stack), - F32Mul => arithmetic_op!(*, f32, stack), - F64Mul => arithmetic_op!(*, f64, stack), + I32Mul => arithmetic!(wrapping_mul, i32, stack), + I64Mul => arithmetic!(wrapping_mul, i64, stack), + F32Mul => arithmetic!(*, f32, stack), + F64Mul => arithmetic!(*, f64, stack), // these can trap - I32DivS => checked_arithmetic_method!(checked_div, i32, stack, crate::Trap::DivisionByZero), - I64DivS => checked_arithmetic_method!(checked_div, i64, stack, crate::Trap::DivisionByZero), - I32DivU => checked_arithmetic_method_cast!(checked_div, i32, u32, stack, crate::Trap::DivisionByZero), - I64DivU => checked_arithmetic_method_cast!(checked_div, i64, u64, stack, crate::Trap::DivisionByZero), - - I32RemS => checked_arithmetic_method!(checked_wrapping_rem, i32, stack, crate::Trap::DivisionByZero), - I64RemS => checked_arithmetic_method!(checked_wrapping_rem, i64, stack, crate::Trap::DivisionByZero), - I32RemU => checked_arithmetic_method_cast!(checked_wrapping_rem, i32, u32, stack, crate::Trap::DivisionByZero), - I64RemU => checked_arithmetic_method_cast!(checked_wrapping_rem, i64, u64, stack, crate::Trap::DivisionByZero), - - I32And => arithmetic_method!(bitand, i32, stack), - I64And => arithmetic_method!(bitand, i64, stack), - I32Or => arithmetic_method!(bitor, i32, stack), - I64Or => arithmetic_method!(bitor, i64, stack), - I32Xor => arithmetic_method!(bitxor, i32, stack), - I64Xor => arithmetic_method!(bitxor, i64, stack), - I32Shl => arithmetic_method!(wasm_shl, i32, stack), - I64Shl => arithmetic_method!(wasm_shl, i64, stack), - I32ShrS => arithmetic_method!(wasm_shr, i32, stack), - I64ShrS => arithmetic_method!(wasm_shr, i64, stack), - I32ShrU => arithmetic_method_cast!(wasm_shr, i32, u32, stack), - I64ShrU => arithmetic_method_cast!(wasm_shr, i64, u64, stack), - I32Rotl => arithmetic_method!(wasm_rotl, i32, stack), - I64Rotl => arithmetic_method!(wasm_rotl, i64, stack), - I32Rotr => arithmetic_method!(wasm_rotr, i32, stack), - I64Rotr => arithmetic_method!(wasm_rotr, i64, stack), - - I32Clz => arithmetic_method_self!(leading_zeros, i32, stack), - I64Clz => arithmetic_method_self!(leading_zeros, i64, stack), - I32Ctz => arithmetic_method_self!(trailing_zeros, i32, stack), - I64Ctz => arithmetic_method_self!(trailing_zeros, i64, stack), - I32Popcnt => arithmetic_method_self!(count_ones, i32, stack), - I64Popcnt => arithmetic_method_self!(count_ones, i64, stack), - - F32ConvertI32S => conv_1!(i32, f32, stack), - F32ConvertI64S => conv_1!(i64, f32, stack), - F64ConvertI32S => conv_1!(i32, f64, stack), - F64ConvertI64S => conv_1!(i64, f64, stack), - F32ConvertI32U => conv_2!(i32, u32, f32, stack), - F32ConvertI64U => conv_2!(i64, u64, f32, stack), - F64ConvertI32U => conv_2!(i32, u32, f64, stack), - F64ConvertI64U => conv_2!(i64, u64, f64, stack), - I32Extend8S => conv_2!(i32, i8, i32, stack), - I32Extend16S => conv_2!(i32, i16, i32, stack), - I64Extend8S => conv_2!(i64, i8, i64, stack), - I64Extend16S => conv_2!(i64, i16, i64, stack), - I64Extend32S => conv_2!(i64, i32, i64, stack), - I64ExtendI32U => conv_2!(i32, u32, i64, stack), - I64ExtendI32S => conv_1!(i32, i64, stack), - I32WrapI64 => conv_1!(i64, i32, stack), - - F32Abs => arithmetic_method_self!(abs, f32, stack), - F64Abs => arithmetic_method_self!(abs, f64, stack), - F32Neg => arithmetic_method_self!(neg, f32, stack), - F64Neg => arithmetic_method_self!(neg, f64, stack), - F32Ceil => arithmetic_method_self!(ceil, f32, stack), - F64Ceil => arithmetic_method_self!(ceil, f64, stack), - F32Floor => arithmetic_method_self!(floor, f32, stack), - F64Floor => arithmetic_method_self!(floor, f64, stack), - F32Trunc => arithmetic_method_self!(trunc, f32, stack), - F64Trunc => arithmetic_method_self!(trunc, f64, stack), - F32Nearest => arithmetic_method_self!(wasm_nearest, f32, stack), - F64Nearest => arithmetic_method_self!(wasm_nearest, f64, stack), - F32Sqrt => arithmetic_method_self!(sqrt, f32, stack), - F64Sqrt => arithmetic_method_self!(sqrt, f64, stack), - F32Min => arithmetic_method!(wasm_min, f32, stack), - F64Min => arithmetic_method!(wasm_min, f64, stack), - F32Max => arithmetic_method!(wasm_max, f32, stack), - F64Max => arithmetic_method!(wasm_max, f64, stack), - F32Copysign => arithmetic_method!(copysign, f32, stack), - F64Copysign => arithmetic_method!(copysign, f64, stack), + I32DivS => checked_arithmetic!(checked_div, i32, stack, crate::Trap::DivisionByZero), + I64DivS => checked_arithmetic!(checked_div, i64, stack, crate::Trap::DivisionByZero), + I32DivU => checked_arithmetic!(checked_div, i32, u32, stack, crate::Trap::DivisionByZero), + I64DivU => checked_arithmetic!(checked_div, i64, u64, stack, crate::Trap::DivisionByZero), + + I32RemS => checked_arithmetic!(checked_wrapping_rem, i32, stack, crate::Trap::DivisionByZero), + I64RemS => checked_arithmetic!(checked_wrapping_rem, i64, stack, crate::Trap::DivisionByZero), + I32RemU => checked_arithmetic!(checked_wrapping_rem, i32, u32, stack, crate::Trap::DivisionByZero), + I64RemU => checked_arithmetic!(checked_wrapping_rem, i64, u64, stack, crate::Trap::DivisionByZero), + + I32And => arithmetic!(bitand, i32, stack), + I64And => arithmetic!(bitand, i64, stack), + I32Or => arithmetic!(bitor, i32, stack), + I64Or => arithmetic!(bitor, i64, stack), + I32Xor => arithmetic!(bitxor, i32, stack), + I64Xor => arithmetic!(bitxor, i64, stack), + I32Shl => arithmetic!(wasm_shl, i32, stack), + I64Shl => arithmetic!(wasm_shl, i64, stack), + I32ShrS => arithmetic!(wasm_shr, i32, stack), + I64ShrS => arithmetic!(wasm_shr, i64, stack), + I32ShrU => arithmetic!(wasm_shr, u32, i32, stack), + I64ShrU => arithmetic!(wasm_shr, u64, i64, stack), + I32Rotl => arithmetic!(wasm_rotl, i32, stack), + I64Rotl => arithmetic!(wasm_rotl, i64, stack), + I32Rotr => arithmetic!(wasm_rotr, i32, stack), + I64Rotr => arithmetic!(wasm_rotr, i64, stack), + + I32Clz => arithmetic_single!(leading_zeros, i32, stack), + I64Clz => arithmetic_single!(leading_zeros, i64, stack), + I32Ctz => arithmetic_single!(trailing_zeros, i32, stack), + I64Ctz => arithmetic_single!(trailing_zeros, i64, stack), + I32Popcnt => arithmetic_single!(count_ones, i32, stack), + I64Popcnt => arithmetic_single!(count_ones, i64, stack), + + F32ConvertI32S => conv!(i32, f32, stack), + F32ConvertI64S => conv!(i64, f32, stack), + F64ConvertI32S => conv!(i32, f64, stack), + F64ConvertI64S => conv!(i64, f64, stack), + F32ConvertI32U => conv!(i32, u32, f32, stack), + F32ConvertI64U => conv!(i64, u64, f32, stack), + F64ConvertI32U => conv!(i32, u32, f64, stack), + F64ConvertI64U => conv!(i64, u64, f64, stack), + I32Extend8S => conv!(i32, i8, i32, stack), + I32Extend16S => conv!(i32, i16, i32, stack), + I64Extend8S => conv!(i64, i8, i64, stack), + I64Extend16S => conv!(i64, i16, i64, stack), + I64Extend32S => conv!(i64, i32, i64, stack), + I64ExtendI32U => conv!(i32, u32, i64, stack), + I64ExtendI32S => conv!(i32, i64, stack), + I32WrapI64 => conv!(i64, i32, stack), + + F32DemoteF64 => conv!(f64, f32, stack), + F64PromoteF32 => conv!(f32, f64, stack), + + F32Abs => arithmetic_single!(abs, f32, stack), + F64Abs => arithmetic_single!(abs, f64, stack), + F32Neg => arithmetic_single!(neg, f32, stack), + F64Neg => arithmetic_single!(neg, f64, stack), + F32Ceil => arithmetic_single!(ceil, f32, stack), + F64Ceil => arithmetic_single!(ceil, f64, stack), + F32Floor => arithmetic_single!(floor, f32, stack), + F64Floor => arithmetic_single!(floor, f64, stack), + F32Trunc => arithmetic_single!(trunc, f32, stack), + F64Trunc => arithmetic_single!(trunc, f64, stack), + F32Nearest => arithmetic_single!(wasm_nearest, f32, stack), + F64Nearest => arithmetic_single!(wasm_nearest, f64, stack), + F32Sqrt => arithmetic_single!(sqrt, f32, stack), + F64Sqrt => arithmetic_single!(sqrt, f64, stack), + F32Min => arithmetic!(wasm_min, f32, stack), + F64Min => arithmetic!(wasm_min, f64, stack), + F32Max => arithmetic!(wasm_max, f32, stack), + F64Max => arithmetic!(wasm_max, f64, stack), + F32Copysign => arithmetic!(copysign, f32, stack), + F64Copysign => arithmetic!(copysign, f64, stack), // no-op instructions since types are erased at runtime I32ReinterpretF32 => {} @@ -394,14 +488,14 @@ fn exec_one( F64ReinterpretI64 => {} // unsigned versions of these are a bit broken atm - I32TruncF32S => checked_float_conv_1!(f32, i32, stack), - I32TruncF64S => checked_float_conv_1!(f64, i32, stack), - I32TruncF32U => checked_float_conv_2!(f32, u32, i32, stack), - I32TruncF64U => checked_float_conv_2!(f64, u32, i32, stack), - I64TruncF32S => checked_float_conv_1!(f32, i64, stack), - I64TruncF64S => checked_float_conv_1!(f64, i64, stack), - I64TruncF32U => checked_float_conv_2!(f32, u64, i64, stack), - I64TruncF64U => checked_float_conv_2!(f64, u64, i64, stack), + I32TruncF32S => checked_conv_float!(f32, i32, stack), + I32TruncF64S => checked_conv_float!(f64, i32, stack), + I32TruncF32U => checked_conv_float!(f32, u32, i32, stack), + I32TruncF64U => checked_conv_float!(f64, u32, i32, stack), + I64TruncF32S => checked_conv_float!(f32, i64, stack), + I64TruncF64S => checked_conv_float!(f64, i64, stack), + I64TruncF32U => checked_conv_float!(f32, u64, i64, stack), + I64TruncF64U => checked_conv_float!(f64, u64, i64, stack), i => { log::error!("unimplemented instruction: {:?}", i); diff --git a/crates/tinywasm/src/runtime/stack.rs b/crates/tinywasm/src/runtime/stack.rs index 0912640..07d9316 100644 --- a/crates/tinywasm/src/runtime/stack.rs +++ b/crates/tinywasm/src/runtime/stack.rs @@ -3,16 +3,12 @@ mod call_stack; mod value_stack; use self::{call_stack::CallStack, value_stack::ValueStack}; -pub(crate) use blocks::{get_label_args, BlockType, LabelFrame}; +pub(crate) use blocks::{BlockType, LabelArgs, LabelFrame}; pub(crate) use call_stack::CallFrame; /// A WebAssembly Stack #[derive(Debug, Default)] pub struct Stack { - // keeping this typed for now to make it easier to debug - // TODO: Maybe split into Vec and Vec for better memory usage? pub(crate) values: ValueStack, - - /// The call stack pub(crate) call_stack: CallStack, } diff --git a/crates/tinywasm/src/runtime/stack/blocks.rs b/crates/tinywasm/src/runtime/stack/blocks.rs index 05c9043..f5a0d8d 100644 --- a/crates/tinywasm/src/runtime/stack/blocks.rs +++ b/crates/tinywasm/src/runtime/stack/blocks.rs @@ -1,10 +1,9 @@ use alloc::vec::Vec; -use log::info; use tinywasm_types::BlockArgs; use crate::{ModuleInstance, Result}; -#[derive(Debug, Default, Clone)] +#[derive(Debug, Clone, Default)] pub(crate) struct Labels(Vec); impl Labels { @@ -13,20 +12,18 @@ impl Labels { } #[inline] - pub(crate) fn push(&mut self, block: LabelFrame) { - self.0.push(block); + pub(crate) fn push(&mut self, label: LabelFrame) { + self.0.push(label); } #[inline] - pub(crate) fn top(&self) -> Option<&LabelFrame> { - self.0.last() - } - - #[inline] - /// get the block at the given index, where 0 is the top of the stack + /// get the label at the given index, where 0 is the top of the stack pub(crate) fn get_relative_to_top(&self, index: usize) -> Option<&LabelFrame> { - info!("get block: {}", index); - info!("blocks: {:?}", self.0); + let len = self.0.len(); + if index >= len { + return None; + } + self.0.get(self.0.len() - index - 1) } @@ -64,19 +61,21 @@ pub(crate) enum BlockType { Block, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Default)] pub(crate) struct LabelArgs { pub(crate) params: usize, pub(crate) results: usize, } -pub(crate) fn get_label_args(args: BlockArgs, module: &ModuleInstance) -> Result { - Ok(match args { - BlockArgs::Empty => LabelArgs { params: 0, results: 0 }, - BlockArgs::Type(_) => LabelArgs { params: 0, results: 1 }, - BlockArgs::FuncType(t) => LabelArgs { - params: module.func_ty(t).params.len(), - results: module.func_ty(t).results.len(), - }, - }) +impl LabelArgs { + pub(crate) fn new(args: BlockArgs, module: &ModuleInstance) -> Result { + Ok(match args { + BlockArgs::Empty => LabelArgs { params: 0, results: 0 }, + BlockArgs::Type(_) => LabelArgs { params: 0, results: 1 }, + BlockArgs::FuncType(t) => LabelArgs { + params: module.func_ty(t).params.len(), + results: module.func_ty(t).results.len(), + }, + }) + } } diff --git a/crates/tinywasm/src/runtime/stack/call_stack.rs b/crates/tinywasm/src/runtime/stack/call_stack.rs index bf45753..239659c 100644 --- a/crates/tinywasm/src/runtime/stack/call_stack.rs +++ b/crates/tinywasm/src/runtime/stack/call_stack.rs @@ -89,16 +89,11 @@ impl CallFrame { } /// Break to a block at the given index (relative to the current frame) + /// Returns `None` if there is no block at the given index (e.g. if we need to return, this is handled by the caller) #[inline] - pub(crate) fn break_to(&mut self, break_to_relative: u32, value_stack: &mut super::ValueStack) -> Result<()> { - let current_label = self.labels.top().ok_or(Error::LabelStackUnderflow)?; - let break_to = self - .labels - .get_relative_to_top(break_to_relative as usize) - .ok_or(Error::LabelStackUnderflow)?; - - // trim the lable's stack from the stack - value_stack.truncate_keep(break_to.stack_ptr, break_to.args.results); + pub(crate) fn break_to(&mut self, break_to_relative: u32, value_stack: &mut super::ValueStack) -> Option<()> { + let break_to = self.labels.get_relative_to_top(break_to_relative as usize)?; + value_stack.break_to(break_to.stack_ptr, break_to.args.results); // instr_ptr points to the label instruction, but the next step // will increment it by 1 since we're changing the "current" instr_ptr @@ -110,7 +105,7 @@ impl CallFrame { // we also want to trim the label stack to the loop (but not including the loop) self.labels.truncate(self.labels.len() - break_to_relative as usize); } - BlockType::Block => { + BlockType::Block | BlockType::If | BlockType::Else => { // this is a block, so we want to jump to the next instruction after the block ends (the inst_ptr will be incremented by 1 before the next instruction is executed) self.instr_ptr = break_to.end_instr_ptr; @@ -118,7 +113,6 @@ impl CallFrame { self.labels .truncate(self.labels.len() - (break_to_relative as usize + 1)); } - _ => unimplemented!("break to block type: {:?}", current_label.ty), } // self.instr_ptr = block_frame.instr_ptr; @@ -134,7 +128,7 @@ impl CallFrame { // // }; // self.block_frames.trim(block_index as usize); - Ok(()) + Some(()) } pub(crate) fn new_raw(func_ptr: usize, params: &[RawWasmValue], local_types: Vec) -> Self { diff --git a/crates/tinywasm/src/runtime/stack/value_stack.rs b/crates/tinywasm/src/runtime/stack/value_stack.rs index 694eb29..10054bc 100644 --- a/crates/tinywasm/src/runtime/stack/value_stack.rs +++ b/crates/tinywasm/src/runtime/stack/value_stack.rs @@ -2,7 +2,6 @@ use core::ops::Range; use crate::{runtime::RawWasmValue, Error, Result}; use alloc::vec::Vec; -use log::info; // minimum stack size pub(crate) const STACK_SIZE: usize = 1024; @@ -25,11 +24,6 @@ impl Default for ValueStack { } impl ValueStack { - #[cfg(test)] - pub(crate) fn data(&self) -> &[RawWasmValue] { - &self.stack - } - #[inline] pub(crate) fn extend_from_within(&mut self, range: Range) { self.top += range.len(); @@ -42,31 +36,24 @@ impl ValueStack { self.top } - #[inline] - pub(crate) fn _truncate(&mut self, n: usize) { - assert!(self.top <= self.stack.len()); - self.top -= n; - self.stack.truncate(self.top); - } - - #[inline] - // example: [1, 2, 3] n=1, end_keep=1 => [1, 3] - // example: [1] n=1, end_keep=1 => [1] pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { - if n == end_keep || n == 0 { - return; + let total_to_keep = n + end_keep; + assert!( + self.top >= total_to_keep, + "Total to keep should be less than or equal to self.top" + ); + + let current_size = self.stack.len(); + if current_size <= total_to_keep { + return; // No need to truncate if the current size is already less than or equal to total_to_keep } - assert!(self.top <= self.stack.len()); - info!("removing from {} to {}", self.top - n, self.top - end_keep); - self.stack.drain(self.top - n..self.top - end_keep); - self.top -= n - end_keep; - } + let items_to_remove = current_size - total_to_keep; + let remove_start_index = self.top - items_to_remove - end_keep; + let remove_end_index = self.top - end_keep; - #[inline] - pub(crate) fn _extend(&mut self, values: impl IntoIterator + ExactSizeIterator) { - self.top += values.len(); - self.stack.extend(values); + self.stack.drain(remove_start_index..remove_end_index); + self.top = total_to_keep; // Update top to reflect the new size } #[inline] @@ -92,6 +79,21 @@ impl ValueStack { self.stack.pop().ok_or(Error::StackUnderflow) } + pub(crate) fn break_to(&mut self, new_stack_size: usize, result_count: usize) { + self.stack + .copy_within((self.top - result_count)..self.top, new_stack_size); + self.top = new_stack_size + result_count; + self.stack.truncate(self.top); + } + + #[inline] + pub(crate) fn last_n(&self, n: usize) -> Result<&[RawWasmValue]> { + if self.top < n { + return Err(Error::StackUnderflow); + } + Ok(&self.stack[self.top - n..self.top]) + } + #[inline] pub(crate) fn pop_n(&mut self, n: usize) -> Result> { if self.top < n { @@ -120,39 +122,6 @@ impl ValueStack { #[cfg(test)] mod tests { use super::*; - use crate::std::panic; - - fn crate_stack + Copy>(data: &[T]) -> ValueStack { - let mut stack = ValueStack::default(); - stack._extend(data.iter().map(|v| (*v).into())); - stack - } - - fn assert_truncate_keep + Copy>(data: &[T], n: usize, end_keep: usize, expected: &[T]) { - let mut stack = crate_stack(data); - stack.truncate_keep(n, end_keep); - assert_eq!( - stack.data(), - expected.iter().map(|v| (*v).into()).collect::>().as_slice() - ); - } - - fn catch_unwind_silent R + panic::UnwindSafe, R>(f: F) -> crate::std::thread::Result { - let prev_hook = panic::take_hook(); - panic::set_hook(alloc::boxed::Box::new(|_| {})); - let result = panic::catch_unwind(f); - panic::set_hook(prev_hook); - result - } - - #[test] - fn test_truncate_keep() { - assert_truncate_keep(&[1, 2, 3], 1, 1, &[1, 2, 3]); - assert_truncate_keep(&[1], 1, 1, &[1]); - assert_truncate_keep(&[1, 2, 3], 2, 1, &[1, 3]); - assert_truncate_keep::(&[], 0, 0, &[]); - catch_unwind_silent(|| assert_truncate_keep(&[1, 2, 3], 4, 1, &[1, 3])).expect_err("should panic"); - } #[test] fn test_value_stack() { diff --git a/crates/tinywasm/src/runtime/value.rs b/crates/tinywasm/src/runtime/value.rs index 16fb42c..b25f634 100644 --- a/crates/tinywasm/src/runtime/value.rs +++ b/crates/tinywasm/src/runtime/value.rs @@ -17,15 +17,19 @@ impl Debug for RawWasmValue { } impl RawWasmValue { + pub fn raw_value(&self) -> u64 { + self.0 + } + pub fn attach_type(self, ty: ValType) -> WasmValue { match ty { ValType::I32 => WasmValue::I32(self.0 as i32), ValType::I64 => WasmValue::I64(self.0 as i64), ValType::F32 => WasmValue::F32(f32::from_bits(self.0 as u32)), ValType::F64 => WasmValue::F64(f64::from_bits(self.0)), - ValType::ExternRef => todo!(), - ValType::FuncRef => todo!(), - ValType::V128 => todo!(), + ValType::ExternRef => todo!("externref"), + ValType::FuncRef => todo!("funcref"), + ValType::V128 => todo!("v128"), } } } @@ -37,6 +41,7 @@ impl From for RawWasmValue { WasmValue::I64(i) => Self(i as u64), WasmValue::F32(i) => Self(i.to_bits() as u64), WasmValue::F64(i) => Self(i.to_bits()), + WasmValue::RefNull(_) => Self(0), } } } @@ -65,3 +70,6 @@ impl_from_raw_wasm_value!(i32, |x| x as u64, |x| x as i32); impl_from_raw_wasm_value!(i64, |x| x as u64, |x| x as i64); impl_from_raw_wasm_value!(f32, |x| f32::to_bits(x) as u64, |x| f32::from_bits(x as u32)); impl_from_raw_wasm_value!(f64, f64::to_bits, f64::from_bits); + +impl_from_raw_wasm_value!(i8, |x| x as u64, |x| x as i8); +impl_from_raw_wasm_value!(i16, |x| x as u64, |x| x as i16); diff --git a/crates/tinywasm/src/store.rs b/crates/tinywasm/src/store.rs index ff44d0d..564232d 100644 --- a/crates/tinywasm/src/store.rs +++ b/crates/tinywasm/src/store.rs @@ -1,11 +1,19 @@ -use core::sync::atomic::{AtomicUsize, Ordering}; +#![allow(dead_code)] // TODO: remove this -use alloc::{format, rc::Rc, vec::Vec}; -use tinywasm_types::{FuncAddr, Function, Instruction, ModuleInstanceAddr, TypeAddr, ValType}; +use core::{ + cell::RefCell, + sync::atomic::{AtomicUsize, Ordering}, +}; + +use alloc::{format, rc::Rc, string::ToString, vec, vec::Vec}; +use tinywasm_types::{ + Addr, Data, Element, ElementKind, FuncAddr, Function, Global, GlobalType, Import, Instruction, MemAddr, MemoryArch, + MemoryType, ModuleInstanceAddr, TableAddr, TableType, TypeAddr, ValType, +}; use crate::{ runtime::{self, DefaultRuntime}, - Error, ModuleInstance, Result, + Error, Extern, LinkedImports, ModuleInstance, RawWasmValue, Result, }; // global store id counter @@ -69,18 +77,254 @@ impl Default for Store { } } +#[derive(Debug, Default)] +/// Global state that can be manipulated by WebAssembly programs +/// +/// Data should only be addressable by the module that owns it +/// See +// TODO: Arena allocate these? +pub(crate) struct StoreData { + pub(crate) funcs: Vec>, + pub(crate) tables: Vec, + pub(crate) mems: Vec>>, + pub(crate) globals: Vec>>, + pub(crate) elems: Vec, + pub(crate) datas: Vec, +} + +impl Store { + /// Get the store's ID (unique per process) + pub fn id(&self) -> usize { + self.id + } + + pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { + self.module_instance_count as ModuleInstanceAddr + } + + /// Initialize the store with global state from the given module + pub(crate) fn add_instance(&mut self, instance: ModuleInstance) -> Result<()> { + self.module_instances.push(instance); + self.module_instance_count += 1; + Ok(()) + } + + /// Add functions to the store, returning their addresses in the store + pub(crate) fn add_funcs(&mut self, funcs: Vec, idx: ModuleInstanceAddr) -> Vec { + let func_count = self.data.funcs.len(); + let mut func_addrs = Vec::with_capacity(func_count); + for (i, func) in funcs.into_iter().enumerate() { + self.data.funcs.push(Rc::new(FunctionInstance { func, owner: idx })); + func_addrs.push((i + func_count) as FuncAddr); + } + func_addrs + } + + /// Add tables to the store, returning their addresses in the store + pub(crate) fn add_tables(&mut self, tables: Vec, idx: ModuleInstanceAddr) -> Vec { + let table_count = self.data.tables.len(); + let mut table_addrs = Vec::with_capacity(table_count); + for (i, table) in tables.into_iter().enumerate() { + self.data.tables.push(TableInstance::new(table, idx)); + table_addrs.push((i + table_count) as TableAddr); + } + table_addrs + } + + /// Add memories to the store, returning their addresses in the store + pub(crate) fn add_mems(&mut self, mems: Vec, idx: ModuleInstanceAddr) -> Result> { + let mem_count = self.data.mems.len(); + let mut mem_addrs = Vec::with_capacity(mem_count); + for (i, mem) in mems.into_iter().enumerate() { + if let MemoryArch::I64 = mem.arch { + return Err(Error::UnsupportedFeature("64-bit memories".to_string())); + } + self.data + .mems + .push(Rc::new(RefCell::new(MemoryInstance::new(mem, idx)))); + + mem_addrs.push((i + mem_count) as MemAddr); + } + Ok(mem_addrs) + } + + /// Add globals to the store, returning their addresses in the store + pub(crate) fn add_globals( + &mut self, + globals: Vec, + wasm_imports: &[Import], + user_imports: &LinkedImports, + idx: ModuleInstanceAddr, + ) -> Result> { + // TODO: initialize imported globals + #![allow(clippy::unnecessary_filter_map)] // this is cleaner + let imported_globals = wasm_imports + .iter() + .filter_map(|import| match &import.kind { + tinywasm_types::ImportKind::Global(_) => Some(import), + _ => None, + }) + .map(|import| { + let Some(global) = user_imports.get(&import.module, &import.name) else { + return Err(Error::Other(format!( + "global import not found for {}::{}", + import.module, import.name + ))); + }; + match global { + Extern::Global(global) => Ok(global), + + #[allow(unreachable_patterns)] // this is non-exhaustive + _ => Err(Error::Other(format!( + "expected global import for {}::{}", + import.module, import.name + ))), + } + }) + .collect::>>()?; + + let global_count = self.data.globals.len(); + let mut global_addrs = Vec::with_capacity(global_count); + + log::debug!("globals: {:?}", globals); + let globals = globals.into_iter(); + let iterator = imported_globals.into_iter().chain(globals.as_ref()); + + for (i, global) in iterator.enumerate() { + self.data.globals.push(Rc::new(RefCell::new(GlobalInstance::new( + global.ty, + self.eval_const(&global.init)?, + idx, + )))); + global_addrs.push((i + global_count) as Addr); + } + + Ok(global_addrs) + } + + pub(crate) fn eval_const(&self, const_instr: &tinywasm_types::ConstInstruction) -> Result { + use tinywasm_types::ConstInstruction::*; + let val = match const_instr { + F32Const(f) => RawWasmValue::from(*f), + F64Const(f) => RawWasmValue::from(*f), + I32Const(i) => RawWasmValue::from(*i), + I64Const(i) => RawWasmValue::from(*i), + GlobalGet(addr) => { + let addr = *addr as usize; + let global = self.data.globals[addr].clone(); + let val = global.borrow().value; + val + } + RefNull(_) => RawWasmValue::default(), + RefFunc(idx) => RawWasmValue::from(*idx as i64), + }; + Ok(val) + } + + /// Add elements to the store, returning their addresses in the store + /// Should be called after the tables have been added + pub(crate) fn add_elems(&mut self, elems: Vec, idx: ModuleInstanceAddr) -> Result> { + let elem_count = self.data.elems.len(); + let mut elem_addrs = Vec::with_capacity(elem_count); + for (i, elem) in elems.into_iter().enumerate() { + match elem.kind { + // doesn't need to be initialized, can be initialized lazily using the `table.init` instruction + ElementKind::Passive => {} + + // this one is active, so we need to initialize it (essentially a `table.init` instruction) + ElementKind::Active { .. } => { + // a. Let n be the length of the vector elem[i].init + // b. Execute the instruction sequence einstrs + // c. Execute the instruction i32.const 0 + // d. Execute the instruction i32.const n + // e. Execute the instruction table.init tableidx i + // f. Execute the instruction elm.drop i + } + + // this one is not available to the runtime but needs to be initialized to declare references + ElementKind::Declared => { + // a. Execute the instruction elm.drop i + } + } + + self.data.elems.push(ElemInstance::new(elem.kind, idx)); + elem_addrs.push((i + elem_count) as Addr); + } + + Ok(elem_addrs) + } + + /// Add data to the store, returning their addresses in the store + pub(crate) fn add_datas(&mut self, datas: Vec, idx: ModuleInstanceAddr) -> Vec { + let data_count = self.data.datas.len(); + let mut data_addrs = Vec::with_capacity(data_count); + for (i, data) in datas.into_iter().enumerate() { + self.data.datas.push(DataInstance::new(data.data.to_vec(), idx)); + data_addrs.push((i + data_count) as Addr); + + use tinywasm_types::DataKind::*; + match data.kind { + Active { .. } => { + // a. Assert: memidx == 0 + // b. Let n be the length of the vector + // c. Execute the instruction sequence + // d. Execute the instruction + // e. Execute the instruction + // f. Execute the instruction + // g. Execute the instruction + } + Passive => {} + } + } + data_addrs + } + + /// Get the function at the actual index in the store + pub(crate) fn get_func(&self, addr: usize) -> Result<&Rc> { + self.data + .funcs + .get(addr) + .ok_or_else(|| Error::Other(format!("function {} not found", addr))) + } + + /// Get the memory at the actual index in the store + pub(crate) fn get_mem(&self, addr: usize) -> Result<&Rc>> { + self.data + .mems + .get(addr) + .ok_or_else(|| Error::Other(format!("memory {} not found", addr))) + } + + /// Get the global at the actual index in the store + pub(crate) fn get_global_val(&self, addr: usize) -> Result { + self.data + .globals + .get(addr) + .ok_or_else(|| Error::Other(format!("global {} not found", addr))) + .map(|global| global.borrow().value) + } + + pub(crate) fn set_global_val(&mut self, addr: usize, value: RawWasmValue) -> Result<()> { + self.data + .globals + .get(addr) + .ok_or_else(|| Error::Other(format!("global {} not found", addr))) + .map(|global| global.borrow_mut().value = value) + } +} + #[derive(Debug)] /// A WebAssembly Function Instance /// /// See pub struct FunctionInstance { pub(crate) func: Function, - pub(crate) _module_instance: ModuleInstanceAddr, // index into store.module_instances + pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances } impl FunctionInstance { pub(crate) fn _module_instance_addr(&self) -> ModuleInstanceAddr { - self._module_instance + self.owner } pub(crate) fn locals(&self) -> &[ValType] { @@ -96,50 +340,166 @@ impl FunctionInstance { } } -#[derive(Debug, Default)] -/// Global state that can be manipulated by WebAssembly programs -pub(crate) struct StoreData { - pub(crate) funcs: Vec>, - // pub tables: Vec, - // pub mems: Vec, - // pub globals: Vec, - // pub elems: Vec, - // pub datas: Vec, +/// A WebAssembly Table Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct TableInstance { + pub(crate) kind: TableType, + pub(crate) elements: Vec, + pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances } -impl Store { - /// Get the store's ID (unique per process) - pub fn id(&self) -> usize { - self.id +impl TableInstance { + pub(crate) fn new(kind: TableType, owner: ModuleInstanceAddr) -> Self { + Self { + elements: vec![0; kind.size_initial as usize], + kind, + owner, + } } +} - pub(crate) fn next_module_instance_idx(&self) -> ModuleInstanceAddr { - self.module_instance_count as ModuleInstanceAddr +pub(crate) const PAGE_SIZE: usize = 65536; +pub(crate) const MAX_PAGES: usize = 65536; +pub(crate) const MAX_SIZE: usize = PAGE_SIZE * MAX_PAGES; + +/// A WebAssembly Memory Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct MemoryInstance { + pub(crate) kind: MemoryType, + pub(crate) data: Vec, + pub(crate) page_count: usize, + pub(crate) owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl MemoryInstance { + pub(crate) fn new(kind: MemoryType, owner: ModuleInstanceAddr) -> Self { + debug_assert!(kind.page_count_initial <= kind.page_count_max.unwrap_or(MAX_PAGES as u64)); + log::debug!("initializing memory with {} pages", kind.page_count_initial); + + Self { + kind, + data: vec![0; PAGE_SIZE * kind.page_count_initial as usize], + page_count: kind.page_count_initial as usize, + owner, + } } - /// Initialize the store with global state from the given module - pub(crate) fn add_instance(&mut self, instance: ModuleInstance) -> Result<()> { - self.module_instances.push(instance); - self.module_instance_count += 1; + pub(crate) fn store(&mut self, addr: usize, _align: usize, data: &[u8]) -> Result<()> { + let end = addr.checked_add(data.len()).ok_or_else(|| { + Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: addr, + len: data.len(), + max: self.data.len(), + }) + })?; + + if end > self.data.len() || end < addr { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: addr, + len: data.len(), + max: self.data.len(), + })); + } + + // WebAssembly doesn't require alignment for stores + self.data[addr..end].copy_from_slice(data); Ok(()) } - pub(crate) fn add_funcs(&mut self, funcs: Vec, idx: ModuleInstanceAddr) -> Vec { - let mut func_addrs = Vec::with_capacity(funcs.len()); - for (i, func) in funcs.into_iter().enumerate() { - self.data.funcs.push(Rc::new(FunctionInstance { - func, - _module_instance: idx, + pub(crate) fn load(&self, addr: usize, _align: usize, len: usize) -> Result<&[u8]> { + let end = addr.checked_add(len).ok_or_else(|| { + Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: addr, + len, + max: self.data.len(), + }) + })?; + + if end > self.data.len() { + return Err(Error::Trap(crate::Trap::MemoryOutOfBounds { + offset: addr, + len, + max: self.data.len(), })); - func_addrs.push(i as FuncAddr); } - func_addrs + + // WebAssembly doesn't require alignment for loads + Ok(&self.data[addr..end]) } - pub(crate) fn get_func(&self, addr: usize) -> Result<&Rc> { - self.data - .funcs - .get(addr) - .ok_or_else(|| Error::Other(format!("function {} not found", addr))) + pub(crate) fn size(&self) -> i32 { + log::debug!("memory pages: {}", self.page_count); + log::debug!("memory size: {}", self.page_count * PAGE_SIZE); + self.page_count as i32 + } + + pub(crate) fn grow(&mut self, delta: i32) -> Result { + let current_pages = self.size(); + let new_pages = current_pages + delta; + if new_pages < 0 || new_pages > MAX_PAGES as i32 { + return Err(Error::Other(format!("memory size out of bounds: {}", new_pages))); + } + let new_size = new_pages as usize * PAGE_SIZE; + if new_size > MAX_SIZE { + return Err(Error::Other(format!("memory size out of bounds: {}", new_size))); + } + self.data.resize(new_size, 0); + self.page_count = new_pages as usize; + + log::debug!("memory was {} pages", current_pages); + log::debug!("memory grown by {} pages", delta); + log::debug!("memory grown to {} pages", self.page_count); + + Ok(current_pages) + } +} + +/// A WebAssembly Global Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct GlobalInstance { + pub(crate) ty: GlobalType, + pub(crate) value: RawWasmValue, + owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl GlobalInstance { + pub(crate) fn new(ty: GlobalType, value: RawWasmValue, owner: ModuleInstanceAddr) -> Self { + Self { ty, value, owner } + } +} + +/// A WebAssembly Element Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct ElemInstance { + kind: ElementKind, + owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl ElemInstance { + pub(crate) fn new(kind: ElementKind, owner: ModuleInstanceAddr) -> Self { + Self { kind, owner } + } +} + +/// A WebAssembly Data Instance +/// +/// See +#[derive(Debug)] +pub(crate) struct DataInstance { + pub(crate) data: Vec, + owner: ModuleInstanceAddr, // index into store.module_instances +} + +impl DataInstance { + pub(crate) fn new(data: Vec, owner: ModuleInstanceAddr) -> Self { + Self { data, owner } } } diff --git a/crates/tinywasm/tests/generate-charts.rs b/crates/tinywasm/tests/generate-charts.rs index f80b092..e8e323d 100644 --- a/crates/tinywasm/tests/generate-charts.rs +++ b/crates/tinywasm/tests/generate-charts.rs @@ -6,11 +6,18 @@ fn main() -> Result<()> { } fn generate_charts() -> Result<()> { + let args = std::env::args().collect::>(); + if args.len() < 2 || args[1] != "--enable" { + return Ok(()); + } + // Create a line chart charts::create_progress_chart( std::path::Path::new("./tests/generated/mvp.csv"), std::path::Path::new("./tests/generated/progress-mvp.svg"), )?; + println!("created progress chart: ./tests/generated/progress-mvp.svg"); + Ok(()) } diff --git a/crates/tinywasm/tests/generated/mvp.csv b/crates/tinywasm/tests/generated/mvp.csv index c18acc5..a238586 100644 --- a/crates/tinywasm/tests/generated/mvp.csv +++ b/crates/tinywasm/tests/generated/mvp.csv @@ -1,4 +1,5 @@ 0.0.3,9258,7567,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":0,"failed":176}] 0.0.4,9258,10909,[{"name":"address.wast","passed":0,"failed":54},{"name":"align.wast","passed":0,"failed":109},{"name":"binary-leb128.wast","passed":66,"failed":25},{"name":"binary.wast","passed":104,"failed":8},{"name":"block.wast","passed":0,"failed":171},{"name":"br.wast","passed":0,"failed":21},{"name":"br_if.wast","passed":0,"failed":30},{"name":"br_table.wast","passed":0,"failed":25},{"name":"call.wast","passed":0,"failed":22},{"name":"call_indirect.wast","passed":0,"failed":56},{"name":"comments.wast","passed":4,"failed":4},{"name":"const.wast","passed":702,"failed":76},{"name":"conversions.wast","passed":0,"failed":93},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":0,"failed":61},{"name":"elem.wast","passed":0,"failed":76},{"name":"endianness.wast","passed":0,"failed":1},{"name":"exports.wast","passed":21,"failed":73},{"name":"f32.wast","passed":1005,"failed":1509},{"name":"f32_bitwise.wast","passed":1,"failed":363},{"name":"f32_cmp.wast","passed":2401,"failed":6},{"name":"f64.wast","passed":1005,"failed":1509},{"name":"f64_bitwise.wast","passed":1,"failed":363},{"name":"f64_cmp.wast","passed":2401,"failed":6},{"name":"fac.wast","passed":0,"failed":2},{"name":"float_exprs.wast","passed":269,"failed":591},{"name":"float_literals.wast","passed":34,"failed":129},{"name":"float_memory.wast","passed":0,"failed":6},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":4,"failed":75},{"name":"func_ptrs.wast","passed":0,"failed":16},{"name":"global.wast","passed":4,"failed":49},{"name":"i32.wast","passed":0,"failed":96},{"name":"i64.wast","passed":0,"failed":42},{"name":"if.wast","passed":0,"failed":118},{"name":"imports.wast","passed":1,"failed":156},{"name":"inline-module.wast","passed":0,"failed":1},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":5,"failed":46},{"name":"labels.wast","passed":1,"failed":28},{"name":"left-to-right.wast","passed":0,"failed":1},{"name":"linking.wast","passed":1,"failed":66},{"name":"load.wast","passed":0,"failed":60},{"name":"local_get.wast","passed":2,"failed":34},{"name":"local_set.wast","passed":5,"failed":48},{"name":"local_tee.wast","passed":0,"failed":42},{"name":"loop.wast","passed":0,"failed":43},{"name":"memory.wast","passed":0,"failed":34},{"name":"memory_grow.wast","passed":0,"failed":19},{"name":"memory_redundancy.wast","passed":0,"failed":1},{"name":"memory_size.wast","passed":0,"failed":6},{"name":"memory_trap.wast","passed":0,"failed":172},{"name":"names.wast","passed":484,"failed":1},{"name":"nop.wast","passed":0,"failed":5},{"name":"return.wast","passed":0,"failed":21},{"name":"select.wast","passed":0,"failed":32},{"name":"skip-stack-guard-page.wast","passed":0,"failed":11},{"name":"stack.wast","passed":0,"failed":2},{"name":"start.wast","passed":0,"failed":10},{"name":"store.wast","passed":0,"failed":59},{"name":"switch.wast","passed":1,"failed":27},{"name":"token.wast","passed":16,"failed":42},{"name":"traps.wast","passed":3,"failed":33},{"name":"type.wast","passed":1,"failed":2},{"name":"unreachable.wast","passed":0,"failed":59},{"name":"unreached-invalid.wast","passed":0,"failed":118},{"name":"unwind.wast","passed":1,"failed":49},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":0,"failed":176}] 0.0.5,11135,9093,[{"name":"address.wast","passed":1,"failed":259},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":78,"failed":13},{"name":"binary.wast","passed":107,"failed":5},{"name":"block.wast","passed":170,"failed":53},{"name":"br.wast","passed":20,"failed":77},{"name":"br_if.wast","passed":29,"failed":89},{"name":"br_table.wast","passed":24,"failed":150},{"name":"call.wast","passed":18,"failed":73},{"name":"call_indirect.wast","passed":34,"failed":136},{"name":"comments.wast","passed":5,"failed":3},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":25,"failed":594},{"name":"custom.wast","passed":10,"failed":1},{"name":"data.wast","passed":22,"failed":39},{"name":"elem.wast","passed":27,"failed":72},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":90,"failed":6},{"name":"f32.wast","passed":1018,"failed":1496},{"name":"f32_bitwise.wast","passed":4,"failed":360},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":1018,"failed":1496},{"name":"f64_bitwise.wast","passed":4,"failed":360},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":1,"failed":7},{"name":"float_exprs.wast","passed":275,"failed":625},{"name":"float_literals.wast","passed":112,"failed":51},{"name":"float_memory.wast","passed":0,"failed":90},{"name":"float_misc.wast","passed":138,"failed":303},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":81,"failed":91},{"name":"func_ptrs.wast","passed":7,"failed":29},{"name":"global.wast","passed":50,"failed":60},{"name":"i32.wast","passed":85,"failed":375},{"name":"i64.wast","passed":31,"failed":385},{"name":"if.wast","passed":116,"failed":125},{"name":"imports.wast","passed":23,"failed":160},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":38,"failed":70},{"name":"int_literals.wast","passed":25,"failed":26},{"name":"labels.wast","passed":13,"failed":16},{"name":"left-to-right.wast","passed":0,"failed":96},{"name":"linking.wast","passed":5,"failed":127},{"name":"load.wast","passed":59,"failed":38},{"name":"local_get.wast","passed":18,"failed":18},{"name":"local_set.wast","passed":38,"failed":15},{"name":"local_tee.wast","passed":41,"failed":56},{"name":"loop.wast","passed":42,"failed":78},{"name":"memory.wast","passed":30,"failed":49},{"name":"memory_grow.wast","passed":11,"failed":85},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":1,"failed":181},{"name":"names.wast","passed":484,"failed":2},{"name":"nop.wast","passed":4,"failed":84},{"name":"return.wast","passed":20,"failed":64},{"name":"select.wast","passed":28,"failed":120},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":4,"failed":16},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":39,"failed":19},{"name":"traps.wast","passed":4,"failed":32},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":0,"failed":64},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":9,"failed":41},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] -0.0.6-alpha.0,17630,2598,[{"name":"address.wast","passed":5,"failed":255},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":110,"failed":2},{"name":"block.wast","passed":193,"failed":30},{"name":"br.wast","passed":84,"failed":13},{"name":"br_if.wast","passed":90,"failed":28},{"name":"br_table.wast","passed":25,"failed":149},{"name":"call.wast","passed":29,"failed":62},{"name":"call_indirect.wast","passed":36,"failed":134},{"name":"comments.wast","passed":7,"failed":1},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":371,"failed":248},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":47,"failed":14},{"name":"elem.wast","passed":50,"failed":49},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":92,"failed":4},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":2,"failed":6},{"name":"float_exprs.wast","passed":761,"failed":139},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":6,"failed":84},{"name":"float_misc.wast","passed":437,"failed":4},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":124,"failed":48},{"name":"func_ptrs.wast","passed":10,"failed":26},{"name":"global.wast","passed":51,"failed":59},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":120,"failed":121},{"name":"imports.wast","passed":74,"failed":109},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":14,"failed":15},{"name":"left-to-right.wast","passed":1,"failed":95},{"name":"linking.wast","passed":21,"failed":111},{"name":"load.wast","passed":60,"failed":37},{"name":"local_get.wast","passed":32,"failed":4},{"name":"local_set.wast","passed":50,"failed":3},{"name":"local_tee.wast","passed":68,"failed":29},{"name":"loop.wast","passed":93,"failed":27},{"name":"memory.wast","passed":34,"failed":45},{"name":"memory_grow.wast","passed":12,"failed":84},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":2,"failed":180},{"name":"names.wast","passed":485,"failed":1},{"name":"nop.wast","passed":46,"failed":42},{"name":"return.wast","passed":73,"failed":11},{"name":"select.wast","passed":86,"failed":62},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":9,"failed":11},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":22,"failed":14},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":50,"failed":14},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":35,"failed":15},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.1.0,17630,2598,[{"name":"address.wast","passed":5,"failed":255},{"name":"align.wast","passed":108,"failed":48},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":110,"failed":2},{"name":"block.wast","passed":193,"failed":30},{"name":"br.wast","passed":84,"failed":13},{"name":"br_if.wast","passed":90,"failed":28},{"name":"br_table.wast","passed":25,"failed":149},{"name":"call.wast","passed":29,"failed":62},{"name":"call_indirect.wast","passed":36,"failed":134},{"name":"comments.wast","passed":7,"failed":1},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":371,"failed":248},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":47,"failed":14},{"name":"elem.wast","passed":50,"failed":49},{"name":"endianness.wast","passed":1,"failed":68},{"name":"exports.wast","passed":92,"failed":4},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":2,"failed":6},{"name":"float_exprs.wast","passed":761,"failed":139},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":6,"failed":84},{"name":"float_misc.wast","passed":437,"failed":4},{"name":"forward.wast","passed":1,"failed":4},{"name":"func.wast","passed":124,"failed":48},{"name":"func_ptrs.wast","passed":10,"failed":26},{"name":"global.wast","passed":51,"failed":59},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":120,"failed":121},{"name":"imports.wast","passed":74,"failed":109},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":14,"failed":15},{"name":"left-to-right.wast","passed":1,"failed":95},{"name":"linking.wast","passed":21,"failed":111},{"name":"load.wast","passed":60,"failed":37},{"name":"local_get.wast","passed":32,"failed":4},{"name":"local_set.wast","passed":50,"failed":3},{"name":"local_tee.wast","passed":68,"failed":29},{"name":"loop.wast","passed":93,"failed":27},{"name":"memory.wast","passed":34,"failed":45},{"name":"memory_grow.wast","passed":12,"failed":84},{"name":"memory_redundancy.wast","passed":1,"failed":7},{"name":"memory_size.wast","passed":6,"failed":36},{"name":"memory_trap.wast","passed":2,"failed":180},{"name":"names.wast","passed":485,"failed":1},{"name":"nop.wast","passed":46,"failed":42},{"name":"return.wast","passed":73,"failed":11},{"name":"select.wast","passed":86,"failed":62},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":2,"failed":5},{"name":"start.wast","passed":9,"failed":11},{"name":"store.wast","passed":59,"failed":9},{"name":"switch.wast","passed":2,"failed":26},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":22,"failed":14},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":50,"failed":14},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":35,"failed":15},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] +0.2.0-alpha.0,19344,884,[{"name":"address.wast","passed":181,"failed":79},{"name":"align.wast","passed":156,"failed":0},{"name":"binary-leb128.wast","passed":91,"failed":0},{"name":"binary.wast","passed":112,"failed":0},{"name":"block.wast","passed":220,"failed":3},{"name":"br.wast","passed":97,"failed":0},{"name":"br_if.wast","passed":118,"failed":0},{"name":"br_table.wast","passed":171,"failed":3},{"name":"call.wast","passed":73,"failed":18},{"name":"call_indirect.wast","passed":50,"failed":120},{"name":"comments.wast","passed":7,"failed":1},{"name":"const.wast","passed":778,"failed":0},{"name":"conversions.wast","passed":439,"failed":180},{"name":"custom.wast","passed":11,"failed":0},{"name":"data.wast","passed":47,"failed":14},{"name":"elem.wast","passed":56,"failed":43},{"name":"endianness.wast","passed":29,"failed":40},{"name":"exports.wast","passed":92,"failed":4},{"name":"f32.wast","passed":2514,"failed":0},{"name":"f32_bitwise.wast","passed":364,"failed":0},{"name":"f32_cmp.wast","passed":2407,"failed":0},{"name":"f64.wast","passed":2514,"failed":0},{"name":"f64_bitwise.wast","passed":364,"failed":0},{"name":"f64_cmp.wast","passed":2407,"failed":0},{"name":"fac.wast","passed":6,"failed":2},{"name":"float_exprs.wast","passed":890,"failed":10},{"name":"float_literals.wast","passed":163,"failed":0},{"name":"float_memory.wast","passed":78,"failed":12},{"name":"float_misc.wast","passed":437,"failed":4},{"name":"forward.wast","passed":5,"failed":0},{"name":"func.wast","passed":168,"failed":4},{"name":"func_ptrs.wast","passed":10,"failed":26},{"name":"global.wast","passed":103,"failed":7},{"name":"i32.wast","passed":460,"failed":0},{"name":"i64.wast","passed":416,"failed":0},{"name":"if.wast","passed":231,"failed":10},{"name":"imports.wast","passed":80,"failed":103},{"name":"inline-module.wast","passed":1,"failed":0},{"name":"int_exprs.wast","passed":108,"failed":0},{"name":"int_literals.wast","passed":51,"failed":0},{"name":"labels.wast","passed":26,"failed":3},{"name":"left-to-right.wast","passed":92,"failed":4},{"name":"linking.wast","passed":29,"failed":103},{"name":"load.wast","passed":93,"failed":4},{"name":"local_get.wast","passed":36,"failed":0},{"name":"local_set.wast","passed":53,"failed":0},{"name":"local_tee.wast","passed":93,"failed":4},{"name":"loop.wast","passed":116,"failed":4},{"name":"memory.wast","passed":78,"failed":1},{"name":"memory_grow.wast","passed":91,"failed":5},{"name":"memory_redundancy.wast","passed":8,"failed":0},{"name":"memory_size.wast","passed":35,"failed":7},{"name":"memory_trap.wast","passed":180,"failed":2},{"name":"names.wast","passed":485,"failed":1},{"name":"nop.wast","passed":78,"failed":10},{"name":"return.wast","passed":84,"failed":0},{"name":"select.wast","passed":114,"failed":34},{"name":"skip-stack-guard-page.wast","passed":1,"failed":10},{"name":"stack.wast","passed":7,"failed":0},{"name":"start.wast","passed":11,"failed":9},{"name":"store.wast","passed":68,"failed":0},{"name":"switch.wast","passed":28,"failed":0},{"name":"token.wast","passed":58,"failed":0},{"name":"traps.wast","passed":36,"failed":0},{"name":"type.wast","passed":3,"failed":0},{"name":"unreachable.wast","passed":64,"failed":0},{"name":"unreached-invalid.wast","passed":118,"failed":0},{"name":"unwind.wast","passed":50,"failed":0},{"name":"utf8-custom-section-id.wast","passed":176,"failed":0},{"name":"utf8-import-field.wast","passed":176,"failed":0},{"name":"utf8-import-module.wast","passed":176,"failed":0},{"name":"utf8-invalid-encoding.wast","passed":176,"failed":0}] diff --git a/crates/tinywasm/tests/generated/progress-mvp.svg b/crates/tinywasm/tests/generated/progress-mvp.svg index 331b0f8..e1fd709 100644 --- a/crates/tinywasm/tests/generated/progress-mvp.svg +++ b/crates/tinywasm/tests/generated/progress-mvp.svg @@ -36,24 +36,29 @@ TinyWasm Version - + v0.0.3 (9258) - - + + v0.0.4 (9258) - - + + v0.0.5 (11135) - - -v0.0.6-alpha.0 (17630) + + +v0.1.0 (17630) - - - - - + + +v0.2.0-alpha.0 (19344) + + + + + + + diff --git a/crates/tinywasm/tests/test-mvp.rs b/crates/tinywasm/tests/test-mvp.rs index fdd043d..5107551 100644 --- a/crates/tinywasm/tests/test-mvp.rs +++ b/crates/tinywasm/tests/test-mvp.rs @@ -1,20 +1,32 @@ mod testsuite; use eyre::{eyre, Result}; +use owo_colors::OwoColorize; use testsuite::TestSuite; fn main() -> Result<()> { + let args = std::env::args().collect::>(); + if args.len() < 2 || args[1] != "--enable" { + return Ok(()); + } + test_mvp() } fn test_mvp() -> Result<()> { let mut test_suite = TestSuite::new(); + TestSuite::set_log_level(log::LevelFilter::Off); test_suite.run_spec_group(wasm_testsuite::MVP_TESTS)?; + test_suite.save_csv("./tests/generated/mvp.csv", env!("CARGO_PKG_VERSION"))?; if test_suite.failed() { - eprintln!("\n\nfailed one or more tests:\n{:#?}", test_suite); - Err(eyre!("failed one or more tests")) + println!(); + Err(eyre!(format!( + "{}:\n{:#?}", + "failed one or more tests".red().bold(), + test_suite, + ))) } else { println!("\n\npassed all tests:\n{:#?}", test_suite); Ok(()) diff --git a/crates/tinywasm/tests/test-wast.rs b/crates/tinywasm/tests/test-wast.rs index 68372a0..42f7091 100644 --- a/crates/tinywasm/tests/test-wast.rs +++ b/crates/tinywasm/tests/test-wast.rs @@ -1,14 +1,19 @@ use std::path::PathBuf; -use eyre::{bail, Result}; +use eyre::{bail, eyre, Result}; +use owo_colors::OwoColorize; use testsuite::TestSuite; mod testsuite; fn main() -> Result<()> { let args = std::env::args().collect::>(); - if args.len() < 2 { - bail!("usage: cargo test-wast ") + if args.len() < 2 || args[1] != "--enable" { + return Ok(()); + } + + if args.len() < 3 { + bail!("usage: cargo test-wast "); } // cwd for relative paths, absolute paths are kept as-is @@ -21,7 +26,7 @@ fn main() -> Result<()> { PathBuf::from("./") }; - wast_file.push(&args[1]); + wast_file.push(&args[2]); let wast_file = cwd.join(wast_file); test_wast(wast_file.to_str().expect("wast_file is not a valid path"))?; @@ -40,9 +45,14 @@ fn test_wast(wast_file: &str) -> Result<()> { test_suite.run_paths(&[wast_file])?; if test_suite.failed() { - eprintln!("\n\nfailed one or more tests:\n{:#?}", test_suite); + println!(); test_suite.print_errors(); - bail!("failed one or more tests") + println!(); + Err(eyre!(format!( + "{}:\n{:#?}", + "failed one or more tests".red().bold(), + test_suite, + ))) } else { println!("\n\npassed all tests:\n{:#?}", test_suite); Ok(()) diff --git a/crates/tinywasm/tests/testsuite/indexmap.rs b/crates/tinywasm/tests/testsuite/indexmap.rs new file mode 100644 index 0000000..d4e3869 --- /dev/null +++ b/crates/tinywasm/tests/testsuite/indexmap.rs @@ -0,0 +1,53 @@ +pub struct IndexMap { + map: std::collections::HashMap, + keys: Vec, +} + +impl IndexMap +where + K: std::cmp::Eq + std::hash::Hash + Clone, +{ + pub fn new() -> Self { + Self { + map: std::collections::HashMap::new(), + keys: Vec::new(), + } + } + + pub fn insert(&mut self, key: K, value: V) -> Option { + if let std::collections::hash_map::Entry::Occupied(mut e) = self.map.entry(key.clone()) { + return Some(e.insert(value)); + } + + self.keys.push(key.clone()); + self.map.insert(key, value) + } + + pub fn get(&self, key: &K) -> Option<&V> { + self.map.get(key) + } + + pub fn get_mut(&mut self, key: &K) -> Option<&mut V> { + self.map.get_mut(key) + } + + pub fn iter(&self) -> impl Iterator { + self.keys.iter().map(move |k| (k, self.map.get(k).unwrap())) + } + + pub fn len(&self) -> usize { + self.map.len() + } + + pub fn keys(&self) -> impl Iterator { + self.keys.iter() + } + + pub fn values(&self) -> impl Iterator { + self.map.values() + } + + pub fn values_mut(&mut self) -> impl Iterator { + self.map.values_mut() + } +} diff --git a/crates/tinywasm/tests/testsuite/mod.rs b/crates/tinywasm/tests/testsuite/mod.rs index fd3e39f..36fc727 100644 --- a/crates/tinywasm/tests/testsuite/mod.rs +++ b/crates/tinywasm/tests/testsuite/mod.rs @@ -1,6 +1,7 @@ #![allow(dead_code)] // rust analyzer doesn't recognize that code is used by tests without harness use eyre::Result; +use owo_colors::OwoColorize; use std::io::{BufRead, Seek, SeekFrom}; use std::{ collections::BTreeMap, @@ -8,11 +9,14 @@ use std::{ io::BufReader, }; +mod indexmap; mod run; mod util; use serde::{Deserialize, Serialize}; +use self::indexmap::IndexMap; + #[derive(Serialize, Deserialize)] pub struct TestGroupResult { pub name: String, @@ -20,33 +24,49 @@ pub struct TestGroupResult { pub failed: usize, } -pub struct TestSuite(BTreeMap); +fn format_linecol(linecol: (usize, usize)) -> String { + format!("{}:{}", linecol.0 + 1, linecol.1 + 1) +} + +pub struct TestSuite(BTreeMap, Vec); impl TestSuite { + pub fn skip(&mut self, groups: &[&str]) { + self.1.extend(groups.iter().map(|s| s.to_string())); + } + pub fn set_log_level(level: log::LevelFilter) { pretty_env_logger::formatted_builder().filter_level(level).init(); } pub fn print_errors(&self) { for (group_name, group) in &self.0 { - for (test_name, test) in &group.tests { + let tests = &group.tests; + for (test_name, test) in tests.iter() { if let Err(e) = &test.result { - eprintln!("{}: {} failed: {:?}", group_name, test_name, e); + eprintln!( + "{} {} failed: {:?}", + link(group_name, &group.file, Some(test.linecol.0 + 1)) + .bold() + .underline(), + test_name.bold(), + e.to_string().bright_red() + ); } } } } pub fn new() -> Self { - Self(BTreeMap::new()) + Self(BTreeMap::new(), Vec::new()) } pub fn failed(&self) -> bool { self.0.values().any(|group| group.stats().1 > 0) } - fn test_group(&mut self, name: &str) -> &mut TestGroup { - self.0.entry(name.to_string()).or_insert_with(TestGroup::new) + fn test_group(&mut self, name: &str, file: &str) -> &mut TestGroup { + self.0.entry(name.to_string()).or_insert_with(|| TestGroup::new(file)) } // create or add to a test result file @@ -93,9 +113,17 @@ impl TestSuite { } } +fn link(name: &str, file: &str, line: Option) -> String { + let (path, name) = match line { + None => (file.to_string(), name.to_owned()), + Some(line) => (format!("{}:{}:0", file, line), (format!("{}:{}", name, line))), + }; + + format!("\x1b]8;;file://{}\x1b\\{}\x1b]8;;\x1b\\", path, name) +} + impl Debug for TestSuite { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - use owo_colors::OwoColorize; let mut total_passed = 0; let mut total_failed = 0; @@ -104,7 +132,7 @@ impl Debug for TestSuite { total_passed += group_passed; total_failed += group_failed; - writeln!(f, "{}", group_name.bold().underline())?; + writeln!(f, "{}", link(group_name, &group.file, None).bold().underline())?; writeln!(f, " Tests Passed: {}", group_passed.to_string().green())?; writeln!(f, " Tests Failed: {}", group_failed.to_string().red())?; @@ -132,12 +160,16 @@ impl Debug for TestSuite { } struct TestGroup { - tests: BTreeMap, + tests: IndexMap, + file: String, } impl TestGroup { - fn new() -> Self { - Self { tests: BTreeMap::new() } + fn new(file: &str) -> Self { + Self { + tests: IndexMap::new(), + file: file.to_string(), + } } fn stats(&self) -> (usize, usize) { @@ -154,12 +186,13 @@ impl TestGroup { (passed_count, failed_count) } - fn add_result(&mut self, name: &str, span: wast::token::Span, result: Result<()>) { - self.tests.insert(name.to_string(), TestCase { result, _span: span }); + fn add_result(&mut self, name: &str, linecol: (usize, usize), result: Result<()>) { + self.tests.insert(name.to_string(), TestCase { result, linecol }); } } +#[derive(Debug)] struct TestCase { result: Result<()>, - _span: wast::token::Span, + linecol: (usize, usize), } diff --git a/crates/tinywasm/tests/testsuite/run.rs b/crates/tinywasm/tests/testsuite/run.rs index fc3d7bc..8a1ddf4 100644 --- a/crates/tinywasm/tests/testsuite/run.rs +++ b/crates/tinywasm/tests/testsuite/run.rs @@ -1,10 +1,14 @@ use crate::testsuite::util::*; -use std::borrow::Cow; +use std::{ + borrow::Cow, + panic::{catch_unwind, AssertUnwindSafe}, +}; use super::TestSuite; use eyre::{eyre, Result}; use log::{debug, error, info}; -use tinywasm_types::TinyWasmModule; +use tinywasm::{Extern, Imports, ModuleInstance}; +use tinywasm_types::{ModuleInstanceAddr, WasmValue}; use wast::{lexer::Lexer, parser::ParseBuffer, Wast}; impl TestSuite { @@ -12,16 +16,37 @@ impl TestSuite { tests.iter().for_each(|group| { let group_wast = std::fs::read(group).expect("failed to read test wast"); let group_wast = Cow::Owned(group_wast); - debug!("running group: {}", group); self.run_group(group, group_wast).expect("failed to run group"); }); Ok(()) } + fn imports(registered_modules: Vec<(String, ModuleInstanceAddr)>) -> Result { + let mut imports = Imports::new(); + + imports + .define("spectest", "global_i32", Extern::global(WasmValue::I32(666), false))? + .define("spectest", "global_i64", Extern::global(WasmValue::I64(666), false))? + .define("spectest", "global_f32", Extern::global(WasmValue::F32(666.0), false))? + .define("spectest", "global_f64", Extern::global(WasmValue::F64(666.0), false))?; + + for (name, addr) in registered_modules { + imports.link_module(&name, addr)?; + } + + Ok(imports) + } + pub fn run_spec_group(&mut self, tests: &[&str]) -> Result<()> { tests.iter().for_each(|group| { let group_wast = wasm_testsuite::get_test_wast(group).expect("failed to get test wast"); + if self.1.contains(&group.to_string()) { + info!("skipping group: {}", group); + self.test_group(&format!("{} (skipped)", group), group); + return; + } + self.run_group(group, group_wast).expect("failed to run group"); }); @@ -29,7 +54,8 @@ impl TestSuite { } pub fn run_group(&mut self, group_name: &str, group_wast: Cow<'_, [u8]>) -> Result<()> { - let test_group = self.test_group(group_name); + let file_name = group_name.split('/').last().unwrap_or(group_name); + let test_group = self.test_group(file_name, group_name); let wast = std::str::from_utf8(&group_wast).expect("failed to convert wast to utf8"); let mut lexer = Lexer::new(wast); @@ -39,22 +65,46 @@ impl TestSuite { let buf = ParseBuffer::new_with_lexer(lexer).expect("failed to create parse buffer"); let wast_data = wast::parser::parse::(&buf).expect("failed to parse wat"); - let mut last_module: Option = None; - debug!("running {} tests for group: {}", wast_data.directives.len(), group_name); + let mut store = tinywasm::Store::default(); + let mut registered_modules = Vec::new(); + let mut last_module: Option = None; + + println!("running {} tests for group: {}", wast_data.directives.len(), group_name); for (i, directive) in wast_data.directives.into_iter().enumerate() { let span = directive.span(); use wast::WastDirective::*; - let name = format!("{}-{}", group_name, i); - - debug!("directive: {:?}", directive); match directive { + Register { span, name, .. } => { + let Some(last) = &last_module else { + test_group.add_result( + &format!("Register({})", i), + span.linecol_in(wast), + Err(eyre!("no module to register")), + ); + continue; + }; + + registered_modules.push((name.to_string(), last.id())); + test_group.add_result(&format!("Register({})", i), span.linecol_in(wast), Ok(())); + } + Wat(mut module) => { + // TODO: modules are not properly isolated from each other - tests fail because of this otherwise + store = tinywasm::Store::default(); debug!("got wat module"); - - let result = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) - .and_then(|res| res); + let result = catch_unwind(AssertUnwindSafe(|| { + let m = parse_module_bytes(&module.encode().expect("failed to encode module")) + .expect("failed to parse module bytes"); + tinywasm::Module::from(m) + .instantiate(&mut store, Some(Self::imports(registered_modules.clone()).unwrap())) + .map_err(|e| { + println!("failed to instantiate module: {:?}", e); + e + }) + .expect("failed to instantiate module") + })) + .map_err(|e| eyre!("failed to parse wat module: {:?}", try_downcast_panic(e))); match &result { Err(_) => last_module = None, @@ -65,7 +115,7 @@ impl TestSuite { debug!("failed to parse module: {:?}", err) } - test_group.add_result(&format!("{}-parse", name), span, result.map(|_| ())); + test_group.add_result(&format!("Wat({})", i), span.linecol_in(wast), result.map(|_| ())); } AssertMalformed { @@ -74,17 +124,17 @@ impl TestSuite { message: _, } => { let Ok(module) = module.encode() else { - test_group.add_result(&format!("{}-malformed", name), span, Ok(())); + test_group.add_result(&format!("AssertMalformed({})", i), span.linecol_in(wast), Ok(())); continue; }; let res = catch_unwind_silent(|| parse_module_bytes(&module)) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) + .map_err(|e| eyre!("failed to parse module (expected): {:?}", try_downcast_panic(e))) .and_then(|res| res); test_group.add_result( - &format!("{}-malformed", name), - span, + &format!("AssertMalformed({})", i), + span.linecol_in(wast), match res { Ok(_) => Err(eyre!("expected module to be malformed")), Err(_) => Ok(()), @@ -98,12 +148,12 @@ impl TestSuite { message: _, } => { let res = catch_unwind_silent(move || parse_module_bytes(&module.encode().unwrap())) - .map_err(|e| eyre!("failed to parse module: {:?}", e)) + .map_err(|e| eyre!("failed to parse module (invalid): {:?}", try_downcast_panic(e))) .and_then(|res| res); test_group.add_result( - &format!("{}-invalid", name), - span, + &format!("AssertInvalid({})", i), + span.linecol_in(wast), match res { Ok(_) => Err(eyre!("expected module to be invalid")), Err(_) => Ok(()), @@ -114,11 +164,18 @@ impl TestSuite { AssertTrap { exec, message: _, span } => { let res: Result, _> = catch_unwind_silent(|| { let (module, name, args) = match exec { - wast::WastExecute::Wat(_wat) => { - panic!("wat not supported"); + wast::WastExecute::Wat(mut wat) => { + let module = parse_module_bytes(&wat.encode().expect("failed to encode module")) + .expect("failed to parse module"); + let module = tinywasm::Module::from(module); + module.instantiate( + &mut store, + Some(Self::imports(registered_modules.clone()).unwrap()), + )?; + return Ok(()); } wast::WastExecute::Get { module: _, global: _ } => { - panic!("wat not supported"); + panic!("get not supported"); } wast::WastExecute::Invoke(invoke) => (last_module.as_ref(), invoke.name, invoke.args), }; @@ -128,48 +185,34 @@ impl TestSuite { .collect::>>() .expect("failed to convert args"); - exec_fn(module, name, &args).map(|_| ()) + exec_fn_instance(module, &mut store, name, &args).map(|_| ()) }); match res { Err(err) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!("test panicked: {:?}, span: {:?}", err, span.linecol_in(&wast))), + &format!("AssertTrap({})", i), + span.linecol_in(wast), + Err(eyre!("test panicked: {:?}", try_downcast_panic(err))), ), Ok(Err(tinywasm::Error::Trap(_))) => { - test_group.add_result(&format!("{}-trap", name), span, Ok(())) + test_group.add_result(&format!("AssertTrap({})", i), span.linecol_in(wast), Ok(())) } Ok(Err(err)) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!( - "expected trap, got error: {:?}, span: {:?}", - err, - span.linecol_in(&wast) - )), + &format!("AssertTrap({})", i), + span.linecol_in(wast), + Err(eyre!("expected trap, got error: {:?}", err,)), ), Ok(Ok(())) => test_group.add_result( - &format!("{}-trap", name), - span, - Err(eyre!("expected trap, got ok, span: {:?}", span.linecol_in(&wast))), + &format!("AssertTrap({})", i), + span.linecol_in(wast), + Err(eyre!("expected trap, got ok")), ), } } - AssertReturn { span, exec, results } => { - info!("AssertReturn: {:?}", exec); + Invoke(invoke) => { + let name = invoke.name; let res: Result, _> = catch_unwind_silent(|| { - let invoke = match exec { - wast::WastExecute::Wat(_) => { - error!("wat not supported"); - return Err(eyre!("wat not supported")); - } - wast::WastExecute::Get { module: _, global: _ } => return Err(eyre!("get not supported")), - wast::WastExecute::Invoke(invoke) => invoke, - }; - - debug!("invoke: {:?}", invoke); let args = invoke .args .into_iter() @@ -180,10 +223,56 @@ impl TestSuite { e })?; - let outcomes = exec_fn(last_module.as_ref(), invoke.name, &args).map_err(|e| { + exec_fn_instance(last_module.as_ref(), &mut store, invoke.name, &args).map_err(|e| { error!("failed to execute function: {:?}", e); e })?; + Ok(()) + }); + + let res = res + .map_err(|e| eyre!("test panicked: {:?}", try_downcast_panic(e))) + .and_then(|r| r); + + test_group.add_result(&format!("Invoke({}-{})", name, i), span.linecol_in(wast), res); + } + + AssertReturn { span, exec, results } => { + info!("AssertReturn: {:?}", exec); + let invoke = match match exec { + wast::WastExecute::Wat(_) => Err(eyre!("wat not supported")), + wast::WastExecute::Get { module: _, global: _ } => Err(eyre!("get not supported")), + wast::WastExecute::Invoke(invoke) => Ok(invoke), + } { + Ok(invoke) => invoke, + Err(err) => { + test_group.add_result( + &format!("AssertReturn(unsupported-{})", i), + span.linecol_in(wast), + Err(eyre!("unsupported directive: {:?}", err)), + ); + continue; + } + }; + let invoke_name = invoke.name; + + let res: Result, _> = catch_unwind_silent(|| { + debug!("invoke: {:?}", invoke); + let args = invoke + .args + .into_iter() + .map(wastarg2tinywasmvalue) + .collect::>>() + .map_err(|e| { + error!("failed to convert args: {:?}", e); + e + })?; + + let outcomes = + exec_fn_instance(last_module.as_ref(), &mut store, invoke.name, &args).map_err(|e| { + error!("failed to execute function: {:?}", e); + e + })?; debug!("outcomes: {:?}", outcomes); @@ -212,25 +301,27 @@ impl TestSuite { .zip(expected) .enumerate() .try_for_each(|(i, (outcome, exp))| { - (outcome.eq_loose(&exp)).then_some(()).ok_or_else(|| { - eyre!( - "span: {:?}: result {} did not match: {:?} != {:?}", - span.linecol_in(&wast), - i, - outcome, - exp - ) - }) + (outcome.eq_loose(&exp)) + .then_some(()) + .ok_or_else(|| eyre!(" result {} did not match: {:?} != {:?}", i, outcome, exp)) }) }); let res = res - .map_err(|e| eyre!("test panicked: {:?}", e.downcast_ref::<&str>())) + .map_err(|e| eyre!("test panicked: {:?}", try_downcast_panic(e))) .and_then(|r| r); - test_group.add_result(&format!("{}-return", name), span, res); + test_group.add_result( + &format!("AssertReturn({}-{})", invoke_name, i), + span.linecol_in(wast), + res, + ); } - _ => test_group.add_result(&format!("{}-unknown", name), span, Err(eyre!("unsupported directive"))), + _ => test_group.add_result( + &format!("Unknown({})", i), + span.linecol_in(wast), + Err(eyre!("unsupported directive")), + ), } } diff --git a/crates/tinywasm/tests/testsuite/util.rs b/crates/tinywasm/tests/testsuite/util.rs index 85b2a06..9ba7934 100644 --- a/crates/tinywasm/tests/testsuite/util.rs +++ b/crates/tinywasm/tests/testsuite/util.rs @@ -1,12 +1,43 @@ -use std::panic; +use std::panic::{self, AssertUnwindSafe}; use eyre::{eyre, Result}; use tinywasm_types::{TinyWasmModule, WasmValue}; +pub fn try_downcast_panic(panic: Box) -> String { + let info = panic + .downcast_ref::() + .or(None) + .map(|p| p.to_string()) + .clone(); + let info_string = panic.downcast_ref::().cloned(); + let info_str = panic.downcast::<&str>().ok().map(|s| *s); + + info.unwrap_or( + info_str + .unwrap_or(&info_string.unwrap_or("unknown panic".to_owned())) + .to_string(), + ) +} + +pub fn exec_fn_instance( + instance: Option<&tinywasm::ModuleInstance>, + store: &mut tinywasm::Store, + name: &str, + args: &[tinywasm_types::WasmValue], +) -> Result, tinywasm::Error> { + let Some(instance) = instance else { + return Err(tinywasm::Error::Other("no instance found".to_string())); + }; + + let func = instance.exported_func_by_name(store, name)?; + func.call(store, args) +} + pub fn exec_fn( module: Option<&TinyWasmModule>, name: &str, args: &[tinywasm_types::WasmValue], + imports: Option, ) -> Result, tinywasm::Error> { let Some(module) = module else { return Err(tinywasm::Error::Other("no module found".to_string())); @@ -14,14 +45,14 @@ pub fn exec_fn( let mut store = tinywasm::Store::new(); let module = tinywasm::Module::from(module); - let instance = module.instantiate(&mut store)?; - instance.get_func(&store, name)?.call(&mut store, args) + let instance = module.instantiate(&mut store, imports)?; + instance.exported_func_by_name(&store, name)?.call(&mut store, args) } -pub fn catch_unwind_silent R + panic::UnwindSafe, R>(f: F) -> std::thread::Result { +pub fn catch_unwind_silent R, R>(f: F) -> std::thread::Result { let prev_hook = panic::take_hook(); panic::set_hook(Box::new(|_| {})); - let result = panic::catch_unwind(f); + let result = panic::catch_unwind(AssertUnwindSafe(f)); panic::set_hook(prev_hook); result } @@ -33,7 +64,7 @@ pub fn parse_module_bytes(bytes: &[u8]) -> Result { pub fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result { let wast::WastArg::Core(arg) = arg else { - return Err(eyre!("unsupported arg type")); + return Err(eyre!("unsupported arg type: Component")); }; use wast::core::WastArgCore::*; @@ -42,7 +73,13 @@ pub fn wastarg2tinywasmvalue(arg: wast::WastArg) -> Result WasmValue::F64(f64::from_bits(f.bits)), I32(i) => WasmValue::I32(i), I64(i) => WasmValue::I64(i), - _ => return Err(eyre!("unsupported arg type")), + // RefExtern(v) => WasmValue::RefExtern(v), + RefNull(t) => WasmValue::RefNull(match t { + wast::core::HeapType::Func => tinywasm_types::ValType::FuncRef, + wast::core::HeapType::Extern => tinywasm_types::ValType::ExternRef, + _ => return Err(eyre!("unsupported arg type: refnull: {:?}", t)), + }), + v => return Err(eyre!("unsupported arg type: {:?}", v)), }) } diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index 95fddc5..01c5b6e 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -1,3 +1,5 @@ +use crate::MemAddr; + use super::{FuncAddr, GlobalAddr, LabelAddr, LocalAddr, TableAddr, TypeAddr, ValType}; #[derive(Debug, Copy, Clone, PartialEq, Eq)] @@ -10,7 +12,9 @@ pub enum BlockArgs { /// Represents a memory immediate in a WebAssembly memory instruction. #[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct MemArg { + pub mem_addr: MemAddr, pub align: u8, + pub align_max: u8, pub offset: u64, } @@ -26,6 +30,8 @@ pub enum ConstInstruction { F32Const(f32), F64Const(f64), GlobalGet(GlobalAddr), + RefNull(ValType), + RefFunc(FuncAddr), } /// A WebAssembly Instruction @@ -99,8 +105,8 @@ pub enum Instruction { I64Store8(MemArg), I64Store16(MemArg), I64Store32(MemArg), - MemorySize, - MemoryGrow, + MemorySize(MemAddr, u8), + MemoryGrow(MemAddr, u8), // Constants I32Const(i32), @@ -108,6 +114,11 @@ pub enum Instruction { F32Const(f32), F64Const(f64), + // Reference Types + RefNull(ValType), + RefFunc(FuncAddr), + RefIsNull, + // Numeric Instructions // See I32Eqz, diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index ad82bd4..d3bf81a 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -88,9 +88,24 @@ pub enum WasmValue { F64(f64), // Vec types // V128(i128), + // RefExtern(ExternAddr), + // RefHost(FuncAddr), + RefNull(ValType), } impl WasmValue { + pub fn const_instr(&self) -> ConstInstruction { + match self { + Self::I32(i) => ConstInstruction::I32Const(*i), + Self::I64(i) => ConstInstruction::I64Const(*i), + Self::F32(i) => ConstInstruction::F32Const(*i), + Self::F64(i) => ConstInstruction::F64Const(*i), + Self::RefNull(ty) => ConstInstruction::RefNull(*ty), + // Self::RefExtern(addr) => ConstInstruction::RefExtern(*addr), + // _ => unimplemented!("const_instr for {:?}", self), + } + } + /// Get the default value for a given type. pub fn default_for(ty: ValType) -> Self { match ty { @@ -208,6 +223,7 @@ impl Debug for WasmValue { WasmValue::I64(i) => write!(f, "i64({})", i), WasmValue::F32(i) => write!(f, "f32({})", i), WasmValue::F64(i) => write!(f, "f64({})", i), + WasmValue::RefNull(ty) => write!(f, "ref.null({:?})", ty), // WasmValue::V128(i) => write!(f, "v128({})", i), } } @@ -221,13 +237,14 @@ impl WasmValue { Self::I64(_) => ValType::I64, Self::F32(_) => ValType::F32, Self::F64(_) => ValType::F64, + Self::RefNull(ty) => *ty, // Self::V128(_) => ValType::V128, } } } /// Type of a WebAssembly value. -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ValType { /// A 32-bit integer. I32, @@ -245,6 +262,12 @@ pub enum ValType { ExternRef, } +impl ValType { + pub fn default_value(&self) -> WasmValue { + WasmValue::default_for(*self) + } +} + /// A WebAssembly External Kind. /// /// See @@ -334,7 +357,7 @@ pub struct Global { pub init: ConstInstruction, } -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct GlobalType { pub mutable: bool, pub ty: ValType, pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy