From 10d2e21b2171cf2a2227f9de04ac89985042a5f0 Mon Sep 17 00:00:00 2001 From: Randy Reddig Date: Mon, 10 Jun 2024 12:21:08 -0700 Subject: [PATCH 01/58] wasm-tools, wit-parser: add --all-features to enable all features gated with @unstable (#1599) * wasm-tools, wit-parser: add --all-features to enable all features gated with @unstable * wasm-tools: put logic for --all-features into WitResolve::resolve_with_features * wit-parser, wasm-tools: --all-features is now an alias for --features * * wasm-tools, wit-parser: --features . instead of * to imply --all-features * wasm-tools: document --features . as alias for --all-features * tests/cli: add tests for --all-features and --features . * wasm-tools: rustfmt * wasm-tools: remove --features . as alias for --all-features --- crates/wit-parser/src/resolve.rs | 6 +++++- src/bin/wasm-tools/component.rs | 25 ++++++++++++++++++---- tests/cli/wit-with-all-features.wit | 18 ++++++++++++++++ tests/cli/wit-with-all-features.wit.stdout | 20 +++++++++++++++++ 4 files changed, 64 insertions(+), 5 deletions(-) create mode 100644 tests/cli/wit-with-all-features.wit create mode 100644 tests/cli/wit-with-all-features.wit.stdout diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index 8a34a8e667..a41290a6aa 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -74,6 +74,10 @@ pub struct Resolve { /// set. #[cfg_attr(feature = "serde", serde(skip))] pub features: IndexSet, + + /// Activate all features for this [`Resolve`]. + #[cfg_attr(feature = "serde", serde(skip))] + pub all_features: bool, } /// A WIT package within a `Resolve`. @@ -1142,7 +1146,7 @@ impl Resolve { fn include_stability(&self, stability: &Stability) -> bool { match stability { Stability::Stable { .. } | Stability::Unknown => true, - Stability::Unstable { feature } => self.features.contains(feature), + Stability::Unstable { feature } => self.features.contains(feature) || self.all_features, } } } diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index b238adf4e4..5d759df482 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -182,11 +182,19 @@ struct WitResolve { /// items are otherwise hidden by default. #[clap(long)] features: Vec, + + /// Enable all features when parsing the `wit` option. + /// + /// This flag enables all `@unstable` features in WIT documents where the + /// items are otherwise hidden by default. + #[clap(long)] + all_features: bool, } impl WitResolve { - fn resolve_with_features(features: &[String]) -> Resolve { + fn resolve_with_features(features: &[String], all_features: bool) -> Resolve { let mut resolve = Resolve::default(); + resolve.all_features = all_features; for feature in features { for f in feature.split_whitespace() { for f in f.split(',').filter(|s| !s.is_empty()) { @@ -198,7 +206,7 @@ impl WitResolve { } fn load(&self) -> Result<(Resolve, Vec)> { - let mut resolve = Self::resolve_with_features(&self.features); + let mut resolve = Self::resolve_with_features(&self.features, self.all_features); let (pkg_ids, _) = resolve.push_path(&self.wit)?; Ok((resolve, pkg_ids)) } @@ -494,6 +502,13 @@ pub struct WitOpts { /// items are otherwise hidden by default. #[clap(long)] features: Vec, + + /// Enable all features when parsing the `wit` option. + /// + /// This flag enables all `@unstable` features in WIT documents where the + /// items are otherwise hidden by default. + #[clap(long)] + all_features: bool, } impl WitOpts { @@ -524,7 +539,8 @@ impl WitOpts { // `parse_wit_from_path`. if let Some(input) = &self.input { if input.is_dir() { - let mut resolve = WitResolve::resolve_with_features(&self.features); + let mut resolve = + WitResolve::resolve_with_features(&self.features, self.all_features); let (pkg_ids, _) = resolve.push_dir(&input)?; return Ok(DecodedWasm::WitPackages(resolve, pkg_ids)); } @@ -572,7 +588,8 @@ impl WitOpts { Ok(s) => s, Err(_) => bail!("input was not valid utf-8"), }; - let mut resolve = WitResolve::resolve_with_features(&self.features); + let mut resolve = + WitResolve::resolve_with_features(&self.features, self.all_features); let pkgs = UnresolvedPackageGroup::parse(path, input)?; let ids = resolve.append(pkgs)?; Ok(DecodedWasm::WitPackages(resolve, ids)) diff --git a/tests/cli/wit-with-all-features.wit b/tests/cli/wit-with-all-features.wit new file mode 100644 index 0000000000..cdae70c7a8 --- /dev/null +++ b/tests/cli/wit-with-all-features.wit @@ -0,0 +1,18 @@ +// RUN: component wit --all-features % + +package a:b; + +@unstable(feature = foo) +interface foo { + @unstable(feature = foo) + type t = u32; +} + +@unstable(feature = bar) +interface bar { + @unstable(feature = baz) + use foo.{t}; + + @unstable(feature = qux) + record x { y: t } +} diff --git a/tests/cli/wit-with-all-features.wit.stdout b/tests/cli/wit-with-all-features.wit.stdout new file mode 100644 index 0000000000..a193ef7786 --- /dev/null +++ b/tests/cli/wit-with-all-features.wit.stdout @@ -0,0 +1,20 @@ +/// RUN: component wit --all-features % +package a:b; + +@unstable(feature = foo) +interface foo { + @unstable(feature = foo) + type t = u32; +} + +@unstable(feature = bar) +interface bar { + @unstable(feature = baz) + use foo.{t}; + + @unstable(feature = qux) + record x { + y: t, + } +} + From 8dc6ddf65063130af285c3b0b4582f7a508e9a0e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jun 2024 15:47:18 -0500 Subject: [PATCH 02/58] Simplify and clarify some value type parsing (#1601) Reading over #1600 I remembered that I have a difficult time understanding how value types are encoded in wasm. There's a number of overlapping concerns and a bit of duplication within `wasmparser` itself. I've tried to leave an explanatory comment for myself in the future which can also hopefully help serve others as well. Along the way I've also managed to remove `ValType::is_valtype_byte` with some simpler logic to avoid duplication of all the value type bytes that are supported. --- crates/wasmparser/src/binary_reader.rs | 29 +++++-- crates/wasmparser/src/readers/core/types.rs | 93 ++++++++++++++++----- 2 files changed, 92 insertions(+), 30 deletions(-) diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs index 99dfca38e0..4df0029072 100644 --- a/crates/wasmparser/src/binary_reader.rs +++ b/crates/wasmparser/src/binary_reader.rs @@ -732,14 +732,27 @@ impl<'a> BinaryReader<'a> { pub(crate) fn read_block_type(&mut self) -> Result { let b = self.peek()?; - // Check for empty block - if b == 0x40 { - self.position += 1; - return Ok(BlockType::Empty); - } - - // Check for a block type of form [] -> [t]. - if ValType::is_valtype_byte(b) { + // Block types are encoded as either 0x40, a `valtype`, or `s33`. All + // current `valtype` encodings are negative numbers when encoded with + // sleb128, but it's also required that valtype encodings are in their + // canonical form. For example an overlong encoding of -1 as `0xff 0x7f` + // is not valid and it is required to be `0x7f`. This means that we + // can't simply match on the `s33` that pops out below since reading the + // whole `s33` might read an overlong encoding. + // + // To test for this the first byte `b` is inspected. The highest bit, + // the continuation bit in LEB128 encoding, must be clear. The next bit, + // the sign bit, must be set to indicate that the number is negative. If + // these two conditions hold then we're guaranteed that this is a + // negative number. + // + // After this a value type is read directly instead of looking for an + // indexed value type. + if b & 0x80 == 0 && b & 0x40 != 0 { + if b == 0x40 { + self.position += 1; + return Ok(BlockType::Empty); + } return Ok(BlockType::Type(self.read()?)); } diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs index 3297a26928..8df5c03d2b 100644 --- a/crates/wasmparser/src/readers/core/types.rs +++ b/crates/wasmparser/src/readers/core/types.rs @@ -1369,18 +1369,10 @@ pub enum HeapType { NoExn, } -impl ValType { - pub(crate) fn is_valtype_byte(byte: u8) -> bool { - match byte { - 0x7F | 0x7E | 0x7D | 0x7C | 0x7B | 0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 - | 0x74 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C | 0x69 => true, - _ => false, - } - } -} - impl<'a> FromReader<'a> for StorageType { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + // NB: See `FromReader<'a> for ValType` for a table of how this + // interacts with other value encodings. match reader.peek()? { 0x78 => { reader.read_u8()?; @@ -1397,6 +1389,53 @@ impl<'a> FromReader<'a> for StorageType { impl<'a> FromReader<'a> for ValType { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + // Decoding value types is sort of subtle because the space of what's + // being decoded here is actually spread out across an number of + // locations. This comment here is intended to serve as a bit of a + // reference to what's being decoded here and how it interacts with + // other locations. + // + // Note that all value types are encoded as canonical-form negative + // numbers in the sleb128 encoding scheme. Currently in the wasm spec + // sleb128 isn't actually used but it looks to be modelled to allow it + // one day. In the meantime the current values used are: + // + // | sleb128 | decimal | type | notes | + // |---------|---------|--------------|------------------------------| + // | 0x7F | -1 | i32 | | + // | 0x7E | -2 | i64 | | + // | 0x7D | -3 | f32 | | + // | 0x7C | -4 | f64 | | + // | 0x7B | -5 | v128 | simd proposal | + // | 0x78 | -8 | i8 | gc proposal, in `FieldType` | + // | 0x77 | -9 | i16 | gc proposal, in `FieldType` | + // | 0x74 | -12 | noexn | gc + exceptions proposal | + // | 0x73 | -13 | nofunc | gc proposal | + // | 0x72 | -14 | noextern | gc proposal | + // | 0x71 | -15 | nullref | gc proposal | + // | 0x70 | -16 | func | reference types proposal | + // | 0x6F | -17 | extern | reference types proposal | + // | 0x6E | -18 | any | gc proposal | + // | 0x6D | -19 | eq | gc proposal | + // | 0x6C | -20 | i31 | gc proposal | + // | 0x6B | -21 | struct | gc proposal | + // | 0x6A | -22 | array | gc proposal | + // | 0x69 | -23 | exnref | gc + exceptions proposal | + // | 0x64 | -28 | ref $t | gc proposal, prefix byte | + // | 0x63 | -29 | ref null $t | gc proposal, prefix byte | + // | 0x60 | -32 | func $t | prefix byte | + // | 0x5f | -33 | struct $t | gc proposal, prefix byte | + // | 0x5e | -34 | array $t | gc proposal, prefix byte | + // | 0x50 | -48 | sub $t | gc proposal, prefix byte | + // | 0x4F | -49 | sub final $t | gc proposal, prefix byte | + // | 0x4E | -50 | rec $t | gc proposal, prefix byte | + // | 0x40 | -64 | ε | empty block type | + // + // Note that not all of these encodings are parsed here, for example + // 0x78 as the encoding for `i8` is parsed only in `FieldType`. The + // parsing of `FieldType` will delegate here without actually consuming + // anything though so the encoding 0x78 still must be disjoint and not + // read here otherwise. match reader.peek()? { 0x7F => { reader.read_u8()?; @@ -1427,25 +1466,27 @@ impl<'a> FromReader<'a> for ValType { impl<'a> FromReader<'a> for RefType { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + // NB: See `FromReader<'a> for ValType` for a table of how this + // interacts with other value encodings. match reader.read()? { - 0x70 => Ok(RefType::FUNC.nullable()), - 0x6F => Ok(RefType::EXTERN.nullable()), - 0x6E => Ok(RefType::ANY.nullable()), - 0x71 => Ok(RefType::NONE.nullable()), - 0x72 => Ok(RefType::NOEXTERN.nullable()), - 0x73 => Ok(RefType::NOFUNC.nullable()), - 0x6D => Ok(RefType::EQ.nullable()), - 0x6B => Ok(RefType::STRUCT.nullable()), - 0x6A => Ok(RefType::ARRAY.nullable()), - 0x6C => Ok(RefType::I31.nullable()), - 0x69 => Ok(RefType::EXN.nullable()), - 0x74 => Ok(RefType::NOEXN.nullable()), byte @ (0x63 | 0x64) => { let nullable = byte == 0x63; let pos = reader.original_position(); RefType::new(nullable, reader.read()?) .ok_or_else(|| crate::BinaryReaderError::new("type index too large", pos)) } + 0x69 => Ok(RefType::EXN.nullable()), + 0x6A => Ok(RefType::ARRAY.nullable()), + 0x6B => Ok(RefType::STRUCT.nullable()), + 0x6C => Ok(RefType::I31.nullable()), + 0x6D => Ok(RefType::EQ.nullable()), + 0x6E => Ok(RefType::ANY.nullable()), + 0x6F => Ok(RefType::EXTERN.nullable()), + 0x70 => Ok(RefType::FUNC.nullable()), + 0x71 => Ok(RefType::NONE.nullable()), + 0x72 => Ok(RefType::NOEXTERN.nullable()), + 0x73 => Ok(RefType::NOFUNC.nullable()), + 0x74 => Ok(RefType::NOEXN.nullable()), _ => bail!(reader.original_position(), "malformed reference type"), } } @@ -1453,6 +1494,8 @@ impl<'a> FromReader<'a> for RefType { impl<'a> FromReader<'a> for HeapType { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + // NB: See `FromReader<'a> for ValType` for a table of how this + // interacts with other value encodings. match reader.peek()? { 0x70 => { reader.read_u8()?; @@ -1669,6 +1712,8 @@ fn read_composite_type( opcode: u8, reader: &mut BinaryReader, ) -> Result { + // NB: See `FromReader<'a> for ValType` for a table of how this + // interacts with other value encodings. Ok(match opcode { 0x60 => CompositeType::Func(reader.read()?), 0x5e => CompositeType::Array(reader.read()?), @@ -1679,6 +1724,8 @@ fn read_composite_type( impl<'a> FromReader<'a> for RecGroup { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + // NB: See `FromReader<'a> for ValType` for a table of how this + // interacts with other value encodings. match reader.peek()? { 0x4e => { reader.read_u8()?; @@ -1702,6 +1749,8 @@ impl<'a> FromReader<'a> for RecGroup { impl<'a> FromReader<'a> for SubType { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { let pos = reader.original_position(); + // NB: See `FromReader<'a> for ValType` for a table of how this + // interacts with other value encodings. Ok(match reader.read_u8()? { opcode @ (0x4f | 0x50) => { let idx_iter = reader.read_iter(MAX_WASM_SUPERTYPES, "supertype idxs")?; From 92861f1426d5f820da97797ffc2fe4e4d3acf634 Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Mon, 10 Jun 2024 15:43:56 -0700 Subject: [PATCH 03/58] proof-of-concept playground (#1597) * proof-of-concept playground * make playground part of the workspace * do not use the workspace lints * print * add redirect * color printing * checkbox for printing skeletons * cross-link, also make parse errors red * allow CI to run * I guess we cannot avoid committing this generated file --- .github/workflows/playground.yml | 59 ++ Cargo.lock | 18 + Cargo.toml | 1 + playground/.gitignore | 3 + playground/README.md | 30 + playground/component/.gitignore | 1 + playground/component/Cargo.toml | 24 + playground/component/src/bindings.rs | 306 ++++++++++ playground/component/src/lib.rs | 63 +++ playground/component/wit/world.wit | 16 + playground/package-lock.json | 798 +++++++++++++++++++++++++++ playground/package.json | 17 + playground/pages/index.html | 2 + playground/pages/parse.html | 86 +++ playground/pages/print.html | 115 ++++ playground/pages/style.css | 31 ++ playground/src/parse.ts | 66 +++ playground/src/print.ts | 118 ++++ playground/src/utilities.ts | 16 + playground/src/worker.ts | 35 ++ playground/tsconfig.json | 12 + 21 files changed, 1817 insertions(+) create mode 100644 .github/workflows/playground.yml create mode 100644 playground/.gitignore create mode 100644 playground/README.md create mode 100644 playground/component/.gitignore create mode 100644 playground/component/Cargo.toml create mode 100644 playground/component/src/bindings.rs create mode 100644 playground/component/src/lib.rs create mode 100644 playground/component/wit/world.wit create mode 100644 playground/package-lock.json create mode 100644 playground/package.json create mode 100644 playground/pages/index.html create mode 100644 playground/pages/parse.html create mode 100644 playground/pages/print.html create mode 100644 playground/pages/style.css create mode 100644 playground/src/parse.ts create mode 100644 playground/src/print.ts create mode 100644 playground/src/utilities.ts create mode 100644 playground/src/worker.ts create mode 100644 playground/tsconfig.json diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml new file mode 100644 index 0000000000..c6472c1606 --- /dev/null +++ b/.github/workflows/playground.yml @@ -0,0 +1,59 @@ +name: Build playground +on: + push: + branches: [main] + pull_request: + merge_group: + +# Cancel any in-flight jobs for the same PR/branch so there's only one active +# at a time +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +defaults: + run: + shell: bash + +jobs: + build: + name: Build + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: true + - uses: actions/setup-node@v4 + with: + node-version: 20 + - uses: bytecodealliance/wasmtime/.github/actions/install-rust@v20.0.0 + - uses: cargo-bins/cargo-binstall@v1.6.9 + - run: cargo binstall cargo-component -y + - run: rustup component add rustfmt # needed for cargo-component, apparently? + - run: npm ci + working-directory: playground + - run: npm run build + working-directory: playground + + # also prepare to deploy GH pages on main + - if: github.ref == 'refs/heads/main' + uses: actions/configure-pages@v5 + - if: github.ref == 'refs/heads/main' + uses: actions/upload-pages-artifact@v3 + with: + path: "./playground/dist" + + deploy: + name: Deploy + if: github.ref == 'refs/heads/main' + needs: build + permissions: + pages: write + id-token: write + runs-on: ubuntu-latest + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + steps: + - id: deployment + uses: actions/deploy-pages@v4 diff --git a/Cargo.lock b/Cargo.lock index 7bf57c07e3..b2b6b81c21 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -285,6 +285,15 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +[[package]] +name = "component" +version = "0.0.0" +dependencies = [ + "wasmprinter 0.209.1", + "wat", + "wit-bindgen-rt", +] + [[package]] name = "constant_time_eq" version = "0.3.0" @@ -2095,6 +2104,15 @@ version = "0.52.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bec47e5bfd1bff0eeaf6d8b485cc1074891a197ab4225d504cb7a1ab88b02bf0" +[[package]] +name = "wit-bindgen-rt" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29c7526379ace8709ee9ab9f2bb50f112d95581063a59ef3097d9c10153886c9" +dependencies = [ + "bitflags", +] + [[package]] name = "wit-component" version = "0.209.1" diff --git a/Cargo.toml b/Cargo.toml index 4bf41c45f8..141c5bc4b2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -39,6 +39,7 @@ members = [ 'fuzz', 'crates/wit-parser/fuzz', 'crates/wit-component/dl', + 'playground/component', ] [workspace.lints.rust] diff --git a/playground/.gitignore b/playground/.gitignore new file mode 100644 index 0000000000..06af0ad93f --- /dev/null +++ b/playground/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +component-built/ +dist/ diff --git a/playground/README.md b/playground/README.md new file mode 100644 index 0000000000..6bc44d3462 --- /dev/null +++ b/playground/README.md @@ -0,0 +1,30 @@ +# Playground + +This is a simple online playground for `wasm-tools parse`, available at https://bytecodealliance.gthub.io/wasm-tools/. + +## Building + +In addition to `cargo`, you'll need to have `node` and [`cargo-component`](https://github.com/bytecodealliance/cargo-Component) installed and on your path. + +Then + +```sh +npm ci +``` +to install dependencies and +```sh +npm run build +``` +to build. + +Output will be placed under `dist`. + +## Making changes + +The [./component/wit/world.wit](./component/wit/world.wit) file defines the exported functions for use by the playground. After changing it you'll need to `npm run build` to regenerate the bindings file (the command will fail but still generate the bindings) and then implement the functions in [./component/src/lib.rs](./component/src/lib.rs). + +After `npm run build`ing again you can make use of those functions from [./src/worker.ts](./src/worker.ts), with full type safety (thanks to [`jco`](https://github.com/bytecodealliance/jco)). + +It is also possible to use them outside of a worker, but since they run synchronously and block their thread the playground is designed to do all calls off the main thread. + +To preview your changes after building you'll need to run a web server; the worker will not load from a `file:` URL. The simplest way to do this is by running `npx http-server dist`, but you can use whatever means you'd like. diff --git a/playground/component/.gitignore b/playground/component/.gitignore new file mode 100644 index 0000000000..2f7896d1d1 --- /dev/null +++ b/playground/component/.gitignore @@ -0,0 +1 @@ +target/ diff --git a/playground/component/Cargo.toml b/playground/component/Cargo.toml new file mode 100644 index 0000000000..4a3cd14a13 --- /dev/null +++ b/playground/component/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "component" +publish = false +edition.workspace = true + +[dependencies] +wit-bindgen-rt = { version = "0.26.0", features = ["bitflags"] } +wat = { workspace = true } +wasmprinter = { workspace = true } + +[lib] +crate-type = ["cdylib"] + +[profile.release] +codegen-units = 1 +opt-level = "s" +debug = false +strip = true +lto = true + +[package.metadata.component] +package = "component:component" + +[package.metadata.component.dependencies] diff --git a/playground/component/src/bindings.rs b/playground/component/src/bindings.rs new file mode 100644 index 0000000000..30fe002dc6 --- /dev/null +++ b/playground/component/src/bindings.rs @@ -0,0 +1,306 @@ +// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT! +// Options used: +#[derive(Clone)] +pub enum PrintPart { + Str(_rt::String), + Name, + Literal, + Keyword, + Type, + Comment, + Reset, +} +impl ::core::fmt::Debug for PrintPart { + fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { + match self { + PrintPart::Str(e) => f.debug_tuple("PrintPart::Str").field(e).finish(), + PrintPart::Name => f.debug_tuple("PrintPart::Name").finish(), + PrintPart::Literal => f.debug_tuple("PrintPart::Literal").finish(), + PrintPart::Keyword => f.debug_tuple("PrintPart::Keyword").finish(), + PrintPart::Type => f.debug_tuple("PrintPart::Type").finish(), + PrintPart::Comment => f.debug_tuple("PrintPart::Comment").finish(), + PrintPart::Reset => f.debug_tuple("PrintPart::Reset").finish(), + } + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn _export_parse_cabi(arg0: *mut u8, arg1: usize) -> *mut u8 { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let len0 = arg1; + let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0); + let result1 = T::parse(_rt::string_lift(bytes0)); + let ptr2 = _RET_AREA.0.as_mut_ptr().cast::(); + match result1 { + Ok(e) => { + *ptr2.add(0).cast::() = (0i32) as u8; + let vec3 = (e).into_boxed_slice(); + let ptr3 = vec3.as_ptr().cast::(); + let len3 = vec3.len(); + ::core::mem::forget(vec3); + *ptr2.add(8).cast::() = len3; + *ptr2.add(4).cast::<*mut u8>() = ptr3.cast_mut(); + } + Err(e) => { + *ptr2.add(0).cast::() = (1i32) as u8; + let vec4 = (e.into_bytes()).into_boxed_slice(); + let ptr4 = vec4.as_ptr().cast::(); + let len4 = vec4.len(); + ::core::mem::forget(vec4); + *ptr2.add(8).cast::() = len4; + *ptr2.add(4).cast::<*mut u8>() = ptr4.cast_mut(); + } + }; + ptr2 +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn __post_return_parse(arg0: *mut u8) { + let l0 = i32::from(*arg0.add(0).cast::()); + match l0 { + 0 => { + let l1 = *arg0.add(4).cast::<*mut u8>(); + let l2 = *arg0.add(8).cast::(); + let base3 = l1; + let len3 = l2; + _rt::cabi_dealloc(base3, len3 * 1, 1); + } + _ => { + let l4 = *arg0.add(4).cast::<*mut u8>(); + let l5 = *arg0.add(8).cast::(); + _rt::cabi_dealloc(l4, l5, 1); + } + } +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn _export_print_cabi(arg0: *mut u8, arg1: usize, arg2: i32) -> *mut u8 { + #[cfg(target_arch = "wasm32")] + _rt::run_ctors_once(); + let len0 = arg1; + let result1 = T::print( + _rt::Vec::from_raw_parts(arg0.cast(), len0, len0), + _rt::bool_lift(arg2 as u8), + ); + let ptr2 = _RET_AREA.0.as_mut_ptr().cast::(); + match result1 { + Ok(e) => { + *ptr2.add(0).cast::() = (0i32) as u8; + let vec4 = e; + let len4 = vec4.len(); + let layout4 = _rt::alloc::Layout::from_size_align_unchecked(vec4.len() * 12, 4); + let result4 = if layout4.size() != 0 { + let ptr = _rt::alloc::alloc(layout4).cast::(); + if ptr.is_null() { + _rt::alloc::handle_alloc_error(layout4); + } + ptr + } else { + { + ::core::ptr::null_mut() + } + }; + for (i, e) in vec4.into_iter().enumerate() { + let base = result4.add(i * 12); + { + match e { + PrintPart::Str(e) => { + *base.add(0).cast::() = (0i32) as u8; + let vec3 = (e.into_bytes()).into_boxed_slice(); + let ptr3 = vec3.as_ptr().cast::(); + let len3 = vec3.len(); + ::core::mem::forget(vec3); + *base.add(8).cast::() = len3; + *base.add(4).cast::<*mut u8>() = ptr3.cast_mut(); + } + PrintPart::Name => { + *base.add(0).cast::() = (1i32) as u8; + } + PrintPart::Literal => { + *base.add(0).cast::() = (2i32) as u8; + } + PrintPart::Keyword => { + *base.add(0).cast::() = (3i32) as u8; + } + PrintPart::Type => { + *base.add(0).cast::() = (4i32) as u8; + } + PrintPart::Comment => { + *base.add(0).cast::() = (5i32) as u8; + } + PrintPart::Reset => { + *base.add(0).cast::() = (6i32) as u8; + } + } + } + } + *ptr2.add(8).cast::() = len4; + *ptr2.add(4).cast::<*mut u8>() = result4; + } + Err(e) => { + *ptr2.add(0).cast::() = (1i32) as u8; + let vec5 = (e.into_bytes()).into_boxed_slice(); + let ptr5 = vec5.as_ptr().cast::(); + let len5 = vec5.len(); + ::core::mem::forget(vec5); + *ptr2.add(8).cast::() = len5; + *ptr2.add(4).cast::<*mut u8>() = ptr5.cast_mut(); + } + }; + ptr2 +} +#[doc(hidden)] +#[allow(non_snake_case)] +pub unsafe fn __post_return_print(arg0: *mut u8) { + let l0 = i32::from(*arg0.add(0).cast::()); + match l0 { + 0 => { + let l4 = *arg0.add(4).cast::<*mut u8>(); + let l5 = *arg0.add(8).cast::(); + let base6 = l4; + let len6 = l5; + for i in 0..len6 { + let base = base6.add(i * 12); + { + let l1 = i32::from(*base.add(0).cast::()); + match l1 { + 0 => { + let l2 = *base.add(4).cast::<*mut u8>(); + let l3 = *base.add(8).cast::(); + _rt::cabi_dealloc(l2, l3, 1); + } + 1 => (), + 2 => (), + 3 => (), + 4 => (), + 5 => (), + _ => (), + } + } + } + _rt::cabi_dealloc(base6, len6 * 12, 4); + } + _ => { + let l7 = *arg0.add(4).cast::<*mut u8>(); + let l8 = *arg0.add(8).cast::(); + _rt::cabi_dealloc(l7, l8, 1); + } + } +} +pub trait Guest { + fn parse(contents: _rt::String) -> Result<_rt::Vec, _rt::String>; + fn print(contents: _rt::Vec, skeleton: bool) -> Result<_rt::Vec, _rt::String>; +} +#[doc(hidden)] + +macro_rules! __export_world_wasm_tools_cabi{ + ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { + + #[export_name = "parse"] + unsafe extern "C" fn export_parse(arg0: *mut u8,arg1: usize,) -> *mut u8 { + $($path_to_types)*::_export_parse_cabi::<$ty>(arg0, arg1) + } + #[export_name = "cabi_post_parse"] + unsafe extern "C" fn _post_return_parse(arg0: *mut u8,) { + $($path_to_types)*::__post_return_parse::<$ty>(arg0) + } + #[export_name = "print"] + unsafe extern "C" fn export_print(arg0: *mut u8,arg1: usize,arg2: i32,) -> *mut u8 { + $($path_to_types)*::_export_print_cabi::<$ty>(arg0, arg1, arg2) + } + #[export_name = "cabi_post_print"] + unsafe extern "C" fn _post_return_print(arg0: *mut u8,) { + $($path_to_types)*::__post_return_print::<$ty>(arg0) + } + };); +} +#[doc(hidden)] +pub(crate) use __export_world_wasm_tools_cabi; +#[repr(align(4))] +struct _RetArea([::core::mem::MaybeUninit; 12]); +static mut _RET_AREA: _RetArea = _RetArea([::core::mem::MaybeUninit::uninit(); 12]); +mod _rt { + pub use alloc_crate::string::String; + + #[cfg(target_arch = "wasm32")] + pub fn run_ctors_once() { + wit_bindgen_rt::run_ctors_once(); + } + pub use alloc_crate::vec::Vec; + pub unsafe fn string_lift(bytes: Vec) -> String { + if cfg!(debug_assertions) { + String::from_utf8(bytes).unwrap() + } else { + String::from_utf8_unchecked(bytes) + } + } + pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { + if size == 0 { + return; + } + let layout = alloc::Layout::from_size_align_unchecked(size, align); + alloc::dealloc(ptr as *mut u8, layout); + } + pub unsafe fn bool_lift(val: u8) -> bool { + if cfg!(debug_assertions) { + match val { + 0 => false, + 1 => true, + _ => panic!("invalid bool discriminant"), + } + } else { + val != 0 + } + } + pub use alloc_crate::alloc; + extern crate alloc as alloc_crate; +} + +/// Generates `#[no_mangle]` functions to export the specified type as the +/// root implementation of all generated traits. +/// +/// For more information see the documentation of `wit_bindgen::generate!`. +/// +/// ```rust +/// # macro_rules! export{ ($($t:tt)*) => (); } +/// # trait Guest {} +/// struct MyType; +/// +/// impl Guest for MyType { +/// // ... +/// } +/// +/// export!(MyType); +/// ``` +#[allow(unused_macros)] +#[doc(hidden)] + +macro_rules! __export_wasm_tools_impl { + ($ty:ident) => (self::export!($ty with_types_in self);); + ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( + $($path_to_types_root)*::__export_world_wasm_tools_cabi!($ty with_types_in $($path_to_types_root)*); + ) +} +#[doc(inline)] +pub(crate) use __export_wasm_tools_impl as export; + +#[cfg(target_arch = "wasm32")] +#[link_section = "component-type:wit-bindgen:0.25.0:wasm-tools:encoded world"] +#[doc(hidden)] +pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 326] = *b"\ +\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xc5\x01\x01A\x02\x01\ +A\x0a\x01q\x07\x03str\x01s\0\x04name\0\0\x07literal\0\0\x07keyword\0\0\x04type\0\ +\0\x07comment\0\0\x05reset\0\0\x03\0\x0aprint-part\x03\0\0\x01p}\x01j\x01\x02\x01\ +s\x01@\x01\x08contentss\0\x03\x04\0\x05parse\x01\x04\x01p\x01\x01j\x01\x05\x01s\x01\ +@\x02\x08contents\x02\x08skeleton\x7f\0\x06\x04\0\x05print\x01\x07\x04\x01\x1eco\ +mponent:component/wasm-tools\x04\0\x0b\x10\x01\0\x0awasm-tools\x03\0\0\0G\x09pro\ +ducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.208.1\x10wit-bindgen-rust\x06\ +0.25.0"; + +#[inline(never)] +#[doc(hidden)] +#[cfg(target_arch = "wasm32")] +pub fn __link_custom_section_describing_imports() { + wit_bindgen_rt::maybe_link_cabi_realloc(); +} diff --git a/playground/component/src/lib.rs b/playground/component/src/lib.rs new file mode 100644 index 0000000000..4f493e1e14 --- /dev/null +++ b/playground/component/src/lib.rs @@ -0,0 +1,63 @@ +#[allow(warnings)] +mod bindings; + +use bindings::{Guest, PrintPart}; + +struct Component; + +struct StringWriter(pub Vec); + +impl wasmprinter::Print for StringWriter { + fn write_str(&mut self, s: &str) -> std::io::Result<()> { + self.0.push(PrintPart::Str(s.to_string())); + Ok(()) + } + + fn start_name(&mut self) -> std::io::Result<()> { + self.0.push(PrintPart::Name); + Ok(()) + } + + fn start_literal(&mut self) -> std::io::Result<()> { + self.0.push(PrintPart::Literal); + Ok(()) + } + + fn start_keyword(&mut self) -> std::io::Result<()> { + self.0.push(PrintPart::Keyword); + Ok(()) + } + + fn start_type(&mut self) -> std::io::Result<()> { + self.0.push(PrintPart::Type); + Ok(()) + } + + fn start_comment(&mut self) -> std::io::Result<()> { + self.0.push(PrintPart::Comment); + Ok(()) + } + + fn reset_color(&mut self) -> std::io::Result<()> { + self.0.push(PrintPart::Reset); + Ok(()) + } +} + +impl Guest for Component { + fn parse(contents: String) -> Result, String> { + wat::parse_str(contents).map_err(|e| e.to_string()) + } + + fn print(contents: Vec, skeleton: bool) -> Result, String> { + let mut config = wasmprinter::Config::new(); + config.print_skeleton(skeleton); + + let mut writer = StringWriter(Vec::new()); + let result = config.print(&contents, &mut writer); + + result.map(|_| writer.0).map_err(|e| e.to_string()) + } +} + +bindings::export!(Component with_types_in bindings); diff --git a/playground/component/wit/world.wit b/playground/component/wit/world.wit new file mode 100644 index 0000000000..9d70466af2 --- /dev/null +++ b/playground/component/wit/world.wit @@ -0,0 +1,16 @@ +package component:component; + +world wasm-tools { + export parse: func(contents: string) -> result, string>; + + variant print-part { + str(string), + name, + literal, + keyword, + %type, + comment, + reset, + } + export print: func(contents: list, skeleton: bool) -> result, string>; +} diff --git a/playground/package-lock.json b/playground/package-lock.json new file mode 100644 index 0000000000..5072d59971 --- /dev/null +++ b/playground/package-lock.json @@ -0,0 +1,798 @@ +{ + "name": "playground", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "dependencies": { + "@bytecodealliance/jco": "^1.2.4", + "esbuild": "^0.21.4", + "typescript": "^5.4.5" + } + }, + "node_modules/@bytecodealliance/jco": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@bytecodealliance/jco/-/jco-1.2.4.tgz", + "integrity": "sha512-uuOm9UkYqWp5uElYDNzlhjbdrAmczEvETgQdI1hFTk79h+mrmHPRV32pgRP5o1eHVwMIpuk4XQkDIhFbksurUw==", + "dependencies": { + "@bytecodealliance/preview2-shim": "^0.16.2", + "binaryen": "^116.0.0", + "chalk-template": "^1", + "commander": "^12", + "mkdirp": "^3", + "ora": "^8", + "terser": "^5" + }, + "bin": { + "jco": "src/jco.js" + } + }, + "node_modules/@bytecodealliance/preview2-shim": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/@bytecodealliance/preview2-shim/-/preview2-shim-0.16.2.tgz", + "integrity": "sha512-36MwesmbLSf3Y5/OHcS85iBaF0N92CQ4gpjtDVKSbrjxmrBKCWlWVfoQ03F/cqDg8k5K7pzVaVBH0XBIbTCfTQ==" + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.4.tgz", + "integrity": "sha512-Zrm+B33R4LWPLjDEVnEqt2+SLTATlru1q/xYKVn8oVTbiRBGmK2VIMoIYGJDGyftnGaC788IuzGFAlb7IQ0Y8A==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.4.tgz", + "integrity": "sha512-E7H/yTd8kGQfY4z9t3nRPk/hrhaCajfA3YSQSBrst8B+3uTcgsi8N+ZWYCaeIDsiVs6m65JPCaQN/DxBRclF3A==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.4.tgz", + "integrity": "sha512-fYFnz+ObClJ3dNiITySBUx+oNalYUT18/AryMxfovLkYWbutXsct3Wz2ZWAcGGppp+RVVX5FiXeLYGi97umisA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.4.tgz", + "integrity": "sha512-mDqmlge3hFbEPbCWxp4fM6hqq7aZfLEHZAKGP9viq9wMUBVQx202aDIfc3l+d2cKhUJM741VrCXEzRFhPDKH3Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.4.tgz", + "integrity": "sha512-72eaIrDZDSiWqpmCzVaBD58c8ea8cw/U0fq/PPOTqE3c53D0xVMRt2ooIABZ6/wj99Y+h4ksT/+I+srCDLU9TA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.4.tgz", + "integrity": "sha512-uBsuwRMehGmw1JC7Vecu/upOjTsMhgahmDkWhGLWxIgUn2x/Y4tIwUZngsmVb6XyPSTXJYS4YiASKPcm9Zitag==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.4.tgz", + "integrity": "sha512-8JfuSC6YMSAEIZIWNL3GtdUT5NhUA/CMUCpZdDRolUXNAXEE/Vbpe6qlGLpfThtY5NwXq8Hi4nJy4YfPh+TwAg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.4.tgz", + "integrity": "sha512-8d9y9eQhxv4ef7JmXny7591P/PYsDFc4+STaxC1GBv0tMyCdyWfXu2jBuqRsyhY8uL2HU8uPyscgE2KxCY9imQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.4.tgz", + "integrity": "sha512-2rqFFefpYmpMs+FWjkzSgXg5vViocqpq5a1PSRgT0AvSgxoXmGF17qfGAzKedg6wAwyM7UltrKVo9kxaJLMF/g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.4.tgz", + "integrity": "sha512-/GLD2orjNU50v9PcxNpYZi+y8dJ7e7/LhQukN3S4jNDXCKkyyiyAz9zDw3siZ7Eh1tRcnCHAo/WcqKMzmi4eMQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.4.tgz", + "integrity": "sha512-pNftBl7m/tFG3t2m/tSjuYeWIffzwAZT9m08+9DPLizxVOsUl8DdFzn9HvJrTQwe3wvJnwTdl92AonY36w/25g==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.4.tgz", + "integrity": "sha512-cSD2gzCK5LuVX+hszzXQzlWya6c7hilO71L9h4KHwqI4qeqZ57bAtkgcC2YioXjsbfAv4lPn3qe3b00Zt+jIfQ==", + "cpu": [ + "loong64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.4.tgz", + "integrity": "sha512-qtzAd3BJh7UdbiXCrg6npWLYU0YpufsV9XlufKhMhYMJGJCdfX/G6+PNd0+v877X1JG5VmjBLUiFB0o8EUSicA==", + "cpu": [ + "mips64el" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.4.tgz", + "integrity": "sha512-yB8AYzOTaL0D5+2a4xEy7OVvbcypvDR05MsB/VVPVA7nL4hc5w5Dyd/ddnayStDgJE59fAgNEOdLhBxjfx5+dg==", + "cpu": [ + "ppc64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.4.tgz", + "integrity": "sha512-Y5AgOuVzPjQdgU59ramLoqSSiXddu7F3F+LI5hYy/d1UHN7K5oLzYBDZe23QmQJ9PIVUXwOdKJ/jZahPdxzm9w==", + "cpu": [ + "riscv64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.4.tgz", + "integrity": "sha512-Iqc/l/FFwtt8FoTK9riYv9zQNms7B8u+vAI/rxKuN10HgQIXaPzKZc479lZ0x6+vKVQbu55GdpYpeNWzjOhgbA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.4.tgz", + "integrity": "sha512-Td9jv782UMAFsuLZINfUpoF5mZIbAj+jv1YVtE58rFtfvoKRiKSkRGQfHTgKamLVT/fO7203bHa3wU122V/Bdg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.4.tgz", + "integrity": "sha512-Awn38oSXxsPMQxaV0Ipb7W/gxZtk5Tx3+W+rAPdZkyEhQ6968r9NvtkjhnhbEgWXYbgV+JEONJ6PcdBS+nlcpA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.4.tgz", + "integrity": "sha512-IsUmQeCY0aU374R82fxIPu6vkOybWIMc3hVGZ3ChRwL9hA1TwY+tS0lgFWV5+F1+1ssuvvXt3HFqe8roCip8Hg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.4.tgz", + "integrity": "sha512-hsKhgZ4teLUaDA6FG/QIu2q0rI6I36tZVfM4DBZv3BG0mkMIdEnMbhc4xwLvLJSS22uWmaVkFkqWgIS0gPIm+A==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.4.tgz", + "integrity": "sha512-UUfMgMoXPoA/bvGUNfUBFLCh0gt9dxZYIx9W4rfJr7+hKe5jxxHmfOK8YSH4qsHLLN4Ck8JZ+v7Q5fIm1huErg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.4.tgz", + "integrity": "sha512-yIxbspZb5kGCAHWm8dexALQ9en1IYDfErzjSEq1KzXFniHv019VT3mNtTK7t8qdy4TwT6QYHI9sEZabONHg+aw==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.4.tgz", + "integrity": "sha512-sywLRD3UK/qRJt0oBwdpYLBibk7KiRfbswmWRDabuncQYSlf8aLEEUor/oP6KRz8KEG+HoiVLBhPRD5JWjS8Sg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz", + "integrity": "sha512-IzL8ZoEDIBRWEzlCcRhOaCupYyN5gdIK+Q6fbFdPDg6HqX6jpkItn7DFIpW9LQzXG6Df9sA7+OKnq0qlz/GaQg==", + "dependencies": { + "@jridgewell/set-array": "^1.2.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.24" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.2.1.tgz", + "integrity": "sha512-R8gLRTZeyp03ymzP/6Lil/28tGeGEzhx1q2k703KGWRAI1VdvPIXdG70VJc2pAMw3NA6JKL5hhFu1sJX0Mnn/A==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.6.tgz", + "integrity": "sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.25" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.25", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz", + "integrity": "sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/acorn": { + "version": "8.11.3", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz", + "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/binaryen": { + "version": "116.0.0", + "resolved": "https://registry.npmjs.org/binaryen/-/binaryen-116.0.0.tgz", + "integrity": "sha512-Hp0dXC6Cb/rTwWEoUS2BRghObE7g/S9umKtxuTDt3f61G6fNTE/YVew/ezyy3IdHcLx3f17qfh6LwETgCfvWkQ==", + "bin": { + "wasm-opt": "bin/wasm-opt", + "wasm2js": "bin/wasm2js" + } + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" + }, + "node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk-template": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/chalk-template/-/chalk-template-1.1.0.tgz", + "integrity": "sha512-T2VJbcDuZQ0Tb2EWwSotMPJjgpy1/tGee1BTpUNsGZ/qgNjV2t7Mvu+d4600U564nbLesN1x2dPL+xii174Ekg==", + "dependencies": { + "chalk": "^5.2.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/chalk/chalk-template?sponsor=1" + } + }, + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", + "dependencies": { + "restore-cursor": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "engines": { + "node": ">=18" + } + }, + "node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==" + }, + "node_modules/esbuild": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.4.tgz", + "integrity": "sha512-sFMcNNrj+Q0ZDolrp5pDhH0nRPN9hLIM3fRPwgbLYJeSHHgnXSnbV3xYgSVuOeLWH9c73VwmEverVzupIv5xuA==", + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.4", + "@esbuild/android-arm": "0.21.4", + "@esbuild/android-arm64": "0.21.4", + "@esbuild/android-x64": "0.21.4", + "@esbuild/darwin-arm64": "0.21.4", + "@esbuild/darwin-x64": "0.21.4", + "@esbuild/freebsd-arm64": "0.21.4", + "@esbuild/freebsd-x64": "0.21.4", + "@esbuild/linux-arm": "0.21.4", + "@esbuild/linux-arm64": "0.21.4", + "@esbuild/linux-ia32": "0.21.4", + "@esbuild/linux-loong64": "0.21.4", + "@esbuild/linux-mips64el": "0.21.4", + "@esbuild/linux-ppc64": "0.21.4", + "@esbuild/linux-riscv64": "0.21.4", + "@esbuild/linux-s390x": "0.21.4", + "@esbuild/linux-x64": "0.21.4", + "@esbuild/netbsd-x64": "0.21.4", + "@esbuild/openbsd-x64": "0.21.4", + "@esbuild/sunos-x64": "0.21.4", + "@esbuild/win32-arm64": "0.21.4", + "@esbuild/win32-ia32": "0.21.4", + "@esbuild/win32-x64": "0.21.4" + } + }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/is-unicode-supported": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-2.0.0.tgz", + "integrity": "sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-6.0.0.tgz", + "integrity": "sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==", + "dependencies": { + "chalk": "^5.3.0", + "is-unicode-supported": "^1.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/mkdirp": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-3.0.1.tgz", + "integrity": "sha512-+NsyUUAZDmo6YVHzL/stxSu3t9YS1iljliy3BSDrXJ/dkn1KYdmtZODGGjLcc9XLgVVpH4KshHB8XmZgMhaBXg==", + "bin": { + "mkdirp": "dist/cjs/src/bin.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-8.0.1.tgz", + "integrity": "sha512-ANIvzobt1rls2BDny5fWZ3ZVKyD6nscLvfFRpQgfWsythlcsVUC9kL0zq6j2Z5z9wwp1kd7wpsD/T9qNPVLCaQ==", + "dependencies": { + "chalk": "^5.3.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.9.2", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^2.0.0", + "log-symbols": "^6.0.0", + "stdin-discarder": "^0.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/stdin-discarder": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.2.2.tgz", + "integrity": "sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.1.0.tgz", + "integrity": "sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/terser": { + "version": "5.31.1", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.31.1.tgz", + "integrity": "sha512-37upzU1+viGvuFtBo9NPufCb9dwM0+l9hMxYyWfBA+fbwrPqNJAhbZ6W47bBFnZHKHTUBnMvi87434qq+qnxOg==", + "dependencies": { + "@jridgewell/source-map": "^0.3.3", + "acorn": "^8.8.2", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" + }, + "bin": { + "terser": "bin/terser" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/terser/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==" + }, + "node_modules/typescript": { + "version": "5.4.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", + "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + } + } +} diff --git a/playground/package.json b/playground/package.json new file mode 100644 index 0000000000..165eede112 --- /dev/null +++ b/playground/package.json @@ -0,0 +1,17 @@ +{ + "type": "module", + "scripts": { + "build": "npm run build-component && npm run transpile && npm run typecheck && npm run bundle && npm run copy-files", + "build-component": "cd component && cargo component build --release --target wasm32-wasi", + "bundle": "for source in worker.ts parse.ts print.ts; do esbuild --bundle --format=esm src/$source --outdir=dist; done", + "copy-files": "cp pages/* component-built/*.wasm dist", + "clean": "rm -rf component/src/bindings.rs component-built dist && echo '## you will need to run `cargo clean` seperately to delete cargo artifacts'", + "transpile": "jco transpile --no-nodejs-compat ../target/wasm32-wasi/release/component.wasm --out-dir component-built", + "typecheck": "tsc" + }, + "dependencies": { + "@bytecodealliance/jco": "^1.2.4", + "esbuild": "^0.21.4", + "typescript": "^5.4.5" + } +} diff --git a/playground/pages/index.html b/playground/pages/index.html new file mode 100644 index 0000000000..38c39b7534 --- /dev/null +++ b/playground/pages/index.html @@ -0,0 +1,2 @@ + + diff --git a/playground/pages/parse.html b/playground/pages/parse.html new file mode 100644 index 0000000000..ae17c56418 --- /dev/null +++ b/playground/pages/parse.html @@ -0,0 +1,86 @@ + + + + + wasm-tools parse + + + + + +
+

wasm-tools parse

+
+ +
+
+

Translate from the WebAssembly text format to binary using wasm-tools parse. + You can go the other direction using print.

+
+
wat
+
+ +
+ +
+ wasm +
+
+ +
+
diff --git a/playground/pages/print.html b/playground/pages/print.html new file mode 100644 index 0000000000..c16657bdb3 --- /dev/null +++ b/playground/pages/print.html @@ -0,0 +1,115 @@ + + + + + wasm-tools print + + + + + +
+

wasm-tools print

+
+ +
Drop .wasm file here
+ +
+
+

Translate WebAssembly binaries to text format using wasm-tools print. + You can go the other direction using parse.

+
+ +
+ Drop a file on this page, or . + + + + + +
+
+ +

+  
+
diff --git a/playground/pages/style.css b/playground/pages/style.css new file mode 100644 index 0000000000..223c12192d --- /dev/null +++ b/playground/pages/style.css @@ -0,0 +1,31 @@ +:root { + --text-color: #404040; +} + +html { + height: 100%; +} + +body { + font-family: system,-apple-system,system-ui,BlinkMacSystemFont,sans-serif; + color: var(--text-color); + font-size: 1.2rem; + line-height: 1.3; + height: calc(100% - 16px - .4rem); + display: flex; + flex-direction: column; + padding: .4rem +} + +header { + text-align: center; +} + +h1 { + margin: 0px; +} + +a { + color: var(--text-color); +} + diff --git a/playground/src/parse.ts b/playground/src/parse.ts new file mode 100644 index 0000000000..2d0618e41b --- /dev/null +++ b/playground/src/parse.ts @@ -0,0 +1,66 @@ +import { type MessageToWorker, debounce } from './utilities.js'; + +// TODO replace this function once https://github.com/tc39/proposal-arraybuffer-base64 ships +function base64(bytes: Uint8Array) { + // @ts-expect-error it's fine to pass a Uint8Array here + return btoa(String.fromCharCode.apply(null, bytes)); +} + +let input: HTMLTextAreaElement = document.querySelector('#input')!; +let output: HTMLTextAreaElement = document.querySelector('#output')!; +let downloadButton: HTMLButtonElement = document.querySelector('#download')!; + +let worker = new Worker('worker.js', { type: 'module' }); + +let lastBytes: Uint8Array; +let sent = ''; +let statusMessageTimeout = 0; + +// we can't just postmessage to the worker right away because it has a top-level await +// so we need to wait for it to tell us it's ready +worker.addEventListener('message', () => { + worker.addEventListener('message', gotMessage); + update(); +}, { once: true }); + +function gotMessage({ data }: { data: { success: true, bytes: Uint8Array } | { success: false, error: string }}) { + clearTimeout(statusMessageTimeout); + if (input.value === sent) { + if (data.success) { + lastBytes = data.bytes; + output.value = base64(lastBytes); + downloadButton.disabled = false; + output.style.color = ''; + } else { + output.value = data.error; + downloadButton.disabled = true; + output.style.color = '#a00'; + } + } else { + // the user has already moved on, ignore this result and re-run + update(); + } +} + +function update() { + sent = input.value; + worker.postMessage({ kind: 'parse', source: input.value } satisfies MessageToWorker); + + // wait a bit before updating the output to avoid rapid changes when the output can be parsed quickly + statusMessageTimeout = setTimeout(() => { + output.value = 'working...'; + downloadButton.disabled = true; + }, 200); +} + +input.addEventListener('input', debounce(update, 100)); + +downloadButton.addEventListener('click', () => { + let blob = new Blob([lastBytes], { type: 'application/wasm' }); + let link = document.createElement('a'); + let url = URL.createObjectURL(blob); + link.href = url; + link.download = 'wasm-tools-parse-output.wasm'; + link.click(); + URL.revokeObjectURL(url); +}); diff --git a/playground/src/print.ts b/playground/src/print.ts new file mode 100644 index 0000000000..7b22e07a52 --- /dev/null +++ b/playground/src/print.ts @@ -0,0 +1,118 @@ +import type { MessageToWorker } from './utilities.js'; +import type { PrintPart } from '../component-built/component.js'; + +function unbase64(chars: string) { + return new Uint8Array(Array.from(atob(chars), c => c.charCodeAt(0))) +} + +let overlay: HTMLDivElement = document.querySelector('#overlay')!; +let upload: HTMLButtonElement = document.querySelector('#upload')!; +let fileInput: HTMLInputElement = document.querySelector('#fileInput')!; +let output: HTMLPreElement = document.querySelector('#output')!; +let skeletonCheckbox: HTMLInputElement = document.querySelector('#skeleton')!; + +let lastTarget: EventTarget | null; +let hideOverlayTimer: number; +addEventListener('dragover', e => { + e.preventDefault(); // necessary to make the drop event work, for some reason + lastTarget = e.target; + overlay.style.display = 'flex'; + clearTimeout(hideOverlayTimer); +}); + +addEventListener('dragleave', e => { + e.preventDefault(); + if (e.target === document || e.target === lastTarget) { + // we need a timer here because otherwise the overlay flashes while moving the mouse around the page + hideOverlayTimer = setTimeout(() => { + overlay.style.display = 'none'; + }, 10); + } +}); + +addEventListener('drop', (e: DragEvent) => { + e.preventDefault(); + overlay.style.display = 'none'; + let file = e.dataTransfer?.files[0]; + if (!file) return; + print(file.arrayBuffer()); +}, { capture: true }); + +let messageId = 0; +async function print(f: Promise) { + output.innerHTML = 'working...'; + let skeleton = skeletonCheckbox.checked; + let bytes = new Uint8Array(await f); + worker.postMessage({ kind: 'print', messageId, bytes, skeleton } satisfies MessageToWorker, [bytes.buffer]); + ++messageId; +} + +let worker = new Worker('worker.js', { type: 'module' }); + +// we can't just postmessage to the worker right away because it has a top-level await +// so we need to wait for it to tell us it's ready +worker.addEventListener('message', () => { + worker.addEventListener('message', gotMessage); + print(Promise.resolve(unbase64('AGFzbQEAAAABDQNgAX8Bf2ABfQBgAAACCwEDZm9vA2JhcgABAwMCAgEEBQFwAQABBQQBAQEBBwUBAWUAAQgBAQoKAgIACwUAQSoaCwsIAQBBAAsCaGk=').buffer)); +}, { once: true },); + +upload.addEventListener('click', () => { + fileInput.click(); + fileInput.onchange = () => { + let file = fileInput.files?.[0]; + if (!file) return; + print(file.arrayBuffer()); + } +}); + +function gotMessage({ data }: { + data: { messageId: number; success: true; source: PrintPart[] } | { messageId: number; success: false; error: string }; +}) { + if (data.messageId !== messageId - 1) { + // user has already asked for a new file + return; + } + output.innerHTML = ''; + if (data.success) { + output.append(toDOM(data.source)); + } else { + let span = document.createElement('span'); + span.style.color = '#a00'; + span.append(data.error); + output.append(span); + } +} + +function toDOM(parts: PrintPart[]) { + let root = document.createElement('span'); + let stack = [root]; + for (let part of parts) { + switch (part.tag) { + case 'str': { + stack[0].append(part.val); + break; + } + case 'name': + case 'literal': + case 'keyword': + case 'type': + case 'comment': { + let ele = document.createElement('span'); + ele.className = 'print-' + part.tag; + stack[0].append(ele); + stack.unshift(ele); + break; + } + case 'reset': { + stack.shift(); + break; + } + default: { + // @ts-expect-error the above should be exhaustive + type remaining = typeof part.tag; + console.error('got unknown print part', part); + } + } + } + return root; +} diff --git a/playground/src/utilities.ts b/playground/src/utilities.ts new file mode 100644 index 0000000000..074c4a1802 --- /dev/null +++ b/playground/src/utilities.ts @@ -0,0 +1,16 @@ +export type MessageToWorker = + | { kind: 'parse'; source: string } + | { kind: 'print'; messageId: number; bytes: Uint8Array, skeleton: boolean }; + +export function debounce(f: (...args: unknown[]) => void, ms: number) { + let timeout: number | null; + return function (this: unknown, ...args: unknown[]) { + let fresh = timeout == null; + if (!fresh) clearTimeout(timeout!); + timeout = setTimeout(() => { + timeout = null; + if (!fresh) f.apply(this, args); + }, ms); + if (fresh) f.apply(this, args); + }; +} diff --git a/playground/src/worker.ts b/playground/src/worker.ts new file mode 100644 index 0000000000..32336bce64 --- /dev/null +++ b/playground/src/worker.ts @@ -0,0 +1,35 @@ +import { parse, print } from '../component-built/component.js'; +import type { MessageToWorker } from './utilities.js' + +// workaround for https://github.com/Microsoft/TypeScript/issues/20595 +declare function postMessage(message: any, transfer?: Transferable[]): void; + +postMessage('loaded'); + +addEventListener('message', ({ data }: { data: MessageToWorker }) => { + switch (data.kind) { + case 'parse': { + try { + let bytes = parse(data.source); + postMessage({ success: true, bytes }, [bytes.buffer]); + } catch (e) { + postMessage({ success: false, error: (e as Error).message }); + } + return; + } + case 'print': { + try { + let source = print(data.bytes, data.skeleton); + postMessage({ success: true, messageId: data.messageId, source }); + } catch (e) { + postMessage({ success: false, messageId: data.messageId, error: (e as Error).message }); + } + return; + } + default: { + // @ts-expect-error the above should be exhaustive + type remaining = typeof data.kind; + console.error('got unknown message', data); + } + } +}); diff --git a/playground/tsconfig.json b/playground/tsconfig.json new file mode 100644 index 0000000000..c93177b190 --- /dev/null +++ b/playground/tsconfig.json @@ -0,0 +1,12 @@ +{ + "include": ["src/**/*"], + "compilerOptions": { + "target": "es2022", + "module": "Node16", + "moduleResolution": "Node16", + "strict": true, + "verbatimModuleSyntax": true, + "noEmit": true, + "lib": ["es2022", "dom", "dom.iterable"], + } +} \ No newline at end of file From 976ce0a4a1cc127aacb733daf4658a933868e093 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 23:02:01 +0000 Subject: [PATCH 04/58] Release wasm-tools 1.210.0 (#1604) [automatically-tag-and-release-this-commit] Co-authored-by: Auto Release Process --- Cargo.lock | 108 ++++++++++++++++++++--------------------- Cargo.toml | 30 ++++++------ crates/wast/Cargo.toml | 2 +- crates/wat/Cargo.toml | 2 +- 4 files changed, 71 insertions(+), 71 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index b2b6b81c21..6d25cdff74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -289,7 +289,7 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" name = "component" version = "0.0.0" dependencies = [ - "wasmprinter 0.209.1", + "wasmprinter 0.210.0", "wat", "wit-bindgen-rt", ] @@ -1489,7 +1489,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-compose" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "glob", @@ -1503,9 +1503,9 @@ dependencies = [ "serde_derive", "serde_yaml", "smallvec", - "wasm-encoder 0.209.1", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasm-encoder 0.210.0", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wat", "wit-component", ] @@ -1521,17 +1521,17 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "leb128", "tempfile", - "wasmparser 0.209.1", + "wasmparser 0.210.0", ] [[package]] name = "wasm-metadata" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "clap", @@ -1540,14 +1540,14 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.209.1", - "wasmparser 0.209.1", + "wasm-encoder 0.210.0", + "wasmparser 0.210.0", "wat", ] [[package]] name = "wasm-mutate" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "clap", @@ -1556,9 +1556,9 @@ dependencies = [ "log", "rand", "thiserror", - "wasm-encoder 0.209.1", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasm-encoder 0.210.0", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wat", ] @@ -1575,14 +1575,14 @@ dependencies = [ "num_cpus", "rand", "wasm-mutate", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wasmtime", ] [[package]] name = "wasm-shrink" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "blake3", @@ -1591,14 +1591,14 @@ dependencies = [ "log", "rand", "wasm-mutate", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wat", ] [[package]] name = "wasm-smith" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "arbitrary", @@ -1611,15 +1611,15 @@ dependencies = [ "rand", "serde", "serde_derive", - "wasm-encoder 0.209.1", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasm-encoder 0.210.0", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wat", ] [[package]] name = "wasm-tools" -version = "1.209.1" +version = "1.210.0" dependencies = [ "addr2line", "anyhow", @@ -1643,17 +1643,17 @@ dependencies = [ "tempfile", "termcolor", "wasm-compose", - "wasm-encoder 0.209.1", + "wasm-encoder 0.210.0", "wasm-metadata", "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wast", "wat", "wit-component", - "wit-parser 0.209.1", + "wit-parser 0.210.0", "wit-smith", ] @@ -1665,8 +1665,8 @@ dependencies = [ "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wast", "wat", ] @@ -1681,16 +1681,16 @@ dependencies = [ "libfuzzer-sys", "log", "tempfile", - "wasm-encoder 0.209.1", + "wasm-encoder 0.210.0", "wasm-mutate", "wasm-smith", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wasmtime", "wast", "wat", "wit-component", - "wit-parser 0.209.1", + "wit-parser 0.210.0", "wit-smith", ] @@ -1707,7 +1707,7 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.209.1" +version = "0.210.0" dependencies = [ "ahash", "anyhow", @@ -1721,7 +1721,7 @@ dependencies = [ "rayon", "semver", "serde", - "wasm-encoder 0.209.1", + "wasm-encoder 0.210.0", "wast", "wat", ] @@ -1738,14 +1738,14 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "diff", "rayon", "tempfile", "termcolor", - "wasmparser 0.209.1", + "wasmparser 0.210.0", "wast", "wat", ] @@ -1963,7 +1963,7 @@ dependencies = [ [[package]] name = "wast" -version = "209.0.1" +version = "210.0.0" dependencies = [ "anyhow", "bumpalo", @@ -1971,14 +1971,14 @@ dependencies = [ "libtest-mimic", "memchr", "unicode-width", - "wasm-encoder 0.209.1", - "wasmparser 0.209.1", + "wasm-encoder 0.210.0", + "wasmparser 0.210.0", "wat", ] [[package]] name = "wat" -version = "1.209.1" +version = "1.210.0" dependencies = [ "wast", ] @@ -2115,7 +2115,7 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "bitflags", @@ -2128,14 +2128,14 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.209.1", + "wasm-encoder 0.210.0", "wasm-metadata", - "wasmparser 0.209.1", - "wasmprinter 0.209.1", + "wasmparser 0.210.0", + "wasmprinter 0.210.0", "wasmtime", "wast", "wat", - "wit-parser 0.209.1", + "wit-parser 0.210.0", ] [[package]] @@ -2158,7 +2158,7 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.209.1" +version = "0.210.0" dependencies = [ "anyhow", "env_logger", @@ -2172,9 +2172,9 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.209.1", + "wasmparser 0.210.0", "wat", - "wit-parser 0.209.1", + "wit-parser 0.210.0", ] [[package]] @@ -2185,13 +2185,13 @@ dependencies = [ "env_logger", "libfuzzer-sys", "log", - "wasmprinter 0.209.1", - "wit-parser 0.209.1", + "wasmprinter 0.210.0", + "wit-parser 0.210.0", ] [[package]] name = "wit-smith" -version = "0.209.1" +version = "0.210.0" dependencies = [ "arbitrary", "clap", @@ -2199,7 +2199,7 @@ dependencies = [ "log", "semver", "wit-component", - "wit-parser 0.209.1", + "wit-parser 0.210.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 141c5bc4b2..ed66749879 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-tools" -version = "1.209.1" +version = "1.210.0" authors = ["The Wasmtime Project Developers"] edition.workspace = true description = "CLI tools for interoperating with WebAssembly files" @@ -50,7 +50,7 @@ all = "allow" [workspace.package] edition = '2021' -version = "0.209.1" +version = "0.210.0" # Current policy for wasm-tools is the same as Wasmtime which is that this # number can be no larger than the current stable release of Rust minus 2. rust-version = "1.76.0" @@ -83,19 +83,19 @@ hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] ahash = { version = "0.8.11", default-features = false } termcolor = "1.2.0" -wasm-compose = { version = "0.209.1", path = "crates/wasm-compose" } -wasm-encoder = { version = "0.209.1", path = "crates/wasm-encoder" } -wasm-metadata = { version = "0.209.1", path = "crates/wasm-metadata" } -wasm-mutate = { version = "0.209.1", path = "crates/wasm-mutate" } -wasm-shrink = { version = "0.209.1", path = "crates/wasm-shrink" } -wasm-smith = { version = "0.209.1", path = "crates/wasm-smith" } -wasmparser = { version = "0.209.1", path = "crates/wasmparser", default-features = false, features = ['std'] } -wasmprinter = { version = "0.209.1", path = "crates/wasmprinter" } -wast = { version = "209.0.1", path = "crates/wast" } -wat = { version = "1.209.1", path = "crates/wat" } -wit-component = { version = "0.209.1", path = "crates/wit-component" } -wit-parser = { version = "0.209.1", path = "crates/wit-parser" } -wit-smith = { version = "0.209.1", path = "crates/wit-smith" } +wasm-compose = { version = "0.210.0", path = "crates/wasm-compose" } +wasm-encoder = { version = "0.210.0", path = "crates/wasm-encoder" } +wasm-metadata = { version = "0.210.0", path = "crates/wasm-metadata" } +wasm-mutate = { version = "0.210.0", path = "crates/wasm-mutate" } +wasm-shrink = { version = "0.210.0", path = "crates/wasm-shrink" } +wasm-smith = { version = "0.210.0", path = "crates/wasm-smith" } +wasmparser = { version = "0.210.0", path = "crates/wasmparser", default-features = false, features = ['std'] } +wasmprinter = { version = "0.210.0", path = "crates/wasmprinter" } +wast = { version = "210.0.0", path = "crates/wast" } +wat = { version = "1.210.0", path = "crates/wat" } +wit-component = { version = "0.210.0", path = "crates/wit-component" } +wit-parser = { version = "0.210.0", path = "crates/wit-parser" } +wit-smith = { version = "0.210.0", path = "crates/wit-smith" } [dependencies] anyhow = { workspace = true } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index 485c20b8f6..296e353f6d 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wast" -version = "209.0.1" +version = "210.0.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml index ade0e261b5..078a8ec3fe 100644 --- a/crates/wat/Cargo.toml +++ b/crates/wat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wat" -version = "1.209.1" +version = "1.210.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" From 5248c7b480f3be5c6b6756df9e474405d63cde6f Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 10 Jun 2024 19:09:22 -0500 Subject: [PATCH 05/58] Minor updates to the playground (#1605) * Fix hyperlink between parse/print online pages Use a relative link instead of an absolute one to handle hosting on different roots * Rename playground jobs Make it a bit more descriptive which workflow they're attached to with just the name of the job * Remove generated `src/bindings.rs` Exclude the crate from testing to avoid building it on other targets. * Remove unused `[profile.release]` section Inherit the default settings for now and if distribution of the wasm is a problem it can be re-added on a per-CI-job-basis. * Exclude the playground from `cargo doc` * Try to fill in missing file for rustfmt * Try a blank line in a new file * Try printf instead --- .github/workflows/main.yml | 1 + .github/workflows/playground.yml | 4 +- playground/.gitignore | 1 + playground/component/Cargo.toml | 10 +- playground/component/src/bindings.rs | 306 --------------------------- playground/pages/parse.html | 2 +- playground/pages/print.html | 2 +- 7 files changed, 9 insertions(+), 317 deletions(-) delete mode 100644 playground/component/src/bindings.rs diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8c0cc80266..24fb01f0cf 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -168,6 +168,7 @@ jobs: - uses: actions/checkout@v4 - uses: bytecodealliance/wasmtime/.github/actions/install-rust@v20.0.0 - run: rustup component add rustfmt + - run: printf "\n" > playground/component/src/bindings.rs # Note that this doesn't use `cargo fmt` because that doesn't format # modules-defined-in-macros which is in use in `wast` for example. This is # the best alternative I can come up with at this time diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index c6472c1606..b13631feeb 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -17,7 +17,7 @@ defaults: jobs: build: - name: Build + name: Build playground deployment runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -44,7 +44,7 @@ jobs: path: "./playground/dist" deploy: - name: Deploy + name: Deploy playground if: github.ref == 'refs/heads/main' needs: build permissions: diff --git a/playground/.gitignore b/playground/.gitignore index 06af0ad93f..79f14c47fc 100644 --- a/playground/.gitignore +++ b/playground/.gitignore @@ -1,3 +1,4 @@ node_modules/ +src/bindings.rs component-built/ dist/ diff --git a/playground/component/Cargo.toml b/playground/component/Cargo.toml index 4a3cd14a13..6d3993ffae 100644 --- a/playground/component/Cargo.toml +++ b/playground/component/Cargo.toml @@ -10,13 +10,9 @@ wasmprinter = { workspace = true } [lib] crate-type = ["cdylib"] - -[profile.release] -codegen-units = 1 -opt-level = "s" -debug = false -strip = true -lto = true +test = false +doctest = false +doc = false [package.metadata.component] package = "component:component" diff --git a/playground/component/src/bindings.rs b/playground/component/src/bindings.rs deleted file mode 100644 index 30fe002dc6..0000000000 --- a/playground/component/src/bindings.rs +++ /dev/null @@ -1,306 +0,0 @@ -// Generated by `wit-bindgen` 0.25.0. DO NOT EDIT! -// Options used: -#[derive(Clone)] -pub enum PrintPart { - Str(_rt::String), - Name, - Literal, - Keyword, - Type, - Comment, - Reset, -} -impl ::core::fmt::Debug for PrintPart { - fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> ::core::fmt::Result { - match self { - PrintPart::Str(e) => f.debug_tuple("PrintPart::Str").field(e).finish(), - PrintPart::Name => f.debug_tuple("PrintPart::Name").finish(), - PrintPart::Literal => f.debug_tuple("PrintPart::Literal").finish(), - PrintPart::Keyword => f.debug_tuple("PrintPart::Keyword").finish(), - PrintPart::Type => f.debug_tuple("PrintPart::Type").finish(), - PrintPart::Comment => f.debug_tuple("PrintPart::Comment").finish(), - PrintPart::Reset => f.debug_tuple("PrintPart::Reset").finish(), - } - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub unsafe fn _export_parse_cabi(arg0: *mut u8, arg1: usize) -> *mut u8 { - #[cfg(target_arch = "wasm32")] - _rt::run_ctors_once(); - let len0 = arg1; - let bytes0 = _rt::Vec::from_raw_parts(arg0.cast(), len0, len0); - let result1 = T::parse(_rt::string_lift(bytes0)); - let ptr2 = _RET_AREA.0.as_mut_ptr().cast::(); - match result1 { - Ok(e) => { - *ptr2.add(0).cast::() = (0i32) as u8; - let vec3 = (e).into_boxed_slice(); - let ptr3 = vec3.as_ptr().cast::(); - let len3 = vec3.len(); - ::core::mem::forget(vec3); - *ptr2.add(8).cast::() = len3; - *ptr2.add(4).cast::<*mut u8>() = ptr3.cast_mut(); - } - Err(e) => { - *ptr2.add(0).cast::() = (1i32) as u8; - let vec4 = (e.into_bytes()).into_boxed_slice(); - let ptr4 = vec4.as_ptr().cast::(); - let len4 = vec4.len(); - ::core::mem::forget(vec4); - *ptr2.add(8).cast::() = len4; - *ptr2.add(4).cast::<*mut u8>() = ptr4.cast_mut(); - } - }; - ptr2 -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub unsafe fn __post_return_parse(arg0: *mut u8) { - let l0 = i32::from(*arg0.add(0).cast::()); - match l0 { - 0 => { - let l1 = *arg0.add(4).cast::<*mut u8>(); - let l2 = *arg0.add(8).cast::(); - let base3 = l1; - let len3 = l2; - _rt::cabi_dealloc(base3, len3 * 1, 1); - } - _ => { - let l4 = *arg0.add(4).cast::<*mut u8>(); - let l5 = *arg0.add(8).cast::(); - _rt::cabi_dealloc(l4, l5, 1); - } - } -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub unsafe fn _export_print_cabi(arg0: *mut u8, arg1: usize, arg2: i32) -> *mut u8 { - #[cfg(target_arch = "wasm32")] - _rt::run_ctors_once(); - let len0 = arg1; - let result1 = T::print( - _rt::Vec::from_raw_parts(arg0.cast(), len0, len0), - _rt::bool_lift(arg2 as u8), - ); - let ptr2 = _RET_AREA.0.as_mut_ptr().cast::(); - match result1 { - Ok(e) => { - *ptr2.add(0).cast::() = (0i32) as u8; - let vec4 = e; - let len4 = vec4.len(); - let layout4 = _rt::alloc::Layout::from_size_align_unchecked(vec4.len() * 12, 4); - let result4 = if layout4.size() != 0 { - let ptr = _rt::alloc::alloc(layout4).cast::(); - if ptr.is_null() { - _rt::alloc::handle_alloc_error(layout4); - } - ptr - } else { - { - ::core::ptr::null_mut() - } - }; - for (i, e) in vec4.into_iter().enumerate() { - let base = result4.add(i * 12); - { - match e { - PrintPart::Str(e) => { - *base.add(0).cast::() = (0i32) as u8; - let vec3 = (e.into_bytes()).into_boxed_slice(); - let ptr3 = vec3.as_ptr().cast::(); - let len3 = vec3.len(); - ::core::mem::forget(vec3); - *base.add(8).cast::() = len3; - *base.add(4).cast::<*mut u8>() = ptr3.cast_mut(); - } - PrintPart::Name => { - *base.add(0).cast::() = (1i32) as u8; - } - PrintPart::Literal => { - *base.add(0).cast::() = (2i32) as u8; - } - PrintPart::Keyword => { - *base.add(0).cast::() = (3i32) as u8; - } - PrintPart::Type => { - *base.add(0).cast::() = (4i32) as u8; - } - PrintPart::Comment => { - *base.add(0).cast::() = (5i32) as u8; - } - PrintPart::Reset => { - *base.add(0).cast::() = (6i32) as u8; - } - } - } - } - *ptr2.add(8).cast::() = len4; - *ptr2.add(4).cast::<*mut u8>() = result4; - } - Err(e) => { - *ptr2.add(0).cast::() = (1i32) as u8; - let vec5 = (e.into_bytes()).into_boxed_slice(); - let ptr5 = vec5.as_ptr().cast::(); - let len5 = vec5.len(); - ::core::mem::forget(vec5); - *ptr2.add(8).cast::() = len5; - *ptr2.add(4).cast::<*mut u8>() = ptr5.cast_mut(); - } - }; - ptr2 -} -#[doc(hidden)] -#[allow(non_snake_case)] -pub unsafe fn __post_return_print(arg0: *mut u8) { - let l0 = i32::from(*arg0.add(0).cast::()); - match l0 { - 0 => { - let l4 = *arg0.add(4).cast::<*mut u8>(); - let l5 = *arg0.add(8).cast::(); - let base6 = l4; - let len6 = l5; - for i in 0..len6 { - let base = base6.add(i * 12); - { - let l1 = i32::from(*base.add(0).cast::()); - match l1 { - 0 => { - let l2 = *base.add(4).cast::<*mut u8>(); - let l3 = *base.add(8).cast::(); - _rt::cabi_dealloc(l2, l3, 1); - } - 1 => (), - 2 => (), - 3 => (), - 4 => (), - 5 => (), - _ => (), - } - } - } - _rt::cabi_dealloc(base6, len6 * 12, 4); - } - _ => { - let l7 = *arg0.add(4).cast::<*mut u8>(); - let l8 = *arg0.add(8).cast::(); - _rt::cabi_dealloc(l7, l8, 1); - } - } -} -pub trait Guest { - fn parse(contents: _rt::String) -> Result<_rt::Vec, _rt::String>; - fn print(contents: _rt::Vec, skeleton: bool) -> Result<_rt::Vec, _rt::String>; -} -#[doc(hidden)] - -macro_rules! __export_world_wasm_tools_cabi{ - ($ty:ident with_types_in $($path_to_types:tt)*) => (const _: () = { - - #[export_name = "parse"] - unsafe extern "C" fn export_parse(arg0: *mut u8,arg1: usize,) -> *mut u8 { - $($path_to_types)*::_export_parse_cabi::<$ty>(arg0, arg1) - } - #[export_name = "cabi_post_parse"] - unsafe extern "C" fn _post_return_parse(arg0: *mut u8,) { - $($path_to_types)*::__post_return_parse::<$ty>(arg0) - } - #[export_name = "print"] - unsafe extern "C" fn export_print(arg0: *mut u8,arg1: usize,arg2: i32,) -> *mut u8 { - $($path_to_types)*::_export_print_cabi::<$ty>(arg0, arg1, arg2) - } - #[export_name = "cabi_post_print"] - unsafe extern "C" fn _post_return_print(arg0: *mut u8,) { - $($path_to_types)*::__post_return_print::<$ty>(arg0) - } - };); -} -#[doc(hidden)] -pub(crate) use __export_world_wasm_tools_cabi; -#[repr(align(4))] -struct _RetArea([::core::mem::MaybeUninit; 12]); -static mut _RET_AREA: _RetArea = _RetArea([::core::mem::MaybeUninit::uninit(); 12]); -mod _rt { - pub use alloc_crate::string::String; - - #[cfg(target_arch = "wasm32")] - pub fn run_ctors_once() { - wit_bindgen_rt::run_ctors_once(); - } - pub use alloc_crate::vec::Vec; - pub unsafe fn string_lift(bytes: Vec) -> String { - if cfg!(debug_assertions) { - String::from_utf8(bytes).unwrap() - } else { - String::from_utf8_unchecked(bytes) - } - } - pub unsafe fn cabi_dealloc(ptr: *mut u8, size: usize, align: usize) { - if size == 0 { - return; - } - let layout = alloc::Layout::from_size_align_unchecked(size, align); - alloc::dealloc(ptr as *mut u8, layout); - } - pub unsafe fn bool_lift(val: u8) -> bool { - if cfg!(debug_assertions) { - match val { - 0 => false, - 1 => true, - _ => panic!("invalid bool discriminant"), - } - } else { - val != 0 - } - } - pub use alloc_crate::alloc; - extern crate alloc as alloc_crate; -} - -/// Generates `#[no_mangle]` functions to export the specified type as the -/// root implementation of all generated traits. -/// -/// For more information see the documentation of `wit_bindgen::generate!`. -/// -/// ```rust -/// # macro_rules! export{ ($($t:tt)*) => (); } -/// # trait Guest {} -/// struct MyType; -/// -/// impl Guest for MyType { -/// // ... -/// } -/// -/// export!(MyType); -/// ``` -#[allow(unused_macros)] -#[doc(hidden)] - -macro_rules! __export_wasm_tools_impl { - ($ty:ident) => (self::export!($ty with_types_in self);); - ($ty:ident with_types_in $($path_to_types_root:tt)*) => ( - $($path_to_types_root)*::__export_world_wasm_tools_cabi!($ty with_types_in $($path_to_types_root)*); - ) -} -#[doc(inline)] -pub(crate) use __export_wasm_tools_impl as export; - -#[cfg(target_arch = "wasm32")] -#[link_section = "component-type:wit-bindgen:0.25.0:wasm-tools:encoded world"] -#[doc(hidden)] -pub static __WIT_BINDGEN_COMPONENT_TYPE: [u8; 326] = *b"\ -\0asm\x0d\0\x01\0\0\x19\x16wit-component-encoding\x04\0\x07\xc5\x01\x01A\x02\x01\ -A\x0a\x01q\x07\x03str\x01s\0\x04name\0\0\x07literal\0\0\x07keyword\0\0\x04type\0\ -\0\x07comment\0\0\x05reset\0\0\x03\0\x0aprint-part\x03\0\0\x01p}\x01j\x01\x02\x01\ -s\x01@\x01\x08contentss\0\x03\x04\0\x05parse\x01\x04\x01p\x01\x01j\x01\x05\x01s\x01\ -@\x02\x08contents\x02\x08skeleton\x7f\0\x06\x04\0\x05print\x01\x07\x04\x01\x1eco\ -mponent:component/wasm-tools\x04\0\x0b\x10\x01\0\x0awasm-tools\x03\0\0\0G\x09pro\ -ducers\x01\x0cprocessed-by\x02\x0dwit-component\x070.208.1\x10wit-bindgen-rust\x06\ -0.25.0"; - -#[inline(never)] -#[doc(hidden)] -#[cfg(target_arch = "wasm32")] -pub fn __link_custom_section_describing_imports() { - wit_bindgen_rt::maybe_link_cabi_realloc(); -} diff --git a/playground/pages/parse.html b/playground/pages/parse.html index ae17c56418..5b05209aa4 100644 --- a/playground/pages/parse.html +++ b/playground/pages/parse.html @@ -60,7 +60,7 @@

wasm-tools parse

Translate from the WebAssembly text format to binary using wasm-tools parse. - You can go the other direction using print.

+ You can go the other direction using print.

wat
diff --git a/playground/pages/print.html b/playground/pages/print.html index c16657bdb3..395f114a30 100644 --- a/playground/pages/print.html +++ b/playground/pages/print.html @@ -97,7 +97,7 @@

wasm-tools print

Translate WebAssembly binaries to text format using wasm-tools print. - You can go the other direction using parse.

+ You can go the other direction using parse.

From 2a80d3da7d7debe56686a5aabad1144430f76113 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Mon, 10 Jun 2024 17:12:42 -0700 Subject: [PATCH 06/58] Fix link to playground (#1607) --- playground/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/playground/README.md b/playground/README.md index 6bc44d3462..1398840501 100644 --- a/playground/README.md +++ b/playground/README.md @@ -1,6 +1,6 @@ # Playground -This is a simple online playground for `wasm-tools parse`, available at https://bytecodealliance.gthub.io/wasm-tools/. +This is a simple online playground for `wasm-tools parse`, available at https://bytecodealliance.github.io/wasm-tools/. ## Building From 34fbd2ad1d4b7ea113ecb9541ea9f3265112c4bc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 11 Jun 2024 12:06:56 -0500 Subject: [PATCH 07/58] Fold `resolve_world_from_name` helper into `select_world` (#1611) * Fold `resolve_world_from_name` helper into `select_world` This is a follow-up to #1577 to refactor a bit to have bindings generators continue to use `Resolve::select_world` for figuring out what to generate bindings for. * Review comments and API changes * Rename `Resolve::append` to `Resolve::push_group` * Add `Resolve::push_str` * Change `&Path` arguments to `impl AsRef` * Fix fuzzer build --- crates/wit-component/src/encoding.rs | 33 +-- crates/wit-component/src/lib.rs | 53 +--- crates/wit-component/src/metadata.rs | 4 +- crates/wit-component/tests/components.rs | 2 +- crates/wit-component/tests/interfaces.rs | 5 +- crates/wit-component/tests/linking.rs | 11 +- crates/wit-component/tests/targets.rs | 23 +- crates/wit-component/tests/wit.rs | 10 +- crates/wit-parser/src/lib.rs | 28 +- crates/wit-parser/src/resolve.rs | 348 +++++++++++++++++------ fuzz/src/roundtrip_wit.rs | 2 +- src/bin/wasm-tools/component.rs | 16 +- 12 files changed, 317 insertions(+), 218 deletions(-) diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 057e755c51..56eecf06a4 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -2129,39 +2129,32 @@ impl ComponentEncoder { #[cfg(all(test, feature = "dummy-module"))] mod test { - use crate::{dummy_module, embed_component_metadata}; - use super::*; - use std::path::Path; - use wit_parser::UnresolvedPackageGroup; + use crate::{dummy_module, embed_component_metadata}; #[test] fn it_renames_imports() { let mut resolve = Resolve::new(); - let UnresolvedPackageGroup { - mut packages, - source_map, - } = UnresolvedPackageGroup::parse( - Path::new("test.wit"), - r#" + let pkgs = resolve + .push_str( + "test.wit", + r#" package test:wit; interface i { -f: func(); + f: func(); } world test { -import i; -import foo: interface { -f: func(); -} + import i; + import foo: interface { + f: func(); + } } "#, - ) - .unwrap(); - let pkg = resolve.push(packages.remove(0), &source_map).unwrap(); - - let world = resolve.select_world(pkg, None).unwrap(); + ) + .unwrap(); + let world = resolve.select_world(&pkgs, None).unwrap(); let mut module = dummy_module(&resolve, world); diff --git a/crates/wit-component/src/lib.rs b/crates/wit-component/src/lib.rs index 7d81335e23..ba175aa896 100644 --- a/crates/wit-component/src/lib.rs +++ b/crates/wit-component/src/lib.rs @@ -5,9 +5,9 @@ use std::str::FromStr; use std::{borrow::Cow, fmt::Display}; -use anyhow::{bail, Context, Result}; +use anyhow::{bail, Result}; use wasm_encoder::{CanonicalOption, Encode, Section}; -use wit_parser::{parse_use_path, PackageId, ParsedUsePath, Resolve, WorldId}; +use wit_parser::{Resolve, WorldId}; mod encoding; mod gc; @@ -79,43 +79,6 @@ impl From for wasm_encoder::CanonicalOption { } } -/// Handles world name resolution for cases when multiple packages may have been resolved. If this -/// is the case, and we're dealing with input that contains a user-supplied world name (like via a -/// CLI command, for instance), we want to ensure that the world name follows the following rules: -/// -/// * If there is a single resolved package with a single world, the world name name MAY be -/// omitted. -/// * If there is a single resolved package with multiple worlds, the world name MUST be supplied, -/// but MAY or MAY NOT be fully-qualified. -/// * If there are multiple resolved packages, the world name MUST be fully-qualified. -pub fn resolve_world_from_name( - resolve: &Resolve, - resolved_packages: Vec, - world_name: Option<&str>, -) -> Result { - match resolved_packages.len() { - 0 => bail!("all of the supplied WIT source files were empty"), - 1 => resolve.select_world(resolved_packages[0], world_name.as_deref()), - _ => match world_name.as_deref() { - Some(name) => { - let world_path = parse_use_path(name).with_context(|| { - format!("failed to parse world specifier `{name}`") - })?; - match world_path { - ParsedUsePath::Name(name) => bail!("the world specifier must be of the fully-qualified, id-based form (ex: \"wasi:http/proxy\" rather than \"proxy\"); you used {name}"), - ParsedUsePath::Package(pkg_name, _) => { - match resolve.package_names.get(&pkg_name) { - Some(pkg_id) => resolve.select_world(pkg_id.clone(), world_name.as_deref()), - None => bail!("the world specifier you provided named {pkg_name}, but no package with that name was found"), - } - } - } - } - None => bail!("the supplied WIT source files describe multiple packages; please provide a fully-qualified world-specifier to the `embed` command"), - }, - } -} - /// A producer section to be added to all modules and components synthesized by /// this crate pub(crate) fn base_producers() -> wasm_metadata::Producers { @@ -145,11 +108,9 @@ pub fn embed_component_metadata( #[cfg(test)] mod tests { - use std::path::Path; - use anyhow::Result; use wasmparser::Payload; - use wit_parser::{Resolve, UnresolvedPackageGroup}; + use wit_parser::Resolve; use super::{embed_component_metadata, StringEncoding}; @@ -184,12 +145,8 @@ world test-world {} // Parse pre-canned WIT to build resolver let mut resolver = Resolve::default(); - let UnresolvedPackageGroup { - mut packages, - source_map, - } = UnresolvedPackageGroup::parse(&Path::new("in-code.wit"), COMPONENT_WIT)?; - let pkg_id = resolver.push(packages.remove(0), &source_map)?; - let world = resolver.select_world(pkg_id, Some("test-world").into())?; + let pkgs = resolver.push_str("in-code.wit", COMPONENT_WIT)?; + let world = resolver.select_world(&pkgs, Some("test-world"))?; // Embed component metadata embed_component_metadata(&mut bytes, &resolver, world, StringEncoding::UTF8)?; diff --git a/crates/wit-component/src/metadata.rs b/crates/wit-component/src/metadata.rs index 59cb7353ee..f3e43614a7 100644 --- a/crates/wit-component/src/metadata.rs +++ b/crates/wit-component/src/metadata.rs @@ -42,7 +42,7 @@ //! the three arguments originally passed to `encode`. use crate::validation::BARE_FUNC_MODULE_NAME; -use crate::{resolve_world_from_name, DecodedWasm, StringEncoding}; +use crate::{DecodedWasm, StringEncoding}; use anyhow::{bail, Context, Result}; use indexmap::IndexMap; use std::borrow::Cow; @@ -264,7 +264,7 @@ impl Bindgen { DecodedWasm::Component(..) => bail!("expected encoded wit package(s)"), }; resolve = r; - world = resolve_world_from_name(&resolve, pkgs, Some(world_name.into()))?; + world = resolve.select_world(&pkgs, Some(world_name.into()))?; } // Current format where `data` is a wasm component itself. diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index 87917cce30..de2ff79c67 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -245,7 +245,7 @@ fn read_core_module(path: &Path, resolve: &Resolve, pkg: PackageId) -> Result Result<()> { let packages = if is_dir { resolve.push_dir(path)?.0 } else { - resolve.append(UnresolvedPackageGroup::parse_file(path)?)? + resolve.push_file(path)? }; for package in packages { @@ -107,8 +107,7 @@ fn assert_print(resolve: &Resolve, pkg_ids: &[PackageId], path: &Path, is_dir: b assert_output(&expected, &output)?; } - UnresolvedPackageGroup::parse("foo.wit".as_ref(), &output) - .context("failed to parse printed output")?; + UnresolvedPackageGroup::parse("foo.wit", &output).context("failed to parse printed output")?; Ok(()) } diff --git a/crates/wit-component/tests/linking.rs b/crates/wit-component/tests/linking.rs index 56757f9750..f1d9ac35dc 100644 --- a/crates/wit-component/tests/linking.rs +++ b/crates/wit-component/tests/linking.rs @@ -1,8 +1,7 @@ use { anyhow::{Context, Result}, - std::path::Path, wit_component::StringEncoding, - wit_parser::{Resolve, UnresolvedPackageGroup}, + wit_parser::Resolve, }; const FOO: &str = r#" @@ -141,12 +140,8 @@ fn encode(wat: &str, wit: Option<&str>) -> Result> { if let Some(wit) = wit { let mut resolve = Resolve::default(); - let UnresolvedPackageGroup { - mut packages, - source_map, - } = UnresolvedPackageGroup::parse(Path::new("wit"), wit)?; - let pkg = resolve.push(packages.remove(0), &source_map)?; - let world = resolve.select_world(pkg, None)?; + let pkgs = resolve.push_str("test.wit", wit)?; + let world = resolve.select_world(&pkgs, None)?; wit_component::embed_component_metadata( &mut module, diff --git a/crates/wit-component/tests/targets.rs b/crates/wit-component/tests/targets.rs index 8dac12d304..7c9581a5c4 100644 --- a/crates/wit-component/tests/targets.rs +++ b/crates/wit-component/tests/targets.rs @@ -1,6 +1,6 @@ -use anyhow::{bail, Context, Result}; +use anyhow::{Context, Result}; use std::{fs, path::Path}; -use wit_parser::{Resolve, UnresolvedPackageGroup, WorldId}; +use wit_parser::{Resolve, WorldId}; /// Tests whether a component targets a world. /// @@ -22,7 +22,7 @@ use wit_parser::{Resolve, UnresolvedPackageGroup, WorldId}; /// Run the test with the environment variable `BLESS` set to update `error.txt`. /// /// Each test is effectively executing as: -/// ```wasm-tools component targets -w foobar test.wit test.wat``` +/// `wasm-tools component targets -w foobar test.wit test.wat` #[test] fn targets() -> Result<()> { drop(env_logger::try_init()); @@ -79,23 +79,10 @@ fn load_test_wit(path: &Path) -> Result<(Resolve, WorldId)> { const TEST_TARGET_WORLD_ID: &str = "foobar"; let test_wit_path = path.join("test.wit"); - let UnresolvedPackageGroup { - mut packages, - source_map, - } = UnresolvedPackageGroup::parse_file(&test_wit_path) - .context("failed to parse WIT package")?; - if packages.is_empty() { - bail!("Files were completely empty - are you sure these are the files you're looking for?") - } - if packages.len() > 1 { - bail!("Multi-package targeting tests are not yet supported.") - } - let mut resolve = Resolve::default(); - let package_id = resolve.push(packages.remove(0), &source_map)?; - + let pkgs = resolve.push_file(&test_wit_path)?; let world_id = resolve - .select_world(package_id, Some(TEST_TARGET_WORLD_ID)) + .select_world(&pkgs, Some(TEST_TARGET_WORLD_ID)) .with_context(|| "failed to select world from package".to_string())?; Ok((resolve, world_id)) diff --git a/crates/wit-component/tests/wit.rs b/crates/wit-component/tests/wit.rs index 860fd1082c..1930eabfc6 100644 --- a/crates/wit-component/tests/wit.rs +++ b/crates/wit-component/tests/wit.rs @@ -9,9 +9,9 @@ fn parse_wit_dir() -> Result<()> { drop(env_logger::try_init()); let mut resolver = Resolve::default(); - let package_id = resolver.push_path("tests/wit/parse-dir/wit")?.0[0]; + let (package_ids, _) = resolver.push_path("tests/wit/parse-dir/wit")?; assert!(resolver - .select_world(package_id, "foo-world".into()) + .select_world(&package_ids, "foo-world".into()) .is_ok()); Ok(()) @@ -23,10 +23,8 @@ fn parse_wit_file() -> Result<()> { drop(env_logger::try_init()); let mut resolver = Resolve::default(); - let package_id = resolver - .push_path("tests/wit/parse-dir/wit/deps/bar/bar.wit")? - .0[0]; - resolver.select_world(package_id, "bar-world".into())?; + let (package_ids, _) = resolver.push_path("tests/wit/parse-dir/wit/deps/bar/bar.wit")?; + resolver.select_world(&package_ids, "bar-world".into())?; assert!(resolver .interfaces .iter() diff --git a/crates/wit-parser/src/lib.rs b/crates/wit-parser/src/lib.rs index 72e0857bce..2ab630cc69 100644 --- a/crates/wit-parser/src/lib.rs +++ b/crates/wit-parser/src/lib.rs @@ -228,10 +228,11 @@ impl UnresolvedPackageGroup { /// Parses the given string as a wit document. /// /// The `path` argument is used for error reporting. The `contents` provided - /// will not be able to use `pkg` use paths to other documents. - pub fn parse(path: &Path, contents: &str) -> Result { + /// are considered to be the contents of `path`. This function does not read + /// the filesystem. + pub fn parse(path: impl AsRef, contents: &str) -> Result { let mut map = SourceMap::default(); - map.push(path, contents); + map.push(path.as_ref(), contents); map.parse() } @@ -240,7 +241,8 @@ impl UnresolvedPackageGroup { /// The path provided is inferred whether it's a file or a directory. A file /// is parsed with [`UnresolvedPackageGroup::parse_file`] and a directory is /// parsed with [`UnresolvedPackageGroup::parse_dir`]. - pub fn parse_path(path: &Path) -> Result { + pub fn parse_path(path: impl AsRef) -> Result { + let path = path.as_ref(); if path.is_dir() { UnresolvedPackageGroup::parse_dir(path) } else { @@ -250,9 +252,10 @@ impl UnresolvedPackageGroup { /// Parses a WIT package from the file provided. /// - /// The WIT package returned will be a single-document package and will not - /// be able to use `pkg` paths to other documents. - pub fn parse_file(path: &Path) -> Result { + /// The return value represents all packages found in the WIT file which + /// might be either one or multiple depending on the syntax used. + pub fn parse_file(path: impl AsRef) -> Result { + let path = path.as_ref(); let contents = std::fs::read_to_string(path) .with_context(|| format!("failed to read file {path:?}"))?; Self::parse(path, &contents) @@ -260,9 +263,12 @@ impl UnresolvedPackageGroup { /// Parses a WIT package from the directory provided. /// - /// All files with the extension `*.wit` or `*.wit.md` will be loaded from - /// `path` into the returned package. - pub fn parse_dir(path: &Path) -> Result { + /// This method will look at all files under the `path` specified. All + /// `*.wit` files are parsed and assumed to be part of the same package + /// grouping. This is useful when a WIT package is split across multiple + /// files. + pub fn parse_dir(path: impl AsRef) -> Result { + let path = path.as_ref(); let mut map = SourceMap::default(); let cx = || format!("failed to read directory {path:?}"); for entry in path.read_dir().with_context(&cx)? { @@ -281,7 +287,7 @@ impl UnresolvedPackageGroup { Some(name) => name, None => continue, }; - if !filename.ends_with(".wit") && !filename.ends_with(".wit.md") { + if !filename.ends_with(".wit") { continue; } map.push_file(&path)?; diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index a41290a6aa..c5a2f237c1 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -154,23 +154,35 @@ impl Resolve { Resolve::default() } - /// Parse a WIT package from the input `path`. + /// Parse WIT packages from the input `path`. /// /// The input `path` can be one of: /// /// * A directory containing a WIT package with an optional `deps` directory /// for any dependent WIT packages it references. - /// * A single standalone WIT file depending on what's already in `Resolve`. + /// * A single standalone WIT file. /// * A wasm-encoded WIT package as a single file in the wasm binary format. /// * A wasm-encoded WIT package as a single file in the wasm text format. /// - /// The `PackageId` of the parsed package is returned. For more information - /// see [`Resolve::push_dir`] and [`Resolve::push_file`]. This method will - /// automatically call the appropriate method based on what kind of - /// filesystem entry `path` is. + /// In all of these cases packages are allowed to depend on previously + /// inserted packages into this `Resolve`. Resolution for packages is based + /// on the name of each package and reference. /// - /// Returns the top-level [`PackageId`] as well as a list of all files read - /// during this parse. + /// This method returns a list of `PackageId` elements and additionally a + /// list of `PathBuf` elements. The `PackageId` elements represent the "main + /// package" that was parsed. For example if a single WIT file was specified + /// this will be all the packages found in the file. For a directory this + /// will be all the packages in the directory itself, but not in the `deps` + /// directory. The list of `PackageId` values is useful to pass to + /// [`Resolve::select_world`] to take a user-specified world in a + /// conventional fashion and select which to use for bindings generation. + /// + /// The returned list of `PathBuf` elements represents all files parsed + /// during this operation. This can be useful for systems that want to + /// rebuild or regenerate bindings based on files modified. + /// + /// More information can also be found at [`Resolve::push_dir`] and + /// [`Resolve::push_file`]. pub fn push_path(&mut self, path: impl AsRef) -> Result<(Vec, Vec)> { self._push_path(path.as_ref()) } @@ -247,25 +259,42 @@ impl Resolve { } /// Parses the filesystem directory at `path` as a WIT package and returns - /// the fully resolved [`PackageId`] as a result. + /// a fully resolved [`PackageId`] list as a result. /// /// The directory itself is parsed with [`UnresolvedPackageGroup::parse_dir`] - /// which has more information on the layout of the directory. This method, - /// however, additionally supports an optional `deps` dir where dependencies - /// can be located. - /// - /// All entries in the `deps` directory are inspected and parsed as follows: - /// - /// * Any directories inside of `deps` are assumed to be another WIT package - /// and are parsed with [`UnresolvedPackageGroup::parse_dir`]. - /// * WIT files (`*.wit`) are parsed with [`UnresolvedPackageGroup::parse_file`]. - /// * WebAssembly files (`*.wasm` or `*.wat`) are assumed to be WIT packages - /// encoded to wasm and are parsed and inserted into `self`. - /// - /// This function returns the [`PackageId`]s of the root parsed packages at - /// `path`, along with a list of all paths that were consumed during parsing - /// for the root package and all dependency packages, for each package encountered. - pub fn push_dir(&mut self, path: &Path) -> Result<(Vec, Vec)> { + /// and then all packages found are inserted into this `Resolve`. The `path` + /// specified may have a `deps` subdirectory which is probed automatically + /// for any other WIT dependencies. + /// + /// The `deps` folder may contain: + /// + /// * `$path/deps/my-package/*.wit` - a directory that may contain multiple + /// WIT files. This is parsed with [`UnresolvedPackageGroup::parse_dir`] + /// and then inserted into this [`Resolve`]. Note that cannot recursively + /// contain a `deps` directory. + /// * `$path/deps/my-package.wit` - a single-file WIT package. This is + /// parsed with [`Resolve::push_file`] and then added to `self` for + /// name reoslution. + /// * `$path/deps/my-package.{wasm,wat}` - a wasm-encoded WIT package either + /// in the text for binary format. + /// + /// In all cases entries in the `deps` folder are added to `self` first + /// before adding files found in `path` itself. All WIT packages found are + /// candidates for name-based resolution that other packages may used. + /// + /// This function returns a tuple of two values. The first value is a list + /// of [`PackageId`] values which represents the WIT packages found within + /// `path`, but not those within `deps`. The `path` provided may contain + /// only a single WIT package but might also use the multi-package form of + /// WIT, and the returned list will indicate which was used. This argument + /// is useful for passing to [`Resolve::select_world`] for choosing + /// something to bindgen with. + /// + /// The second value returned here is the list of paths that were parsed + /// when generating the return value. This can be useful for build systems + /// that want to rebuild bindings whenever one of the files change. + pub fn push_dir(&mut self, path: impl AsRef) -> Result<(Vec, Vec)> { + let path = path.as_ref(); let deps_path = path.join("deps"); let unresolved_deps = self.parse_deps_dir(&deps_path).with_context(|| { format!( @@ -342,13 +371,14 @@ impl Resolve { /// In this the package and all of its dependencies are automatically /// inserted into `self`. /// - /// In both situations the `PackageId` of the resulting resolved package is - /// returned from this method. + /// In both situations the `PackageId`s of the resulting resolved packages + /// are returned from this method. The return value is mostly useful in + /// conjunction with [`Resolve::select_world`]. pub fn push_file(&mut self, path: impl AsRef) -> Result> { match self._push_file(path.as_ref())? { #[cfg(feature = "decoding")] ParsedFile::Package(id) => Ok(vec![id]), - ParsedFile::Unresolved(pkgs) => self.append(pkgs), + ParsedFile::Unresolved(pkgs) => self.push_group(pkgs), } } @@ -413,22 +443,32 @@ impl Resolve { source_map.rewrite_error(|| Remap::default().append(self, unresolved)) } - /// Appends new [`UnresolvedPackageSet`] to this [`Resolve`], creating a + /// Appends new [`UnresolvedPackageGroup`] to this [`Resolve`], creating a /// fully resolved package with no dangling references. /// - /// The `deps` argument indicates that the named dependencies in - /// `unresolved` to packages are resolved by the mapping specified. - /// /// Any dependency resolution error or otherwise world-elaboration error /// will be returned here, if successful a package identifier is returned /// which corresponds to the package that was just inserted. /// - /// The returned [PackageId]s are listed in topologically sorted order. - pub fn append(&mut self, unresolved_groups: UnresolvedPackageGroup) -> Result> { + /// The returned [`PackageId`]s are listed in topologically sorted order. + pub fn push_group( + &mut self, + unresolved_groups: UnresolvedPackageGroup, + ) -> Result> { let (pkg_ids, _) = self.sort_unresolved_packages(vec![unresolved_groups])?; Ok(pkg_ids) } + /// Convenience method for combining [`UnresolvedPackageGroup::parse`] and + /// [`Resolve::push_group`]. + /// + /// The `path` provided is used for error messages but otherwise is not + /// read. This method does not touch the filesystem. The `contents` provided + /// are the contents of a WIT package. + pub fn push_str(&mut self, path: impl AsRef, contents: &str) -> Result> { + self.push_group(UnresolvedPackageGroup::parse(path.as_ref(), contents)?) + } + pub fn all_bits_valid(&self, ty: &Type) -> bool { match ty { Type::U8 @@ -798,46 +838,154 @@ impl Resolve { base } - /// Attempts to locate a world given the "default" package `pkg` and the + /// Attempts to locate a world given the "default" set of `packages` and the /// optional string specifier `world`. /// /// This method is intended to be used by bindings generation tools to - /// select a world from either `pkg` or a package in this `Resolve`. + /// select a world from either `packages` or a package in this `Resolve`. + /// The `packages` list is a return value from methods such as + /// [`push_path`](Resolve::push_path), [`push_dir`](Resolve::push_dir), + /// [`push_file`](Resolve::push_file), [`push_group`](Resolve::push_group), + /// or [`push_str`](Resolve::push_str). The return values of those methods + /// are the "main package list" which is specified by the user and is used + /// as a heuristic for world selection. /// - /// If `world` is `None` then `pkg` must have precisely one world which will - /// be returned. + /// If `world` is `None` then `packages` must have one entry and that + /// package must have exactly one world. If this is the case then that world + /// will be returned, otherwise an error will be returned. /// /// If `world` is `Some` then it can either be: /// - /// * A kebab-name of a world contained within `pkg` which is being - /// selected, such as `"the-world"`. + /// * A kebab-name of a world such as `"the-world"`. In this situation + /// the `packages` list must have only a single entry. If `packages` has + /// no entries or more than one, or if the kebab-name does not exist in + /// the one package specified, then an error will be returned. /// /// * An ID-based form of a world which is selected within this `Resolve`, - /// ignoring `pkg`. For example `"wasi:http/proxy"`. + /// for example `"wasi:http/proxy"`. In this situation the `packages` + /// array is ignored and the ID specified is use to lookup a package. Note + /// that a version does not need to be specified in this string if there's + /// only one package of the same name and it has a version. In this + /// situation the version can be omitted. /// /// If successful the corresponding `WorldId` is returned, otherwise an /// error is returned. - pub fn select_world(&self, pkg: PackageId, world: Option<&str>) -> Result { - let world = match world { - Some(world) => world, - None => { - let pkg = &self.packages[pkg]; - match pkg.worlds.len() { - 0 => bail!("no worlds found in package `{}`", pkg.name), - 1 => return Ok(*pkg.worlds.values().next().unwrap()), - _ => bail!( - "multiple worlds found in package `{}`: one must be explicitly chosen", - pkg.name - ), - } - } + /// + /// # Examples + /// + /// ``` + /// use anyhow::Result; + /// use wit_parser::Resolve; + /// + /// fn main() -> Result<()> { + /// let mut resolve = Resolve::default(); + /// + /// // For inputs which have a single package and only one world `None` + /// // can be specified. + /// let ids = resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit1; + /// + /// world foo { + /// // ... + /// } + /// "#, + /// )?; + /// assert!(resolve.select_world(&ids, None).is_ok()); + /// + /// // For inputs which have a single package and multiple worlds then + /// // a world must be specified. + /// let ids = resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit2; + /// + /// world foo { /* ... */ } + /// + /// world bar { /* ... */ } + /// "#, + /// )?; + /// assert!(resolve.select_world(&ids, None).is_err()); + /// assert!(resolve.select_world(&ids, Some("foo")).is_ok()); + /// assert!(resolve.select_world(&ids, Some("bar")).is_ok()); + /// + /// // For inputs which have more than one package then a fully + /// // qualified name must be specified. + /// let ids = resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit3 { + /// world foo { /* ... */ } + /// } + /// + /// package example:wit4 { + /// world foo { /* ... */ } + /// } + /// "#, + /// )?; + /// assert!(resolve.select_world(&ids, None).is_err()); + /// assert!(resolve.select_world(&ids, Some("foo")).is_err()); + /// assert!(resolve.select_world(&ids, Some("example:wit3/foo")).is_ok()); + /// assert!(resolve.select_world(&ids, Some("example:wit4/foo")).is_ok()); + /// + /// // Note that the `ids` or `packages` argument is ignored if a fully + /// // qualified world specified is provided meaning previous worlds + /// // can be selected. + /// assert!(resolve.select_world(&[], Some("example:wit1/foo")).is_ok()); + /// assert!(resolve.select_world(&[], Some("example:wit2/foo")).is_ok()); + /// + /// // When selecting with a version it's ok to drop the version when + /// // there's only a single copy of that package in `Resolve`. + /// resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit5@1.0.0; + /// + /// world foo { /* ... */ } + /// "#, + /// )?; + /// assert!(resolve.select_world(&[], Some("example:wit5/foo")).is_ok()); + /// + /// // However when a single package has multiple versions in a resolve + /// // it's required to specify the version to select which one. + /// resolve.push_str( + /// "./my-test.wit", + /// r#" + /// package example:wit5@2.0.0; + /// + /// world foo { /* ... */ } + /// "#, + /// )?; + /// assert!(resolve.select_world(&[], Some("example:wit5/foo")).is_err()); + /// assert!(resolve.select_world(&[], Some("example:wit5/foo@1.0.0")).is_ok()); + /// assert!(resolve.select_world(&[], Some("example:wit5/foo@2.0.0")).is_ok()); + /// + /// Ok(()) + /// } + /// ``` + pub fn select_world(&self, packages: &[PackageId], world: Option<&str>) -> Result { + let world_path = match world { + Some(world) => Some( + parse_use_path(world) + .with_context(|| format!("failed to parse world specifier `{world}`"))?, + ), + None => None, }; - let path = parse_use_path(world) - .with_context(|| format!("failed to parse world specifier `{world}`"))?; - let (pkg, world) = match path { - ParsedUsePath::Name(name) => (pkg, name), - ParsedUsePath::Package(pkg, interface) => { + let (pkg, world_name) = match world_path { + Some(ParsedUsePath::Name(name)) => match packages { + [] => bail!("no packages were found to locate the world `{name}` within"), + [one] => (*one, name), + [..] => { + bail!( + "the supplied WIT source files describe multiple packages; \ + please provide a fully-qualified world-specifier select \ + a world amongst these packages" + ) + } + }, + Some(ParsedUsePath::Package(pkg, interface)) => { let pkg = match self.package_names.get(&pkg) { Some(pkg) => *pkg, None => { @@ -863,14 +1011,35 @@ impl Resolve { } } }; - (pkg, interface) - } + (pkg, interface.to_string()) + } + None => match packages { + [] => bail!("no packages were specified nor is a world specified"), + [one] => { + let pkg = &self.packages[*one]; + match pkg.worlds.len() { + 0 => bail!("no worlds found in package `{}`", pkg.name), + 1 => return Ok(*pkg.worlds.values().next().unwrap()), + _ => bail!( + "multiple worlds found in package `{}`: one must be explicitly chosen", + pkg.name + ), + } + } + [..] => { + bail!( + "the supplied WIT source files describe multiple packages; \ + please provide a fully-qualified world-specifier select \ + a world amongst these packages" + ) + } + }, }; let pkg = &self.packages[pkg]; pkg.worlds - .get(&world) + .get(&world_name) .copied() - .ok_or_else(|| anyhow!("no world named `{world}` in package")) + .ok_or_else(|| anyhow!("no world named `{world_name}` in package")) } /// Assigns a human readable name to the `WorldKey` specified. @@ -2512,63 +2681,60 @@ impl<'a> MergeMap<'a> { #[cfg(test)] mod tests { - use crate::{PackageId, Resolve}; + use crate::Resolve; + use anyhow::Result; #[test] - fn select_world() { + fn select_world() -> Result<()> { let mut resolve = Resolve::default(); - parse_into( - &mut resolve, + resolve.push_str( + "test.wit", r#" package foo:bar@0.1.0; world foo {} "#, - ); - parse_into( - &mut resolve, + )?; + resolve.push_str( + "test.wit", r#" package foo:baz@0.1.0; world foo {} "#, - ); - parse_into( - &mut resolve, + )?; + resolve.push_str( + "test.wit", r#" package foo:baz@0.2.0; world foo {} "#, - ); + )?; - let dummy = parse_into( - &mut resolve, + let dummy = resolve.push_str( + "test.wit", r#" package foo:dummy; world foo {} "#, - ); + )?; - assert!(resolve.select_world(dummy, None).is_ok()); - assert!(resolve.select_world(dummy, Some("xx")).is_err()); - assert!(resolve.select_world(dummy, Some("")).is_err()); - assert!(resolve.select_world(dummy, Some("foo:bar/foo")).is_ok()); + assert!(resolve.select_world(&dummy, None).is_ok()); + assert!(resolve.select_world(&dummy, Some("xx")).is_err()); + assert!(resolve.select_world(&dummy, Some("")).is_err()); + assert!(resolve.select_world(&dummy, Some("foo:bar/foo")).is_ok()); assert!(resolve - .select_world(dummy, Some("foo:bar/foo@0.1.0")) + .select_world(&dummy, Some("foo:bar/foo@0.1.0")) .is_ok()); - assert!(resolve.select_world(dummy, Some("foo:baz/foo")).is_err()); + assert!(resolve.select_world(&dummy, Some("foo:baz/foo")).is_err()); assert!(resolve - .select_world(dummy, Some("foo:baz/foo@0.1.0")) + .select_world(&dummy, Some("foo:baz/foo@0.1.0")) .is_ok()); assert!(resolve - .select_world(dummy, Some("foo:baz/foo@0.2.0")) + .select_world(&dummy, Some("foo:baz/foo@0.2.0")) .is_ok()); - } - - fn parse_into(resolve: &mut Resolve, wit: &str) -> PackageId { - let pkgs = crate::UnresolvedPackageGroup::parse("input.wit".as_ref(), wit).unwrap(); - resolve.append(pkgs).unwrap()[0] + Ok(()) } } diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs index 19967992e1..b15cd8c415 100644 --- a/fuzz/src/roundtrip_wit.rs +++ b/fuzz/src/roundtrip_wit.rs @@ -90,7 +90,7 @@ fn roundtrip_through_printing(file: &str, resolve: &Resolve, wasm: &[u8]) { write_file(&format!("{file}-{pkg_name}.wit"), &doc); map.push(format!("{pkg_name}.wit").as_ref(), doc); let unresolved = map.parse().unwrap(); - let id = new_resolve.append(unresolved).unwrap(); + let id = new_resolve.push_group(unresolved).unwrap(); last = Some(id.last().unwrap().to_owned()); } diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 5d759df482..d546f82883 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -11,10 +11,9 @@ use wasm_tools::Output; use wasmparser::WasmFeatures; use wat::Detect; use wit_component::{ - embed_component_metadata, resolve_world_from_name, ComponentEncoder, DecodedWasm, Linker, - StringEncoding, WitPrinter, + embed_component_metadata, ComponentEncoder, DecodedWasm, Linker, StringEncoding, WitPrinter, }; -use wit_parser::{PackageId, Resolve, UnresolvedPackageGroup}; +use wit_parser::{PackageId, Resolve}; /// WebAssembly wit-based component tooling. #[derive(Parser)] @@ -285,7 +284,7 @@ impl EmbedOpts { Some(self.io.parse_input_wasm()?) }; let (resolve, pkg_ids) = self.resolve.load()?; - let world = resolve_world_from_name(&resolve, pkg_ids, self.world.as_deref())?; + let world = resolve.select_world(&pkg_ids, self.world.as_deref())?; let mut wasm = wasm.unwrap_or_else(|| wit_component::dummy_module(&resolve, world)); embed_component_metadata( @@ -590,8 +589,7 @@ impl WitOpts { }; let mut resolve = WitResolve::resolve_with_features(&self.features, self.all_features); - let pkgs = UnresolvedPackageGroup::parse(path, input)?; - let ids = resolve.append(pkgs)?; + let ids = resolve.push_str(path, input)?; Ok(DecodedWasm::WitPackages(resolve, ids)) } } @@ -723,7 +721,7 @@ impl TargetsOpts { /// Executes the application. fn run(self) -> Result<()> { let (resolve, pkg_ids) = self.resolve.load()?; - let world = resolve_world_from_name(&resolve, pkg_ids, self.world.as_deref())?; + let world = resolve.select_world(&pkg_ids, self.world.as_deref())?; let component_to_test = self.input.parse_wasm()?; wit_component::targets(&resolve, world, &component_to_test)?; @@ -763,8 +761,8 @@ impl SemverCheckOpts { fn run(self) -> Result<()> { let (resolve, pkg_ids) = self.resolve.load()?; - let prev = resolve_world_from_name(&resolve, pkg_ids.clone(), Some(self.prev).as_deref())?; - let new = resolve_world_from_name(&resolve, pkg_ids, Some(self.new).as_deref())?; + let prev = resolve.select_world(&pkg_ids, Some(self.prev.as_str()))?; + let new = resolve.select_world(&pkg_ids, Some(self.new.as_str()))?; wit_component::semver_check(resolve, prev, new)?; Ok(()) } From 2108dfca955b05b1d0659ac386e9c9dd875979b4 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 12 Jun 2024 10:19:39 -0700 Subject: [PATCH 08/58] threads: add `shared` heap types (#1600) * threads: add `shared` heap types The shared-everything-thread [proposal] specifies that all abstract heap types can be marked `shared` by prepending them with the 0x65 prefix byte. While we wait for clarification on what this precisely means, I chose to implement the "extra byte" version, which will result in larger binaries that use `shared` types. The key change here is that `wasmparser::HeapType` now groups all of the abstract heap types (e.g., `any`) into a `wasmparser::HeapType::Abstract` variant, which allows us to mark those as `shared` in a single place. This results in a cascade of changes elsewhere, most of which are pretty harmless (an extra, internal `match` on the `ty` of the `Abstract` variant). The real business left unfinished by this commit is _what_ to do with the `shared` field in all of these locations; for now I've just noted those as TODOs to get a review on this approach so far before forging ahead. [proposal]: https://github.com/WebAssembly/shared-everything-threads [#64]: https://github.com/WebAssembly/shared-everything-threads/issues/64 * threads: propagate `shared` field to `wasm_encoder` This continues the work of the previous commit by propagating the `shared` field of abstract heap types through to `wasm_encoder`, with uses in various other crates. As before, the boundary at which we cannot yet handle sharedness is marked with TODO. * threads: propagate `shared` into `wast` This finally extends `shared` into the heap types defined in `wast`. As before, a distinction is drawn between abstract and concrete heap types. * review: refactor reading of short-hand reftypes * review: run CI-specific `rustfmt` invocation * review: use short-hand `RefType` constant for brevity * review: use `Into` for wasmparser-to-wasm-encoder conversion * review: document which proposal `shared` appears in * review: fix typo, 'eqf' -> 'eq' * review: remove unnecessary TODO * review: link to shared-ness issue for reference conversions * review: fix typo * review: add `wasmparser` feature for `Into` conversions * review: use type shorthand in wasm-smith * review: use more `Into` conversions * review: add aliases for heap types * review: add clarity parentheses * review: add and use more heap type aliases (in wasmparser) * review: use aliases... more * review: integrate with upstream * review: re-add full explicit matching in subtype check * fix: re-run rustfmt * review: handle shared peeking in wast * threads: check reference types for `shared`-ness In previous commits, we were unable to determine if a reference type was shared because concrete heap types point to a type elsewhere in the module that we weren't able to retrieve at the time the check was made. This change fixes that by plumbing a `TypeList` through to the right place and implementing `TypeList::is_shared`. This will still fail with a `todo!()` at the boundary of where shared-ness is implemented; once composite types gain shared flags that `todo!()` should go away. * threads: fix parsing ambiguities When parsing a shared global in the WebAssembly text format, one might run across the following text: `(global (shared anyref))`. In the current paradigm, this should parse to a `shared` global that contains an invalid `anyref` (i.e., `(ref null any)` in long-form, which is unshared); this should be parseable but result in a validation error. The correct form is `(global (shared (shared anyref)))`. This change fixes several issues related to this as well as refactoring some of the "short-hand"/"long-hand" parsing code. * threads: add heap type tests This change adds generated tests for all of the abstract heap types when they are `shared` and placed in `shared` globals. Since globals are the only thing we can mark shared now that can touch these types, they are a good vehicle for testing the parsing, encoding, and validation of all of this. Eventually the same must be done for concrete heap types once composite types (`func`, `struct`, `array`) can be marked shared. --- crates/wasm-encoder/src/core/types.rs | 217 +++++++-- crates/wasm-mutate/Cargo.toml | 2 +- crates/wasm-mutate/src/module.rs | 16 +- .../wasm-mutate/src/mutators/add_function.rs | 12 +- .../src/mutators/modify_const_exprs.rs | 10 +- .../wasm-mutate/src/mutators/peephole/dfg.rs | 4 +- .../src/mutators/peephole/eggsy/lang.rs | 12 +- .../wasm-mutate/src/mutators/snip_function.rs | 4 +- crates/wasm-mutate/src/mutators/translate.rs | 25 +- crates/wasm-smith/src/core.rs | 325 +++++++------ crates/wasm-smith/src/core/code_builder.rs | 85 ++-- crates/wasmparser/src/readers/core/types.rs | 444 +++++++++++------- crates/wasmparser/src/resources.rs | 1 - crates/wasmparser/src/validator.rs | 87 ++-- crates/wasmparser/src/validator/core.rs | 22 +- crates/wasmparser/src/validator/operators.rs | 22 +- crates/wasmparser/src/validator/types.rs | 160 ++++--- crates/wasmprinter/src/lib.rs | 35 +- crates/wast/src/component/binary.rs | 33 +- crates/wast/src/component/resolve.rs | 13 +- crates/wast/src/core/binary.rs | 106 ++--- crates/wast/src/core/types.rs | 203 ++++++-- crates/wit-component/Cargo.toml | 2 +- crates/wit-component/src/gc.rs | 29 +- crates/wit-component/src/linking.rs | 16 +- src/bin/wasm-tools/json_from_wast.rs | 41 +- .../global-heap-types.wast | 311 ++++++++++++ .../shared-everything-threads/global.wast | 1 - .../global-heap-types.wast.json | 317 +++++++++++++ .../global-heap-types.wast/0.print | 10 + .../global-heap-types.wast/12.print | 10 + .../global-heap-types.wast/16.print | 10 + .../global-heap-types.wast/20.print | 10 + .../global-heap-types.wast/24.print | 10 + .../global-heap-types.wast/28.print | 10 + .../global-heap-types.wast/32.print | 10 + .../global-heap-types.wast/36.print | 10 + .../global-heap-types.wast/4.print | 10 + .../global-heap-types.wast/40.print | 10 + .../global-heap-types.wast/44.print | 10 + .../global-heap-types.wast/8.print | 10 + .../global.wast.json | 44 +- 42 files changed, 1931 insertions(+), 788 deletions(-) create mode 100644 tests/local/shared-everything-threads/global-heap-types.wast create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/0.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/12.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/16.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/20.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/24.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/28.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/32.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/36.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/4.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/40.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/44.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/8.print diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs index 8b5f340bb6..0fa12f68fc 100644 --- a/crates/wasm-encoder/src/core/types.rs +++ b/crates/wasm-encoder/src/core/types.rs @@ -344,33 +344,91 @@ pub struct RefType { } impl RefType { - /// Alias for the `funcref` type in WebAssembly + /// Alias for the `anyref` type in WebAssembly. + pub const ANYREF: RefType = RefType { + nullable: true, + heap_type: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Any, + }, + }; + + /// Alias for the `anyref` type in WebAssembly. + pub const EQREF: RefType = RefType { + nullable: true, + heap_type: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Eq, + }, + }; + + /// Alias for the `funcref` type in WebAssembly. pub const FUNCREF: RefType = RefType { nullable: true, - heap_type: HeapType::Func, + heap_type: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Func, + }, }; - /// Alias for the `externref` type in WebAssembly + /// Alias for the `externref` type in WebAssembly. pub const EXTERNREF: RefType = RefType { nullable: true, - heap_type: HeapType::Extern, + heap_type: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Extern, + }, }; - /// Alias for the `exnref` type in WebAssembly + /// Alias for the `i31ref` type in WebAssembly. + pub const I31REF: RefType = RefType { + nullable: true, + heap_type: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::I31, + }, + }; + + /// Alias for the `arrayref` type in WebAssembly. + pub const ARRAYREF: RefType = RefType { + nullable: true, + heap_type: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Array, + }, + }; + + /// Alias for the `exnref` type in WebAssembly. pub const EXNREF: RefType = RefType { nullable: true, - heap_type: HeapType::Exn, + heap_type: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Exn, + }, }; + + /// Set the nullability of this reference type. + pub fn nullable(mut self, nullable: bool) -> Self { + self.nullable = nullable; + self + } } impl Encode for RefType { fn encode(&self, sink: &mut Vec) { if self.nullable { // Favor the original encodings of `funcref` and `externref` where - // possible + // possible. + use AbstractHeapType::*; match self.heap_type { - HeapType::Func => return sink.push(0x70), - HeapType::Extern => return sink.push(0x6f), + HeapType::Abstract { + shared: false, + ty: Func, + } => return sink.push(0x70), + HeapType::Abstract { + shared: false, + ty: Extern, + } => return sink.push(0x6f), _ => {} } } @@ -405,6 +463,78 @@ impl From for ValType { /// Part of the function references proposal. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum HeapType { + /// An abstract heap type; e.g., `anyref`. + Abstract { + /// Whether the type is shared. + shared: bool, + /// The actual heap type. + ty: AbstractHeapType, + }, + + /// A concrete Wasm-defined type at the given index. + Concrete(u32), +} + +impl HeapType { + /// Alias for the unshared `any` heap type. + pub const ANY: Self = Self::Abstract { + shared: false, + ty: AbstractHeapType::Any, + }; + + /// Alias for the unshared `func` heap type. + pub const FUNC: Self = Self::Abstract { + shared: false, + ty: AbstractHeapType::Func, + }; + + /// Alias for the unshared `extern` heap type. + pub const EXTERN: Self = Self::Abstract { + shared: false, + ty: AbstractHeapType::Extern, + }; + + /// Alias for the unshared `i31` heap type. + pub const I31: Self = Self::Abstract { + shared: false, + ty: AbstractHeapType::I31, + }; +} + +impl Encode for HeapType { + fn encode(&self, sink: &mut Vec) { + match self { + HeapType::Abstract { shared, ty } => { + if *shared { + sink.push(0x65); + } + ty.encode(sink); + } + // Note that this is encoded as a signed type rather than unsigned + // as it's decoded as an s33 + HeapType::Concrete(i) => i64::from(*i).encode(sink), + } + } +} + +#[cfg(feature = "wasmparser")] +impl TryFrom for HeapType { + type Error = (); + + fn try_from(heap_type: wasmparser::HeapType) -> Result { + Ok(match heap_type { + wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().ok_or(())?), + wasmparser::HeapType::Abstract { shared, ty } => HeapType::Abstract { + shared, + ty: ty.into(), + }, + }) + } +} + +/// An abstract heap type. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] +pub enum AbstractHeapType { /// Untyped (any) function. Func, @@ -455,53 +585,46 @@ pub enum HeapType { /// The abstract `noexn` heap type. NoExn, - - /// A concrete Wasm-defined type at the given index. - Concrete(u32), } -impl Encode for HeapType { +impl Encode for AbstractHeapType { fn encode(&self, sink: &mut Vec) { + use AbstractHeapType::*; match self { - HeapType::Func => sink.push(0x70), - HeapType::Extern => sink.push(0x6F), - HeapType::Any => sink.push(0x6E), - HeapType::None => sink.push(0x71), - HeapType::NoExtern => sink.push(0x72), - HeapType::NoFunc => sink.push(0x73), - HeapType::Eq => sink.push(0x6D), - HeapType::Struct => sink.push(0x6B), - HeapType::Array => sink.push(0x6A), - HeapType::I31 => sink.push(0x6C), - HeapType::Exn => sink.push(0x69), - HeapType::NoExn => sink.push(0x74), - // Note that this is encoded as a signed type rather than unsigned - // as it's decoded as an s33 - HeapType::Concrete(i) => i64::from(*i).encode(sink), + Func => sink.push(0x70), + Extern => sink.push(0x6F), + Any => sink.push(0x6E), + None => sink.push(0x71), + NoExtern => sink.push(0x72), + NoFunc => sink.push(0x73), + Eq => sink.push(0x6D), + Struct => sink.push(0x6B), + Array => sink.push(0x6A), + I31 => sink.push(0x6C), + Exn => sink.push(0x69), + NoExn => sink.push(0x74), } } } #[cfg(feature = "wasmparser")] -impl TryFrom for HeapType { - type Error = (); - - fn try_from(heap_type: wasmparser::HeapType) -> Result { - Ok(match heap_type { - wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().ok_or(())?), - wasmparser::HeapType::Func => HeapType::Func, - wasmparser::HeapType::Extern => HeapType::Extern, - wasmparser::HeapType::Any => HeapType::Any, - wasmparser::HeapType::None => HeapType::None, - wasmparser::HeapType::NoExtern => HeapType::NoExtern, - wasmparser::HeapType::NoFunc => HeapType::NoFunc, - wasmparser::HeapType::Eq => HeapType::Eq, - wasmparser::HeapType::Struct => HeapType::Struct, - wasmparser::HeapType::Array => HeapType::Array, - wasmparser::HeapType::I31 => HeapType::I31, - wasmparser::HeapType::Exn => HeapType::Exn, - wasmparser::HeapType::NoExn => HeapType::NoExn, - }) +impl From for AbstractHeapType { + fn from(value: wasmparser::AbstractHeapType) -> Self { + use wasmparser::AbstractHeapType::*; + match value { + Func => AbstractHeapType::Func, + Extern => AbstractHeapType::Extern, + Any => AbstractHeapType::Any, + None => AbstractHeapType::None, + NoExtern => AbstractHeapType::NoExtern, + NoFunc => AbstractHeapType::NoFunc, + Eq => AbstractHeapType::Eq, + Struct => AbstractHeapType::Struct, + Array => AbstractHeapType::Array, + I31 => AbstractHeapType::I31, + Exn => AbstractHeapType::Exn, + NoExn => AbstractHeapType::NoExn, + } } } diff --git a/crates/wasm-mutate/Cargo.toml b/crates/wasm-mutate/Cargo.toml index 6a96c9edfb..3a9385cdd1 100644 --- a/crates/wasm-mutate/Cargo.toml +++ b/crates/wasm-mutate/Cargo.toml @@ -14,7 +14,7 @@ workspace = true clap = { workspace = true, optional = true } thiserror = "1.0.28" wasmparser = { workspace = true } -wasm-encoder = { workspace = true } +wasm-encoder = { workspace = true, features = ["wasmparser"] } rand = { workspace = true } log = { workspace = true } egg = "0.6.0" diff --git a/crates/wasm-mutate/src/module.rs b/crates/wasm-mutate/src/module.rs index 87653dd76b..d6ef3846d9 100644 --- a/crates/wasm-mutate/src/module.rs +++ b/crates/wasm-mutate/src/module.rs @@ -80,19 +80,11 @@ pub fn map_ref_type(ref_ty: wasmparser::RefType) -> Result { Ok(RefType { nullable: ref_ty.is_nullable(), heap_type: match ref_ty.heap_type() { - wasmparser::HeapType::Func => HeapType::Func, - wasmparser::HeapType::Extern => HeapType::Extern, - wasmparser::HeapType::Any => HeapType::Any, - wasmparser::HeapType::None => HeapType::None, - wasmparser::HeapType::NoExtern => HeapType::NoExtern, - wasmparser::HeapType::NoFunc => HeapType::NoFunc, - wasmparser::HeapType::Eq => HeapType::Eq, - wasmparser::HeapType::Struct => HeapType::Struct, - wasmparser::HeapType::Array => HeapType::Array, - wasmparser::HeapType::I31 => HeapType::I31, - wasmparser::HeapType::Exn => HeapType::Exn, - wasmparser::HeapType::NoExn => HeapType::NoExn, wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().unwrap()), + wasmparser::HeapType::Abstract { shared, ty } => { + let ty = ty.into(); + HeapType::Abstract { shared, ty } + } }, }) } diff --git a/crates/wasm-mutate/src/mutators/add_function.rs b/crates/wasm-mutate/src/mutators/add_function.rs index 2de031fd21..7165f367bf 100644 --- a/crates/wasm-mutate/src/mutators/add_function.rs +++ b/crates/wasm-mutate/src/mutators/add_function.rs @@ -4,7 +4,7 @@ use super::Mutator; use crate::module::{PrimitiveTypeInfo, TypeInfo}; use crate::{Result, WasmMutate}; use rand::Rng; -use wasm_encoder::{HeapType, Instruction, Module}; +use wasm_encoder::{AbstractHeapType, HeapType, Instruction, Module}; /// Mutator that adds new, empty functions to a Wasm module. #[derive(Clone, Copy)] @@ -62,10 +62,16 @@ impl Mutator for AddFunctionMutator { func.instruction(&Instruction::V128Const(0)); } PrimitiveTypeInfo::FuncRef => { - func.instruction(&Instruction::RefNull(HeapType::Func)); + func.instruction(&Instruction::RefNull(HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Func, + })); } PrimitiveTypeInfo::ExternRef => { - func.instruction(&Instruction::RefNull(HeapType::Extern)); + func.instruction(&Instruction::RefNull(HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Extern, + })); } PrimitiveTypeInfo::Empty => unreachable!(), } diff --git a/crates/wasm-mutate/src/mutators/modify_const_exprs.rs b/crates/wasm-mutate/src/mutators/modify_const_exprs.rs index 7b7fb90559..173f53cfb3 100644 --- a/crates/wasm-mutate/src/mutators/modify_const_exprs.rs +++ b/crates/wasm-mutate/src/mutators/modify_const_exprs.rs @@ -124,8 +124,14 @@ impl<'cfg, 'wasm> Translator for InitTranslator<'cfg, 'wasm> { } else { f64::from_bits(self.config.rng().gen()) }), - T::FUNCREF => CE::ref_null(wasm_encoder::HeapType::Func), - T::EXTERNREF => CE::ref_null(wasm_encoder::HeapType::Extern), + T::FUNCREF => CE::ref_null(wasm_encoder::HeapType::Abstract { + shared: false, + ty: wasm_encoder::AbstractHeapType::Func, + }), + T::EXTERNREF => CE::ref_null(wasm_encoder::HeapType::Abstract { + shared: false, + ty: wasm_encoder::AbstractHeapType::Func, + }), T::Ref(_) => unimplemented!(), } } else { diff --git a/crates/wasm-mutate/src/mutators/peephole/dfg.rs b/crates/wasm-mutate/src/mutators/peephole/dfg.rs index c69391b386..a5e1ca2a51 100644 --- a/crates/wasm-mutate/src/mutators/peephole/dfg.rs +++ b/crates/wasm-mutate/src/mutators/peephole/dfg.rs @@ -758,12 +758,12 @@ impl<'a> DFGBuilder { } Operator::RefNull { - hty: wasmparser::HeapType::Extern, + hty: wasmparser::HeapType::EXTERN, } => { self.push_node(Lang::RefNull(RefType::Extern), idx); } Operator::RefNull { - hty: wasmparser::HeapType::Func, + hty: wasmparser::HeapType::FUNC, } => { self.push_node(Lang::RefNull(RefType::Func), idx); } diff --git a/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs b/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs index e5bb831bad..db4d9210c1 100644 --- a/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs +++ b/crates/wasm-mutate/src/mutators/peephole/eggsy/lang.rs @@ -3,7 +3,7 @@ use egg::Id; use std::fmt::{self, Display}; use std::str::FromStr; -use wasm_encoder::HeapType; +use wasm_encoder::{AbstractHeapType, HeapType}; /// This is a macro used to define the `Lang` enum. /// @@ -1072,8 +1072,14 @@ pub enum RefType { impl From for HeapType { fn from(rt: RefType) -> Self { match rt { - RefType::Func => HeapType::Func, - RefType::Extern => HeapType::Extern, + RefType::Func => HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Func, + }, + RefType::Extern => HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Extern, + }, } } } diff --git a/crates/wasm-mutate/src/mutators/snip_function.rs b/crates/wasm-mutate/src/mutators/snip_function.rs index 1ac67890b3..00f86e9264 100644 --- a/crates/wasm-mutate/src/mutators/snip_function.rs +++ b/crates/wasm-mutate/src/mutators/snip_function.rs @@ -61,10 +61,10 @@ impl Mutator for SnipMutator { f.instruction(&Instruction::V128Const(0)); } PrimitiveTypeInfo::FuncRef => { - f.instruction(&Instruction::RefNull(HeapType::Func)); + f.instruction(&Instruction::RefNull(HeapType::FUNC)); } PrimitiveTypeInfo::ExternRef => { - f.instruction(&Instruction::RefNull(HeapType::Extern)); + f.instruction(&Instruction::RefNull(HeapType::EXTERN)); } PrimitiveTypeInfo::Empty => { unreachable!() diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs index 5c5fbbb291..839879bc71 100644 --- a/crates/wasm-mutate/src/mutators/translate.rs +++ b/crates/wasm-mutate/src/mutators/translate.rs @@ -210,21 +210,16 @@ pub fn refty(t: &mut dyn Translator, ty: &wasmparser::RefType) -> Result Result { match ty { - wasmparser::HeapType::Func => Ok(HeapType::Func), - wasmparser::HeapType::Extern => Ok(HeapType::Extern), - wasmparser::HeapType::Any => Ok(HeapType::Any), - wasmparser::HeapType::None => Ok(HeapType::None), - wasmparser::HeapType::NoExtern => Ok(HeapType::NoExtern), - wasmparser::HeapType::NoFunc => Ok(HeapType::NoFunc), - wasmparser::HeapType::Eq => Ok(HeapType::Eq), - wasmparser::HeapType::Struct => Ok(HeapType::Struct), - wasmparser::HeapType::Array => Ok(HeapType::Array), - wasmparser::HeapType::I31 => Ok(HeapType::I31), - wasmparser::HeapType::Exn => Ok(HeapType::Exn), - wasmparser::HeapType::NoExn => Ok(HeapType::NoExn), wasmparser::HeapType::Concrete(i) => Ok(HeapType::Concrete( t.remap(Item::Type, i.as_module_index().unwrap())?, )), + wasmparser::HeapType::Abstract { shared, ty } => { + let ty = (*ty).into(); + Ok(HeapType::Abstract { + shared: *shared, + ty, + }) + } } } @@ -251,7 +246,11 @@ pub fn const_expr( match op { Operator::RefFunc { .. } | Operator::RefNull { - hty: wasmparser::HeapType::Func, + hty: + wasmparser::HeapType::Abstract { + ty: wasmparser::AbstractHeapType::Func, + .. + }, .. } | Operator::GlobalGet { .. } => {} diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index 263c294e28..ede06314e3 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -15,8 +15,8 @@ use std::ops::Range; use std::rc::Rc; use std::str::{self, FromStr}; use wasm_encoder::{ - ArrayType, BlockType, ConstExpr, ExportKind, FieldType, HeapType, RefType, StorageType, - StructType, ValType, + AbstractHeapType, ArrayType, BlockType, ConstExpr, ExportKind, FieldType, HeapType, RefType, + StorageType, StructType, ValType, }; pub(crate) use wasm_encoder::{GlobalType, MemoryType, TableType}; @@ -449,31 +449,69 @@ impl Module { } fn heap_type_is_sub_type(&self, a: HeapType, b: HeapType) -> bool { + use AbstractHeapType::*; use HeapType as HT; match (a, b) { (a, b) if a == b => true, - (HT::Eq | HT::I31 | HT::Struct | HT::Array | HT::None, HT::Any) => true, - (HT::I31 | HT::Struct | HT::Array | HT::None, HT::Eq) => true, - (HT::NoExtern, HT::Extern) => true, - (HT::NoFunc, HT::Func) => true, - (HT::None, HT::I31 | HT::Array | HT::Struct) => true, - - (HT::Concrete(a), HT::Eq | HT::Any) => matches!( - self.ty(a).composite_type, - CompositeType::Array(_) | CompositeType::Struct(_) - ), - - (HT::Concrete(a), HT::Struct) => { - matches!(self.ty(a).composite_type, CompositeType::Struct(_)) + ( + HT::Abstract { + shared: a_shared, + ty: a_ty, + }, + HT::Abstract { + shared: b_shared, + ty: b_ty, + }, + ) => { + a_shared == b_shared + && match (a_ty, b_ty) { + (Eq | I31 | Struct | Array | None, Any) => true, + (I31 | Struct | Array | None, Eq) => true, + (NoExtern, Extern) => true, + (NoFunc, Func) => true, + (None, I31 | Array | Struct) => true, + (NoExn, Exn) => true, + _ => false, + } } - (HT::Concrete(a), HT::Array) => { - matches!(self.ty(a).composite_type, CompositeType::Array(_)) + (HT::Concrete(a), HT::Abstract { shared, ty }) => { + if shared { + // TODO: handle shared + todo!("check shared-ness of concrete type"); + } + match ty { + Eq | Any => matches!( + self.ty(a).composite_type, + CompositeType::Array(_) | CompositeType::Struct(_) + ), + Struct => { + matches!(self.ty(a).composite_type, CompositeType::Struct(_)) + } + Array => { + matches!(self.ty(a).composite_type, CompositeType::Array(_)) + } + Func => { + matches!(self.ty(a).composite_type, CompositeType::Func(_)) + } + _ => false, + } } - (HT::Concrete(a), HT::Func) => { - matches!(self.ty(a).composite_type, CompositeType::Func(_)) + (HT::Abstract { shared, ty }, HT::Concrete(b)) => { + if shared { + // TODO: handle shared + todo!("check shared-ness of concrete type"); + } + match ty { + None => matches!( + self.ty(b).composite_type, + CompositeType::Array(_) | CompositeType::Struct(_) + ), + NoFunc => matches!(self.ty(b).composite_type, CompositeType::Func(_)), + _ => false, + } } (HT::Concrete(mut a), HT::Concrete(b)) => loop { @@ -486,33 +524,6 @@ impl Module { return false; } }, - - (HT::None, HT::Concrete(b)) => matches!( - self.ty(b).composite_type, - CompositeType::Array(_) | CompositeType::Struct(_) - ), - - (HT::NoFunc, HT::Concrete(b)) => { - matches!(self.ty(b).composite_type, CompositeType::Func(_)) - } - - (HT::NoExn, HT::Exn) => true, - - // Nothing else matches. (Avoid full wildcard matches so that - // adding/modifying variants is easier in the future.) - (HT::Concrete(_), _) - | (HT::Func, _) - | (HT::Extern, _) - | (HT::Any, _) - | (HT::None, _) - | (HT::NoExtern, _) - | (HT::NoFunc, _) - | (HT::Eq, _) - | (HT::Struct, _) - | (HT::Array, _) - | (HT::I31, _) - | (HT::Exn, _) - | (HT::NoExn, _) => false, } } @@ -731,26 +742,40 @@ impl Module { use HeapType as HT; let mut choices = vec![ty]; match ty { - HT::Any => { - choices.extend([HT::Eq, HT::Struct, HT::Array, HT::I31, HT::None]); - choices.extend(self.array_types.iter().copied().map(HT::Concrete)); - choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); - } - HT::Eq => { - choices.extend([HT::Struct, HT::Array, HT::I31, HT::None]); - choices.extend(self.array_types.iter().copied().map(HT::Concrete)); - choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); - } - HT::Struct => { - choices.extend([HT::Struct, HT::None]); - choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); - } - HT::Array => { - choices.extend([HT::Array, HT::None]); - choices.extend(self.array_types.iter().copied().map(HT::Concrete)); - } - HT::I31 => { - choices.push(HT::None); + HT::Abstract { shared, ty } => { + use AbstractHeapType::*; + let ht = |ty| HT::Abstract { shared, ty }; + match ty { + Any => { + choices.extend([ht(Eq), ht(Struct), ht(Array), ht(I31), ht(None)]); + choices.extend(self.array_types.iter().copied().map(HT::Concrete)); + choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); + } + Eq => { + choices.extend([ht(Struct), ht(Array), ht(I31), ht(None)]); + choices.extend(self.array_types.iter().copied().map(HT::Concrete)); + choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); + } + Struct => { + choices.extend([ht(Struct), ht(None)]); + choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); + } + Array => { + choices.extend([ht(Array), ht(None)]); + choices.extend(self.array_types.iter().copied().map(HT::Concrete)); + } + I31 => { + choices.push(ht(None)); + } + Func => { + choices.extend(self.func_types.iter().copied().map(HT::Concrete)); + choices.push(ht(NoFunc)); + } + Extern => { + choices.push(ht(NoExtern)); + } + Exn | NoExn | None | NoExtern | NoFunc => {} + } } HT::Concrete(idx) => { if let Some(subs) = self.super_to_sub_types.get(&idx) { @@ -762,9 +787,15 @@ impl Module { .map(|ty| &ty.composite_type) { Some(CompositeType::Array(_)) | Some(CompositeType::Struct(_)) => { - choices.push(HT::None) + choices.push(HT::Abstract { + shared: false, // TODO: handle shared + ty: AbstractHeapType::None, + }) } - Some(CompositeType::Func(_)) => choices.push(HT::NoFunc), + Some(CompositeType::Func(_)) => choices.push(HT::Abstract { + shared: false, // TODO: handle shared + ty: AbstractHeapType::NoFunc, + }), None => { // The referenced type might be part of this same rec // group we are currently generating, but not generated @@ -774,14 +805,6 @@ impl Module { } } } - HT::Func => { - choices.extend(self.func_types.iter().copied().map(HT::Concrete)); - choices.push(HT::NoFunc); - } - HT::Extern => { - choices.push(HT::NoExtern); - } - HT::Exn | HT::NoExn | HT::None | HT::NoExtern | HT::NoFunc => {} } Ok(*u.choose(&choices)?) } @@ -848,35 +871,58 @@ impl Module { use HeapType as HT; let mut choices = vec![ty]; match ty { - HT::None => { - choices.extend([HT::Any, HT::Eq, HT::Struct, HT::Array, HT::I31]); - choices.extend(self.array_types.iter().copied().map(HT::Concrete)); - choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); - } - HT::NoExtern => { - choices.push(HT::Extern); - } - HT::NoFunc => { - choices.extend(self.func_types.iter().copied().map(HT::Concrete)); - choices.push(HT::Func); - } - HT::NoExn => { - choices.push(HT::Exn); + HT::Abstract { shared, ty } => { + use AbstractHeapType::*; + let ht = |ty| HT::Abstract { shared, ty }; + match ty { + None => { + choices.extend([ht(Any), ht(Eq), ht(Struct), ht(Array), ht(I31)]); + choices.extend(self.array_types.iter().copied().map(HT::Concrete)); + choices.extend(self.struct_types.iter().copied().map(HT::Concrete)); + } + NoExtern => { + choices.push(ht(Extern)); + } + NoFunc => { + choices.extend(self.func_types.iter().copied().map(HT::Concrete)); + choices.push(ht(Func)); + } + NoExn => { + choices.push(ht(Exn)); + } + Struct | Array | I31 => { + choices.extend([ht(Any), ht(Eq)]); + } + Eq => { + choices.push(ht(Any)); + } + Exn | Any | Func | Extern => {} + } } HT::Concrete(mut idx) => { + // TODO: handle shared + let ht = |ty| HT::Abstract { shared: false, ty }; match &self .types .get(usize::try_from(idx).unwrap()) .map(|ty| &ty.composite_type) { Some(CompositeType::Array(_)) => { - choices.extend([HT::Any, HT::Eq, HT::Array]); + choices.extend([ + ht(AbstractHeapType::Any), + ht(AbstractHeapType::Eq), + ht(AbstractHeapType::Array), + ]); } Some(CompositeType::Func(_)) => { - choices.push(HT::Func); + choices.push(ht(AbstractHeapType::Func)); } Some(CompositeType::Struct(_)) => { - choices.extend([HT::Any, HT::Eq, HT::Struct]); + choices.extend([ + ht(AbstractHeapType::Any), + ht(AbstractHeapType::Eq), + ht(AbstractHeapType::Struct), + ]); } None => { // Same as in `arbitrary_matching_heap_type`: this was a @@ -896,13 +942,6 @@ impl Module { idx = supertype; } } - HT::Struct | HT::Array | HT::I31 => { - choices.extend([HT::Any, HT::Eq]); - } - HT::Eq => { - choices.push(HT::Any); - } - HT::Exn | HT::Any | HT::Func | HT::Extern => {} } Ok(*u.choose(&choices)?) } @@ -972,27 +1011,23 @@ impl Module { return Ok(HeapType::Concrete(idx)); } - let mut choices = vec![HeapType::Func, HeapType::Extern]; + use AbstractHeapType::*; + let mut choices = vec![Func, Extern]; if self.config.exceptions_enabled { - choices.push(HeapType::Exn); + choices.push(Exn); } if self.config.gc_enabled { choices.extend( - [ - HeapType::Any, - HeapType::None, - HeapType::NoExtern, - HeapType::NoFunc, - HeapType::Eq, - HeapType::Struct, - HeapType::Array, - HeapType::I31, - ] - .iter() - .copied(), + [Any, None, NoExtern, NoFunc, Eq, Struct, Array, I31] + .iter() + .copied(), ); } - u.choose(&choices).copied() + + Ok(HeapType::Abstract { + shared: false, // TODO: turn on shared attribute with shared-everything-threads. + ty: *u.choose(&choices)?, + }) } fn arbitrary_func_type(&mut self, u: &mut Unstructured) -> Result> { @@ -1572,7 +1607,10 @@ impl Module { } match ty.heap_type { - HeapType::Func if num_funcs > 0 => { + HeapType::Abstract { + ty: AbstractHeapType::Func, + .. + } if num_funcs > 0 => { choices.push(Box::new(move |u, _| { let func = u.int_in_range(0..=num_funcs - 1)?; Ok(ConstExpr::ref_func(func)) @@ -1636,22 +1674,31 @@ impl Module { #[cfg(feature = "wasmparser")] fn _required_exports(&mut self, u: &mut Unstructured, example_module: &[u8]) -> Result<()> { fn convert_heap_type(ty: &wasmparser::HeapType) -> HeapType { + use wasmparser::AbstractHeapType::*; match ty { wasmparser::HeapType::Concrete(_) => { panic!("Unable to handle concrete types in exports") } - wasmparser::HeapType::Func => HeapType::Func, - wasmparser::HeapType::Extern => HeapType::Extern, - wasmparser::HeapType::Any => HeapType::Any, - wasmparser::HeapType::None => HeapType::None, - wasmparser::HeapType::NoExtern => HeapType::NoExtern, - wasmparser::HeapType::NoFunc => HeapType::NoFunc, - wasmparser::HeapType::Eq => HeapType::Eq, - wasmparser::HeapType::Struct => HeapType::Struct, - wasmparser::HeapType::Array => HeapType::Array, - wasmparser::HeapType::I31 => HeapType::I31, - wasmparser::HeapType::Exn => HeapType::Exn, - wasmparser::HeapType::NoExn => HeapType::NoExn, + wasmparser::HeapType::Abstract { shared, ty } => { + let ty = match ty { + Func => AbstractHeapType::Func, + Extern => AbstractHeapType::Extern, + Any => AbstractHeapType::Any, + None => AbstractHeapType::None, + NoExtern => AbstractHeapType::NoExtern, + NoFunc => AbstractHeapType::NoFunc, + Eq => AbstractHeapType::Eq, + Struct => AbstractHeapType::Struct, + Array => AbstractHeapType::Array, + I31 => AbstractHeapType::I31, + Exn => AbstractHeapType::Exn, + NoExn => AbstractHeapType::NoExn, + }; + HeapType::Abstract { + shared: *shared, + ty, + } + } } } @@ -2016,7 +2063,10 @@ impl Module { let mut func_candidates = Vec::new(); if can_use_function_list { match ty.heap_type { - HeapType::Func => { + HeapType::Abstract { + ty: AbstractHeapType::Func, + .. + } => { func_candidates.extend(0..self.funcs.len() as u32); } HeapType::Concrete(ty) => { @@ -2450,21 +2500,14 @@ pub(crate) fn configured_valtypes(config: &Config) -> Vec { // contain a non-null self-reference are also impossible to create). true, ] { - for heap_type in [ - HeapType::Any, - HeapType::Eq, - HeapType::I31, - HeapType::Array, - HeapType::Struct, - HeapType::None, - HeapType::Func, - HeapType::NoFunc, - HeapType::Extern, - HeapType::NoExtern, + use AbstractHeapType::*; + for ty in [ + Any, Eq, I31, Array, Struct, None, Func, NoFunc, Extern, NoExtern, ] { valtypes.push(ValType::Ref(RefType { nullable, - heap_type, + // TODO: handle shared + heap_type: HeapType::Abstract { shared: false, ty }, })); } } diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 0c67b6a998..aef756b402 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -7,8 +7,8 @@ use arbitrary::{Result, Unstructured}; use std::collections::{BTreeMap, BTreeSet}; use std::rc::Rc; use wasm_encoder::{ - ArrayType, BlockType, Catch, ConstExpr, ExportKind, FieldType, GlobalType, HeapType, MemArg, - RefType, StorageType, StructType, + AbstractHeapType, ArrayType, BlockType, Catch, ConstExpr, ExportKind, FieldType, GlobalType, + HeapType, MemArg, RefType, StorageType, StructType, }; mod no_traps; @@ -2301,18 +2301,12 @@ fn br_on_null( if !module.types.is_empty() && u.arbitrary()? { HeapType::Concrete(u.int_in_range(0..=u32::try_from(module.types.len()).unwrap())?) } else { - *u.choose(&[ - HeapType::Func, - HeapType::Extern, - HeapType::Any, - HeapType::None, - HeapType::NoExtern, - HeapType::NoFunc, - HeapType::Eq, - HeapType::Struct, - HeapType::Array, - HeapType::I31, - ])? + use AbstractHeapType::*; + let ty = *u.choose(&[ + Func, Extern, Any, None, NoExtern, NoFunc, Eq, Struct, Array, I31, + ])?; + // TODO: handle shared + HeapType::Abstract { shared: false, ty } } } }; @@ -5384,18 +5378,23 @@ fn ref_null( choices.push(RefType::EXNREF); } if module.config.gc_enabled { + use AbstractHeapType::*; let r = |heap_type| RefType { nullable: true, heap_type, }; - choices.push(r(HeapType::Any)); - choices.push(r(HeapType::Eq)); - choices.push(r(HeapType::Array)); - choices.push(r(HeapType::Struct)); - choices.push(r(HeapType::I31)); - choices.push(r(HeapType::None)); - choices.push(r(HeapType::NoFunc)); - choices.push(r(HeapType::NoExtern)); + let a = |abstract_heap_type| HeapType::Abstract { + shared: false, // TODO: handle shared + ty: abstract_heap_type, + }; + choices.push(r(a(Any))); + choices.push(r(a(Eq))); + choices.push(r(a(Array))); + choices.push(r(a(Struct))); + choices.push(r(a(I31))); + choices.push(r(a(None))); + choices.push(r(a(NoFunc))); + choices.push(r(a(NoExtern))); for i in 0..module.types.len() { let i = u32::try_from(i).unwrap(); choices.push(r(HeapType::Concrete(i))); @@ -5457,10 +5456,7 @@ fn ref_as_non_null( #[inline] fn ref_eq_valid(module: &Module, builder: &mut CodeBuilder) -> bool { - let eq_ref = ValType::Ref(RefType { - nullable: true, - heap_type: HeapType::Eq, - }); + let eq_ref = ValType::Ref(RefType::EQREF); module.config.gc_enabled && builder.types_on_stack(module, &[eq_ref, eq_ref]) } @@ -6359,14 +6355,7 @@ fn array_set( #[inline] fn array_len_valid(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.gc_enabled - && builder.type_on_stack( - module, - ValType::Ref(RefType { - nullable: true, - heap_type: HeapType::Array, - }), - ) + module.config.gc_enabled && builder.type_on_stack(module, ValType::Ref(RefType::ARRAYREF)) } fn array_len( @@ -6574,10 +6563,7 @@ fn ref_i31( instructions: &mut Vec, ) -> Result<()> { builder.pop_operand(); - builder.push_operand(Some(ValType::Ref(RefType { - nullable: false, - heap_type: HeapType::I31, - }))); + builder.push_operand(Some(ValType::Ref(RefType::I31REF))); instructions.push(Instruction::RefI31); Ok(()) } @@ -6617,7 +6603,7 @@ fn any_convert_extern_valid(module: &Module, builder: &mut CodeBuilder) -> bool module, ValType::Ref(RefType { nullable: true, - heap_type: HeapType::Extern, + heap_type: HeapType::EXTERN, }), ) } @@ -6634,7 +6620,7 @@ fn any_convert_extern( }; builder.push_operand(Some(ValType::Ref(RefType { nullable, - heap_type: HeapType::Any, + heap_type: HeapType::ANY, }))); instructions.push(Instruction::AnyConvertExtern); Ok(()) @@ -6642,14 +6628,7 @@ fn any_convert_extern( #[inline] fn extern_convert_any_valid(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.gc_enabled - && builder.type_on_stack( - module, - ValType::Ref(RefType { - nullable: true, - heap_type: HeapType::Any, - }), - ) + module.config.gc_enabled && builder.type_on_stack(module, ValType::Ref(RefType::ANYREF)) } fn extern_convert_any( @@ -6662,10 +6641,12 @@ fn extern_convert_any( None => u.arbitrary()?, Some(r) => r.nullable, }; - builder.push_operand(Some(ValType::Ref(RefType { - nullable, - heap_type: HeapType::Extern, - }))); + let ty = if nullable { + RefType::EXTERNREF.nullable(true) + } else { + RefType::EXTERNREF + }; + builder.push_operand(Some(ValType::Ref(ty))); instructions.push(Instruction::ExternConvertAny); Ok(()) } diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs index 8df5c03d2b..eb1b253d48 100644 --- a/crates/wasmparser/src/readers/core/types.rs +++ b/crates/wasmparser/src/readers/core/types.rs @@ -570,6 +570,11 @@ impl CompositeType { _ => panic!("not a struct"), } } + + /// Is the composite type `shared`? + pub fn is_shared(&self) -> bool { + todo!("shared composite types are not yet implemented") + } } /// Represents a type of a function in a WebAssembly module. @@ -818,15 +823,6 @@ impl ValType { } } - /// Whether the type is `shared`. - pub fn is_shared(&self) -> bool { - match *self { - Self::I32 | Self::I64 | Self::F32 | Self::F64 | Self::V128 => true, - // TODO: parsing of `shared` refs is not yet implemented. - Self::Ref(_) => false, - } - } - /// Maps any `UnpackedIndex` via the specified closure. #[cfg(feature = "validate")] pub(crate) fn remap_indices( @@ -857,27 +853,29 @@ impl ValType { /// The GC proposal introduces heap types: any, eq, i31, struct, array, /// nofunc, noextern, none. // -// RefType is a bit-packed enum that fits in a `u24` aka `[u8; 3]`. -// Note that its content is opaque (and subject to change), but its API -// is stable. +// RefType is a bit-packed enum that fits in a `u24` aka `[u8; 3]`. Note that +// its content is opaque (and subject to change), but its API is stable. // // It has the following internal structure: // // ``` // [nullable:u1 concrete==1:u1 index:u22] -// [nullable:u1 concrete==0:u1 abstype:u4 (unused):u18] +// [nullable:u1 concrete==0:u1 shared:u1 abstype:u4 (unused):u17] // ``` // // Where // // - `nullable` determines nullability of the ref, // -// - `concrete` determines if the ref is of a dynamically defined type -// with an index (encoded in a following bit-packing section) or of a -// known fixed type, +// - `concrete` determines if the ref is of a dynamically defined type with an +// index (encoded in a following bit-packing section) or of a known fixed +// type, // // - `index` is the type index, // +// - `shared` determines if the ref is shared, but only if it is not concrete in +// which case we would need to examine the type at the concrete index, +// // - `abstype` is an enumeration of abstract types: // // ``` @@ -903,33 +901,31 @@ pub struct RefType([u8; 3]); impl fmt::Debug for RefType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match (self.is_nullable(), self.heap_type()) { - (true, HeapType::Any) => write!(f, "anyref"), - (false, HeapType::Any) => write!(f, "(ref any)"), - (true, HeapType::None) => write!(f, "nullref"), - (false, HeapType::None) => write!(f, "(ref none)"), - (true, HeapType::NoExtern) => write!(f, "nullexternref"), - (false, HeapType::NoExtern) => write!(f, "(ref noextern)"), - (true, HeapType::NoFunc) => write!(f, "nullfuncref"), - (false, HeapType::NoFunc) => write!(f, "(ref nofunc)"), - (true, HeapType::Eq) => write!(f, "eqref"), - (false, HeapType::Eq) => write!(f, "(ref eq)"), - (true, HeapType::Struct) => write!(f, "structref"), - (false, HeapType::Struct) => write!(f, "(ref struct)"), - (true, HeapType::Array) => write!(f, "arrayref"), - (false, HeapType::Array) => write!(f, "(ref array)"), - (true, HeapType::I31) => write!(f, "i31ref"), - (false, HeapType::I31) => write!(f, "(ref i31)"), - (true, HeapType::Extern) => write!(f, "externref"), - (false, HeapType::Extern) => write!(f, "(ref extern)"), - (true, HeapType::Func) => write!(f, "funcref"), - (false, HeapType::Func) => write!(f, "(ref func)"), - (true, HeapType::Exn) => write!(f, "exnref"), - (false, HeapType::Exn) => write!(f, "(ref exn)"), - (true, HeapType::NoExn) => write!(f, "nullexnref"), - (false, HeapType::NoExn) => write!(f, "(ref noexn)"), - (true, HeapType::Concrete(idx)) => write!(f, "(ref null {idx})"), - (false, HeapType::Concrete(idx)) => write!(f, "(ref {idx})"), + let heap_type = self.heap_type(); + match heap_type { + // All abstract types follow the same general pattern. + HeapType::Abstract { shared, ty } => { + let nullable = self.is_nullable(); + + let name = ty.as_str(nullable); + match (nullable, shared) { + // Print the shortened form if nullable; i.e., `*ref`. + (true, true) => write!(f, "(shared {}ref)", name), + (true, false) => write!(f, "{}ref", name), + // Print the long form otherwise; i.e., `(ref *)`. + (false, true) => write!(f, "(ref (shared {}))", name), + (false, false) => write!(f, "(ref {})", name), + } + } + // Handle concrete types separately; they always use the long form + // and don't show `shared`-ness. + HeapType::Concrete(index) => { + if self.is_nullable() { + write!(f, "(ref null {})", index) + } else { + write!(f, "(ref {})", index) + } + } } } } @@ -967,19 +963,20 @@ impl RefType { const CONCRETE_BIT: u32 = 1 << 22; // The `abstype` field is valid only when `concrete == 0`. - const ABSTYPE_MASK: u32 = 0b1111 << 18; - const ANY_ABSTYPE: u32 = 0b1111 << 18; - const EQ_ABSTYPE: u32 = 0b1101 << 18; - const I31_ABSTYPE: u32 = 0b1000 << 18; - const STRUCT_ABSTYPE: u32 = 0b1001 << 18; - const ARRAY_ABSTYPE: u32 = 0b1100 << 18; - const FUNC_ABSTYPE: u32 = 0b0101 << 18; - const NOFUNC_ABSTYPE: u32 = 0b0100 << 18; - const EXTERN_ABSTYPE: u32 = 0b0011 << 18; - const NOEXTERN_ABSTYPE: u32 = 0b0010 << 18; - const EXN_ABSTYPE: u32 = 0b0001 << 18; - const NOEXN_ABSTYPE: u32 = 0b1110 << 18; - const NONE_ABSTYPE: u32 = 0b0000 << 18; + const SHARED_BIT: u32 = 1 << 21; + const ABSTYPE_MASK: u32 = 0b1111 << 17; + const ANY_ABSTYPE: u32 = 0b1111 << 17; + const EQ_ABSTYPE: u32 = 0b1101 << 17; + const I31_ABSTYPE: u32 = 0b1000 << 17; + const STRUCT_ABSTYPE: u32 = 0b1001 << 17; + const ARRAY_ABSTYPE: u32 = 0b1100 << 17; + const FUNC_ABSTYPE: u32 = 0b0101 << 17; + const NOFUNC_ABSTYPE: u32 = 0b0100 << 17; + const EXTERN_ABSTYPE: u32 = 0b0011 << 17; + const NOEXTERN_ABSTYPE: u32 = 0b0010 << 17; + const EXN_ABSTYPE: u32 = 0b0001 << 17; + const NOEXN_ABSTYPE: u32 = 0b1110 << 17; + const NONE_ABSTYPE: u32 = 0b0000 << 17; // The `index` is valid only when `concrete == 1`. const INDEX_MASK: u32 = (1 << 22) - 1; @@ -1127,21 +1124,27 @@ impl RefType { /// Returns `None` when the heap type's type index (if any) is beyond this /// crate's implementation limits and therefore is not representable. pub fn new(nullable: bool, heap_type: HeapType) -> Option { - let nullable32 = Self::NULLABLE_BIT * (nullable as u32); + let base32 = Self::NULLABLE_BIT * (nullable as u32); match heap_type { HeapType::Concrete(index) => Some(RefType::concrete(nullable, index.pack()?)), - HeapType::Func => Some(Self::from_u32(nullable32 | Self::FUNC_ABSTYPE)), - HeapType::Extern => Some(Self::from_u32(nullable32 | Self::EXTERN_ABSTYPE)), - HeapType::Any => Some(Self::from_u32(nullable32 | Self::ANY_ABSTYPE)), - HeapType::None => Some(Self::from_u32(nullable32 | Self::NONE_ABSTYPE)), - HeapType::NoExtern => Some(Self::from_u32(nullable32 | Self::NOEXTERN_ABSTYPE)), - HeapType::NoFunc => Some(Self::from_u32(nullable32 | Self::NOFUNC_ABSTYPE)), - HeapType::Eq => Some(Self::from_u32(nullable32 | Self::EQ_ABSTYPE)), - HeapType::Struct => Some(Self::from_u32(nullable32 | Self::STRUCT_ABSTYPE)), - HeapType::Array => Some(Self::from_u32(nullable32 | Self::ARRAY_ABSTYPE)), - HeapType::I31 => Some(Self::from_u32(nullable32 | Self::I31_ABSTYPE)), - HeapType::Exn => Some(Self::from_u32(nullable32 | Self::EXN_ABSTYPE)), - HeapType::NoExn => Some(Self::from_u32(nullable32 | Self::NOEXN_ABSTYPE)), + HeapType::Abstract { shared, ty } => { + use AbstractHeapType::*; + let base32 = base32 | (Self::SHARED_BIT * (shared as u32)); + match ty { + Func => Some(Self::from_u32(base32 | Self::FUNC_ABSTYPE)), + Extern => Some(Self::from_u32(base32 | Self::EXTERN_ABSTYPE)), + Any => Some(Self::from_u32(base32 | Self::ANY_ABSTYPE)), + None => Some(Self::from_u32(base32 | Self::NONE_ABSTYPE)), + NoExtern => Some(Self::from_u32(base32 | Self::NOEXTERN_ABSTYPE)), + NoFunc => Some(Self::from_u32(base32 | Self::NOFUNC_ABSTYPE)), + Eq => Some(Self::from_u32(base32 | Self::EQ_ABSTYPE)), + Struct => Some(Self::from_u32(base32 | Self::STRUCT_ABSTYPE)), + Array => Some(Self::from_u32(base32 | Self::ARRAY_ABSTYPE)), + I31 => Some(Self::from_u32(base32 | Self::I31_ABSTYPE)), + Exn => Some(Self::from_u32(base32 | Self::EXN_ABSTYPE)), + NoExn => Some(Self::from_u32(base32 | Self::NOEXN_ABSTYPE)), + } + } } } @@ -1193,7 +1196,7 @@ impl RefType { !self.is_concrete_type_ref() && self.abstype() == Self::EXTERN_ABSTYPE } - /// Is this the abstract untyped array refrence type aka `(ref null + /// Is this the abstract untyped array reference type aka `(ref null /// array)` aka `arrayref`? pub const fn is_array_ref(&self) -> bool { !self.is_concrete_type_ref() && self.abstype() == Self::ARRAY_ABSTYPE @@ -1220,27 +1223,39 @@ impl RefType { Self::from_u32(self.as_u32() | Self::NULLABLE_BIT) } + /// Get the shared version of this ref type as long as it is abstract. + pub const fn shared(&self) -> Option { + if self.is_concrete_type_ref() { + None + } else { + Some(Self::from_u32(self.as_u32() | Self::SHARED_BIT)) + } + } + /// Get the heap type that this is a reference to. pub fn heap_type(&self) -> HeapType { let s = self.as_u32(); if self.is_concrete_type_ref() { HeapType::Concrete(self.type_index().unwrap().unpack()) } else { - match s & Self::ABSTYPE_MASK { - Self::FUNC_ABSTYPE => HeapType::Func, - Self::EXTERN_ABSTYPE => HeapType::Extern, - Self::ANY_ABSTYPE => HeapType::Any, - Self::NONE_ABSTYPE => HeapType::None, - Self::NOEXTERN_ABSTYPE => HeapType::NoExtern, - Self::NOFUNC_ABSTYPE => HeapType::NoFunc, - Self::EQ_ABSTYPE => HeapType::Eq, - Self::STRUCT_ABSTYPE => HeapType::Struct, - Self::ARRAY_ABSTYPE => HeapType::Array, - Self::I31_ABSTYPE => HeapType::I31, - Self::EXN_ABSTYPE => HeapType::Exn, - Self::NOEXN_ABSTYPE => HeapType::NoExn, + use AbstractHeapType::*; + let shared = s & Self::SHARED_BIT != 0; + let ty = match s & Self::ABSTYPE_MASK { + Self::FUNC_ABSTYPE => Func, + Self::EXTERN_ABSTYPE => Extern, + Self::ANY_ABSTYPE => Any, + Self::NONE_ABSTYPE => None, + Self::NOEXTERN_ABSTYPE => NoExtern, + Self::NOFUNC_ABSTYPE => NoFunc, + Self::EQ_ABSTYPE => Eq, + Self::STRUCT_ABSTYPE => Struct, + Self::ARRAY_ABSTYPE => Array, + Self::I31_ABSTYPE => I31, + Self::EXN_ABSTYPE => Exn, + Self::NOEXN_ABSTYPE => NoExn, _ => unreachable!(), - } + }; + HeapType::Abstract { shared, ty } } } @@ -1248,33 +1263,72 @@ impl RefType { // the indexes stubbed out. #[cfg(feature = "validate")] pub(crate) fn wat(&self) -> &'static str { - match (self.is_nullable(), self.heap_type()) { - (true, HeapType::Func) => "funcref", - (true, HeapType::Extern) => "externref", - (true, HeapType::Concrete(_)) => "(ref null $type)", - (true, HeapType::Any) => "anyref", - (true, HeapType::None) => "nullref", - (true, HeapType::NoExtern) => "nullexternref", - (true, HeapType::NoFunc) => "nullfuncref", - (true, HeapType::Eq) => "eqref", - (true, HeapType::Struct) => "structref", - (true, HeapType::Array) => "arrayref", - (true, HeapType::I31) => "i31ref", - (true, HeapType::Exn) => "exnref", - (true, HeapType::NoExn) => "nullexnref", - (false, HeapType::Func) => "(ref func)", - (false, HeapType::Extern) => "(ref extern)", - (false, HeapType::Concrete(_)) => "(ref $type)", - (false, HeapType::Any) => "(ref any)", - (false, HeapType::None) => "(ref none)", - (false, HeapType::NoExtern) => "(ref noextern)", - (false, HeapType::NoFunc) => "(ref nofunc)", - (false, HeapType::Eq) => "(ref eq)", - (false, HeapType::Struct) => "(ref struct)", - (false, HeapType::Array) => "(ref array)", - (false, HeapType::I31) => "(ref i31)", - (false, HeapType::Exn) => "(ref exn)", - (false, HeapType::NoExn) => "(ref noexn)", + let nullable = self.is_nullable(); + match self.heap_type() { + HeapType::Abstract { shared, ty } => { + use AbstractHeapType::*; + match (shared, nullable, ty) { + // Shared and nullable. + (true, true, Func) => "(shared funcref)", + (true, true, Extern) => "(shared externref)", + (true, true, Any) => "(shared anyref)", + (true, true, None) => "(shared nullref)", + (true, true, NoExtern) => "(shared nullexternref)", + (true, true, NoFunc) => "(shared nullfuncref)", + (true, true, Eq) => "(shared eqref)", + (true, true, Struct) => "(shared structref)", + (true, true, Array) => "(shared arrayref)", + (true, true, I31) => "(shared i31ref)", + (true, true, Exn) => "(shared exnref)", + (true, true, NoExn) => "(shared nullexnref)", + // Unshared but nullable. + (false, true, Func) => "funcref", + (false, true, Extern) => "externref", + (false, true, Any) => "anyref", + (false, true, None) => "nullref", + (false, true, NoExtern) => "nullexternref", + (false, true, NoFunc) => "nullfuncref", + (false, true, Eq) => "eqref", + (false, true, Struct) => "structref", + (false, true, Array) => "arrayref", + (false, true, I31) => "i31ref", + (false, true, Exn) => "exnref", + (false, true, NoExn) => "nullexnref", + // Shared but not nullable. + (true, false, Func) => "(ref (shared func))", + (true, false, Extern) => "(ref (shared extern))", + (true, false, Any) => "(ref (shared any))", + (true, false, None) => "(ref (shared none))", + (true, false, NoExtern) => "(ref (shared noextern))", + (true, false, NoFunc) => "(ref (shared nofunc))", + (true, false, Eq) => "(ref (shared eq))", + (true, false, Struct) => "(ref (shared struct))", + (true, false, Array) => "(ref (shared array))", + (true, false, I31) => "(ref (shared i31))", + (true, false, Exn) => "(ref (shared exn))", + (true, false, NoExn) => "(ref (shared noexn))", + // Neither shared nor nullable. + (false, false, Func) => "(ref func)", + (false, false, Extern) => "(ref extern)", + (false, false, Any) => "(ref any)", + (false, false, None) => "(ref none)", + (false, false, NoExtern) => "(ref noextern)", + (false, false, NoFunc) => "(ref nofunc)", + (false, false, Eq) => "(ref eq)", + (false, false, Struct) => "(ref struct)", + (false, false, Array) => "(ref array)", + (false, false, I31) => "(ref i31)", + (false, false, Exn) => "(ref exn)", + (false, false, NoExn) => "(ref noexn)", + } + } + HeapType::Concrete(_) => { + if nullable { + "(ref null $type)" + } else { + "(ref $type)" + } + } } } } @@ -1282,11 +1336,38 @@ impl RefType { /// A heap type. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub enum HeapType { + /// An abstract heap type; e.g., `anyref`. + Abstract { + /// Whether the type is shared. + /// + /// Introduced in the shared-everything-threads proposal. + shared: bool, + /// The actual heap type. + ty: AbstractHeapType, + }, /// A concrete, user-defined type. /// /// Introduced in the function-references proposal. Concrete(UnpackedIndex), +} +impl HeapType { + /// Alias for an unshared `func` heap type. + pub const FUNC: Self = Self::Abstract { + shared: false, + ty: AbstractHeapType::Func, + }; + + /// Alias for an unshared `extern` heap type. + pub const EXTERN: Self = Self::Abstract { + shared: false, + ty: AbstractHeapType::Extern, + }; +} + +/// An abstract heap type. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum AbstractHeapType { /// The abstract, untyped (any) function. /// /// Introduced in the references-types proposal. @@ -1369,6 +1450,30 @@ pub enum HeapType { NoExn, } +impl AbstractHeapType { + const fn as_str(&self, nullable: bool) -> &str { + use AbstractHeapType::*; + match (nullable, self) { + (_, Any) => "any", + (true, None) => "null", + (false, None) => "none", + (true, NoExtern) => "nullextern", + (false, NoExtern) => "noextern", + (true, NoFunc) => "nullfunc", + (false, NoFunc) => "nofunc", + (_, Eq) => "eq", + (_, Struct) => "struct", + (_, Array) => "array", + (_, I31) => "i31", + (_, Extern) => "extern", + (_, Func) => "func", + (_, Exn) => "exn", + (true, NoExn) => "nullexn", + (false, NoExn) => "noexn", + } + } +} + impl<'a> FromReader<'a> for StorageType { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { // NB: See `FromReader<'a> for ValType` for a table of how this @@ -1421,6 +1526,7 @@ impl<'a> FromReader<'a> for ValType { // | 0x6B | -21 | struct | gc proposal | // | 0x6A | -22 | array | gc proposal | // | 0x69 | -23 | exnref | gc + exceptions proposal | + // | 0x65 | -27 | shared $t | shared-everything proposal | // | 0x64 | -28 | ref $t | gc proposal, prefix byte | // | 0x63 | -29 | ref null $t | gc proposal, prefix byte | // | 0x60 | -32 | func $t | prefix byte | @@ -1457,8 +1563,8 @@ impl<'a> FromReader<'a> for ValType { reader.read_u8()?; Ok(ValType::V128) } - 0x70 | 0x6F | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 | 0x73 | 0x74 | 0x6D | 0x6B | 0x6A - | 0x6C | 0x69 => Ok(ValType::Ref(reader.read()?)), + 0x70 | 0x6F | 0x65 | 0x64 | 0x63 | 0x6E | 0x71 | 0x72 | 0x73 | 0x74 | 0x6D | 0x6B + | 0x6A | 0x6C | 0x69 => Ok(ValType::Ref(reader.read()?)), _ => bail!(reader.original_position(), "invalid value type"), } } @@ -1466,27 +1572,41 @@ impl<'a> FromReader<'a> for ValType { impl<'a> FromReader<'a> for RefType { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + let absheapty = |byte, pos| match byte { + 0x70 => Ok(RefType::FUNC.nullable()), + 0x6F => Ok(RefType::EXTERN.nullable()), + 0x6E => Ok(RefType::ANY.nullable()), + 0x71 => Ok(RefType::NONE.nullable()), + 0x72 => Ok(RefType::NOEXTERN.nullable()), + 0x73 => Ok(RefType::NOFUNC.nullable()), + 0x6D => Ok(RefType::EQ.nullable()), + 0x6B => Ok(RefType::STRUCT.nullable()), + 0x6A => Ok(RefType::ARRAY.nullable()), + 0x6C => Ok(RefType::I31.nullable()), + 0x69 => Ok(RefType::EXN.nullable()), + 0x74 => Ok(RefType::NOEXN.nullable()), + _ => bail!(pos, "invalid abstract heap type"), + }; + // NB: See `FromReader<'a> for ValType` for a table of how this // interacts with other value encodings. match reader.read()? { + byte @ (0x70 | 0x6F | 0x6E | 0x71 | 0x72 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C | 0x69 + | 0x74) => { + let pos = reader.original_position(); + absheapty(byte, pos) + } + 0x65 => { + let byte = reader.read()?; + let pos = reader.original_position(); + Ok(absheapty(byte, pos)?.shared().expect("must be abstract")) + } byte @ (0x63 | 0x64) => { let nullable = byte == 0x63; let pos = reader.original_position(); RefType::new(nullable, reader.read()?) .ok_or_else(|| crate::BinaryReaderError::new("type index too large", pos)) } - 0x69 => Ok(RefType::EXN.nullable()), - 0x6A => Ok(RefType::ARRAY.nullable()), - 0x6B => Ok(RefType::STRUCT.nullable()), - 0x6C => Ok(RefType::I31.nullable()), - 0x6D => Ok(RefType::EQ.nullable()), - 0x6E => Ok(RefType::ANY.nullable()), - 0x6F => Ok(RefType::EXTERN.nullable()), - 0x70 => Ok(RefType::FUNC.nullable()), - 0x71 => Ok(RefType::NONE.nullable()), - 0x72 => Ok(RefType::NOEXTERN.nullable()), - 0x73 => Ok(RefType::NOFUNC.nullable()), - 0x74 => Ok(RefType::NOEXN.nullable()), _ => bail!(reader.original_position(), "malformed reference type"), } } @@ -1497,53 +1617,14 @@ impl<'a> FromReader<'a> for HeapType { // NB: See `FromReader<'a> for ValType` for a table of how this // interacts with other value encodings. match reader.peek()? { - 0x70 => { - reader.read_u8()?; - Ok(HeapType::Func) - } - 0x6F => { + 0x65 => { reader.read_u8()?; - Ok(HeapType::Extern) + let ty = reader.read()?; + Ok(HeapType::Abstract { shared: true, ty }) } - 0x6E => { - reader.read_u8()?; - Ok(HeapType::Any) - } - 0x71 => { - reader.read_u8()?; - Ok(HeapType::None) - } - 0x72 => { - reader.read_u8()?; - Ok(HeapType::NoExtern) - } - 0x73 => { - reader.read_u8()?; - Ok(HeapType::NoFunc) - } - 0x6D => { - reader.read_u8()?; - Ok(HeapType::Eq) - } - 0x6B => { - reader.read_u8()?; - Ok(HeapType::Struct) - } - 0x6A => { - reader.read_u8()?; - Ok(HeapType::Array) - } - 0x6C => { - reader.read_u8()?; - Ok(HeapType::I31) - } - 0x69 => { - reader.read_u8()?; - Ok(HeapType::Exn) - } - 0x74 => { - reader.read_u8()?; - Ok(HeapType::NoExn) + 0x70 | 0x6F | 0x6E | 0x71 | 0x72 | 0x73 | 0x6D | 0x6B | 0x6A | 0x6C | 0x69 | 0x74 => { + let ty = reader.read()?; + Ok(HeapType::Abstract { shared: false, ty }) } _ => { let idx = match u32::try_from(reader.read_var_s33()?) { @@ -1564,6 +1645,29 @@ impl<'a> FromReader<'a> for HeapType { } } +impl<'a> FromReader<'a> for AbstractHeapType { + fn from_reader(reader: &mut BinaryReader<'a>) -> Result { + use AbstractHeapType::*; + match reader.read_u8()? { + 0x70 => Ok(Func), + 0x6F => Ok(Extern), + 0x6E => Ok(Any), + 0x71 => Ok(None), + 0x72 => Ok(NoExtern), + 0x73 => Ok(NoFunc), + 0x6D => Ok(Eq), + 0x6B => Ok(Struct), + 0x6A => Ok(Array), + 0x6C => Ok(I31), + 0x69 => Ok(Exn), + 0x74 => Ok(NoExn), + _ => { + bail!(reader.original_position(), "invalid abstract heap type"); + } + } + } +} + /// Represents a table's type. #[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] pub struct TableType { diff --git a/crates/wasmparser/src/resources.rs b/crates/wasmparser/src/resources.rs index 20ce74358a..729fc285cb 100644 --- a/crates/wasmparser/src/resources.rs +++ b/crates/wasmparser/src/resources.rs @@ -161,7 +161,6 @@ where fn is_subtype(&self, a: ValType, b: ValType) -> bool { T::is_subtype(self, a, b) } - fn element_count(&self) -> u32 { T::element_count(self) } diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index 5902b92594..4396cb51f4 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -15,9 +15,9 @@ use crate::prelude::*; use crate::{ - limits::*, BinaryReaderError, Encoding, FromReader, FunctionBody, HeapType, Parser, Payload, - RefType, Result, SectionLimited, ValType, WasmFeatures, WASM_COMPONENT_VERSION, - WASM_MODULE_VERSION, + limits::*, AbstractHeapType, BinaryReaderError, Encoding, FromReader, FunctionBody, HeapType, + Parser, Payload, RefType, Result, SectionLimited, ValType, WasmFeatures, + WASM_COMPONENT_VERSION, WASM_MODULE_VERSION, }; use ::core::mem; use ::core::ops::Range; @@ -249,55 +249,54 @@ impl WasmFeatures { if !self.reference_types() { return Err("reference types support is not enabled"); } - match (r.heap_type(), r.is_nullable()) { - // funcref/externref only require `reference-types`. - (HeapType::Func, true) | (HeapType::Extern, true) => Ok(()), - - // Non-nullable func/extern references requires the - // `function-references` proposal. - (HeapType::Func | HeapType::Extern, false) => { - if self.function_references() { - Ok(()) - } else { - Err("function references required for non-nullable types") - } - } - - // Indexed types require either the function-references or gc - // proposal as gc implies function references here. - (HeapType::Concrete(_), _) => { + match r.heap_type() { + HeapType::Concrete(_) => { + // Indexed types require either the function-references or gc + // proposal as gc implies function references here. if self.function_references() || self.gc() { Ok(()) } else { Err("function references required for index reference types") } } - - // These types were added in the gc proposal. - ( - HeapType::Any - | HeapType::None - | HeapType::Eq - | HeapType::Struct - | HeapType::Array - | HeapType::I31 - | HeapType::NoExtern - | HeapType::NoFunc, - _, - ) => { - if self.gc() { - Ok(()) - } else { - Err("heap types not supported without the gc feature") + HeapType::Abstract { shared, ty } => { + use AbstractHeapType::*; + if shared && !self.shared_everything_threads() { + return Err( + "shared reference types require the shared-everything-threads proposal", + ); } - } + match (ty, r.is_nullable()) { + // funcref/externref only require `reference-types`. + (Func, true) | (Extern, true) => Ok(()), + + // Non-nullable func/extern references requires the + // `function-references` proposal. + (Func | Extern, false) => { + if self.function_references() { + Ok(()) + } else { + Err("function references required for non-nullable types") + } + } - // These types were added in the exception-handling proposal. - (HeapType::Exn | HeapType::NoExn, _) => { - if self.exceptions() { - Ok(()) - } else { - Err("exception refs not supported without the exception handling feature") + // These types were added in the gc proposal. + (Any | None | Eq | Struct | Array | I31 | NoExtern | NoFunc, _) => { + if self.gc() { + Ok(()) + } else { + Err("heap types not supported without the gc feature") + } + } + + // These types were added in the exception-handling proposal. + (Exn | NoExn, _) => { + if self.exceptions() { + Ok(()) + } else { + Err("exception refs not supported without the exception handling feature") + } + } } } } diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs index b189c41924..173a74d833 100644 --- a/crates/wasmparser/src/validator/core.rs +++ b/crates/wasmparser/src/validator/core.rs @@ -132,7 +132,7 @@ impl ModuleState { offset: usize, ) -> Result<()> { self.module - .check_global_type(&mut global.ty, features, offset)?; + .check_global_type(&mut global.ty, features, types, offset)?; self.check_const_expr(&global.init_expr, global.ty.content_type, features, types)?; self.module.assert_mut().globals.push(global.ty); Ok(()) @@ -855,7 +855,7 @@ impl Module { EntityType::Tag(self.types[t.func_type_idx as usize]) } TypeRef::Global(t) => { - self.check_global_type(t, features, offset)?; + self.check_global_type(t, features, types, offset)?; EntityType::Global(*t) } }) @@ -1017,20 +1017,9 @@ impl Module { } fn check_heap_type(&self, ty: &mut HeapType, offset: usize) -> Result<()> { - // Check that the heap type is valid + // Check that the heap type is valid. let type_index = match ty { - HeapType::Func - | HeapType::Extern - | HeapType::Any - | HeapType::None - | HeapType::NoExtern - | HeapType::NoFunc - | HeapType::Eq - | HeapType::Struct - | HeapType::Array - | HeapType::I31 - | HeapType::Exn - | HeapType::NoExn => return Ok(()), + HeapType::Abstract { .. } => return Ok(()), HeapType::Concrete(type_index) => type_index, }; match type_index { @@ -1073,6 +1062,7 @@ impl Module { &self, ty: &mut GlobalType, features: &WasmFeatures, + types: &TypeList, offset: usize, ) -> Result<()> { self.check_value_type(&mut ty.content_type, features, offset)?; @@ -1083,7 +1073,7 @@ impl Module { offset, )); } - if !ty.content_type.is_shared() { + if !types.valtype_is_shared(ty.content_type) { return Err(BinaryReaderError::new( "shared globals must have a shared value type", offset, diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs index 775964cecb..e13994d3a9 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -22,13 +22,13 @@ // confusing it's recommended to read over that section to see how it maps to // the various methods here. +use crate::prelude::*; use crate::{ - limits::MAX_WASM_FUNCTION_LOCALS, ArrayType, BinaryReaderError, BlockType, BrTable, Catch, - CompositeType, FieldType, FuncType, HeapType, Ieee32, Ieee64, MemArg, RefType, Result, - StorageType, StructType, SubType, TableType, TryTable, UnpackedIndex, ValType, VisitOperator, - WasmFeatures, WasmModuleResources, V128, + limits::MAX_WASM_FUNCTION_LOCALS, AbstractHeapType, ArrayType, BinaryReaderError, BlockType, + BrTable, Catch, CompositeType, FieldType, FuncType, GlobalType, HeapType, Ieee32, Ieee64, + MemArg, RefType, Result, StorageType, StructType, SubType, TableType, TryTable, UnpackedIndex, + ValType, VisitOperator, WasmFeatures, WasmModuleResources, V128, }; -use crate::{prelude::*, GlobalType}; use core::ops::{Deref, DerefMut}; pub(crate) struct OperatorValidator { @@ -3991,7 +3991,11 @@ where let is_nullable = extern_ref .as_type() .map_or(false, |ty| ty.as_reference_type().unwrap().is_nullable()); - let any_ref = RefType::new(is_nullable, HeapType::Any).unwrap(); + let heap_type = HeapType::Abstract { + shared: false, // TODO: handle shared--see https://github.com/WebAssembly/shared-everything-threads/issues/65. + ty: AbstractHeapType::Any, + }; + let any_ref = RefType::new(is_nullable, heap_type).unwrap(); self.push_operand(any_ref) } fn visit_extern_convert_any(&mut self) -> Self::Output { @@ -3999,7 +4003,11 @@ where let is_nullable = any_ref .as_type() .map_or(false, |ty| ty.as_reference_type().unwrap().is_nullable()); - let extern_ref = RefType::new(is_nullable, HeapType::Extern).unwrap(); + let heap_type = HeapType::Abstract { + shared: false, // TODO: handle shared--see https://github.com/WebAssembly/shared-everything-threads/issues/65. + ty: AbstractHeapType::Extern, + }; + let extern_ref = RefType::new(is_nullable, heap_type).unwrap(); self.push_operand(extern_ref) } fn visit_ref_test_non_null(&mut self, heap_type: HeapType) -> Self::Output { diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index 8da034d770..6e4f452e38 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -4,8 +4,8 @@ use super::{ component::{ComponentState, ExternKind}, core::Module, }; -use crate::collections::map::Entry; use crate::prelude::*; +use crate::{collections::map::Entry, AbstractHeapType}; use crate::{validator::names::KebabString, HeapType, ValidatorId}; use crate::{ BinaryReaderError, CompositeType, Export, ExternalKind, FuncType, GlobalType, Import, Matches, @@ -2743,63 +2743,87 @@ impl TypeList { &self[id] }; + use AbstractHeapType::*; use HeapType as HT; match (a.heap_type(), b.heap_type()) { (a, b) if a == b => true, - (HT::Eq | HT::I31 | HT::Struct | HT::Array | HT::None, HT::Any) => true, - (HT::I31 | HT::Struct | HT::Array | HT::None, HT::Eq) => true, - (HT::NoExtern, HT::Extern) => true, - (HT::NoFunc, HT::Func) => true, - (HT::None, HT::I31 | HT::Array | HT::Struct) => true, - - (HT::Concrete(a), HT::Eq | HT::Any) => matches!( - subtype(a_group, a).composite_type, - CompositeType::Array(_) | CompositeType::Struct(_) - ), - - (HT::Concrete(a), HT::Struct) => { - matches!(subtype(a_group, a).composite_type, CompositeType::Struct(_)) + ( + HT::Abstract { + shared: a_shared, + ty: a_ty, + }, + HT::Abstract { + shared: b_shared, + ty: b_ty, + }, + ) => { + a_shared == b_shared + && match (a_ty, b_ty) { + (Eq | I31 | Struct | Array | None, Any) => true, + (I31 | Struct | Array | None, Eq) => true, + (NoExtern, Extern) => true, + (NoFunc, Func) => true, + (None, I31 | Array | Struct) => true, + (NoExn, Exn) => true, + // Nothing else matches. (Avoid full wildcard matches so + // that adding/modifying variants is easier in the + // future.) + ( + Func | Extern | Exn | Any | Eq | Array | I31 | Struct | None | NoFunc + | NoExtern | NoExn, + _, + ) => false, + } } - (HT::Concrete(a), HT::Array) => { - matches!(subtype(a_group, a).composite_type, CompositeType::Array(_)) + (HT::Concrete(a), HT::Abstract { shared, ty }) => { + if shared { + // TODO: handle shared + todo!("check shared-ness of concrete type"); + } + match ty { + Any | Eq => matches!( + subtype(a_group, a).composite_type, + CompositeType::Array(_) | CompositeType::Struct(_) + ), + Struct => { + matches!(subtype(a_group, a).composite_type, CompositeType::Struct(_)) + } + Array => { + matches!(subtype(a_group, a).composite_type, CompositeType::Array(_)) + } + Func => { + matches!(subtype(a_group, a).composite_type, CompositeType::Func(_)) + } + // Nothing else matches. (Avoid full wildcard matches so + // that adding/modifying variants is easier in the future.) + Extern | Exn | I31 | None | NoFunc | NoExtern | NoExn => false, + } } - (HT::Concrete(a), HT::Func) => { - matches!(subtype(a_group, a).composite_type, CompositeType::Func(_)) + (HT::Abstract { shared, ty }, HT::Concrete(b)) => { + if shared { + // TODO: handle shared + todo!("check shared-ness of concrete type"); + } + match ty { + None => matches!( + subtype(b_group, b).composite_type, + CompositeType::Array(_) | CompositeType::Struct(_) + ), + NoFunc => matches!(subtype(b_group, b).composite_type, CompositeType::Func(_)), + // Nothing else matches. (Avoid full wildcard matches so + // that adding/modifying variants is easier in the future.) + Func | Extern | Exn | Any | Eq | Array | I31 | Struct | NoExtern | NoExn => { + false + } + } } (HT::Concrete(a), HT::Concrete(b)) => { self.id_is_subtype(core_type_id(a_group, a), core_type_id(b_group, b)) } - - (HT::None, HT::Concrete(b)) => matches!( - subtype(b_group, b).composite_type, - CompositeType::Array(_) | CompositeType::Struct(_) - ), - - (HT::NoFunc, HT::Concrete(b)) => { - matches!(subtype(b_group, b).composite_type, CompositeType::Func(_)) - } - - (HT::NoExn, HT::Exn) => true, - - // Nothing else matches. (Avoid full wildcard matches so that - // adding/modifying variants is easier in the future.) - (HT::Concrete(_), _) - | (HT::Func, _) - | (HT::Extern, _) - | (HT::Any, _) - | (HT::None, _) - | (HT::NoExtern, _) - | (HT::NoFunc, _) - | (HT::Eq, _) - | (HT::Struct, _) - | (HT::Array, _) - | (HT::I31, _) - | (HT::Exn, _) - | (HT::NoExn, _) => false, } } @@ -2819,25 +2843,49 @@ impl TypeList { } } + /// Is `ty` shared? + /// + /// This is complicated by reference types, since they may have concrete + /// heap types whose shared-ness must be checked by looking at the type they + /// point to. + pub fn valtype_is_shared(&self, ty: ValType) -> bool { + match ty { + ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => true, + ValType::Ref(rt) => match rt.heap_type() { + HeapType::Abstract { shared, .. } => shared, + HeapType::Concrete(index) => self[index.as_core_type_id().unwrap()] + .composite_type + .is_shared(), + }, + } + } + /// Get the top type of the given heap type. /// /// Concrete types must have had their indices canonicalized to core type /// ids, otherwise this method will panic. pub fn top_type(&self, heap_type: &HeapType) -> HeapType { + use AbstractHeapType::*; match *heap_type { HeapType::Concrete(idx) => match self[idx.as_core_type_id().unwrap()].composite_type { - CompositeType::Func(_) => HeapType::Func, - CompositeType::Array(_) | CompositeType::Struct(_) => HeapType::Any, + CompositeType::Func(_) => HeapType::Abstract { + shared: false, // TODO: handle shared--retrieve from `func` type. + ty: Func, + }, + CompositeType::Array(_) | CompositeType::Struct(_) => HeapType::Abstract { + shared: false, // TODO: handle shared--retrieve from `array` or `struct` type. + ty: Any, + }, }, - HeapType::Func | HeapType::NoFunc => HeapType::Func, - HeapType::Extern | HeapType::NoExtern => HeapType::Extern, - HeapType::Any - | HeapType::Eq - | HeapType::Struct - | HeapType::Array - | HeapType::I31 - | HeapType::None => HeapType::Any, - HeapType::Exn | HeapType::NoExn => HeapType::Exn, + HeapType::Abstract { shared, ty } => { + let ty = match ty { + Func | NoFunc => Func, + Extern | NoExtern => Extern, + Any | Eq | Struct | Array | I31 | None => Any, + Exn | NoExn => Exn, + }; + HeapType::Abstract { shared, ty } + } } } diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index bf342e7a18..5b86607dad 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -1018,21 +1018,32 @@ impl Printer<'_, '_> { fn print_heaptype(&mut self, state: &State, ty: HeapType) -> Result<()> { match ty { - HeapType::Func => self.print_type_keyword("func")?, - HeapType::Extern => self.print_type_keyword("extern")?, - HeapType::Any => self.print_type_keyword("any")?, - HeapType::None => self.print_type_keyword("none")?, - HeapType::NoExtern => self.print_type_keyword("noextern")?, - HeapType::NoFunc => self.print_type_keyword("nofunc")?, - HeapType::Eq => self.print_type_keyword("eq")?, - HeapType::Struct => self.print_type_keyword("struct")?, - HeapType::Array => self.print_type_keyword("array")?, - HeapType::I31 => self.print_type_keyword("i31")?, - HeapType::Exn => self.print_type_keyword("exn")?, - HeapType::NoExn => self.print_type_keyword("noexn")?, HeapType::Concrete(i) => { self.print_idx(&state.core.type_names, i.as_module_index().unwrap())?; } + HeapType::Abstract { shared, ty } => { + use AbstractHeapType::*; + if shared { + self.start_group("shared ")?; + } + match ty { + Func => self.print_type_keyword("func")?, + Extern => self.print_type_keyword("extern")?, + Any => self.print_type_keyword("any")?, + None => self.print_type_keyword("none")?, + NoExtern => self.print_type_keyword("noextern")?, + NoFunc => self.print_type_keyword("nofunc")?, + Eq => self.print_type_keyword("eq")?, + Struct => self.print_type_keyword("struct")?, + Array => self.print_type_keyword("array")?, + I31 => self.print_type_keyword("i31")?, + Exn => self.print_type_keyword("exn")?, + NoExn => self.print_type_keyword("noexn")?, + } + if shared { + self.end_group()?; + } + } } Ok(()) } diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index 68864b0c20..a55626e7a4 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -594,24 +594,27 @@ impl From> for wasm_encoder::RefType { impl From> for wasm_encoder::HeapType { fn from(r: core::HeapType<'_>) -> Self { + use wasm_encoder::AbstractHeapType::*; match r { - core::HeapType::Func => Self::Func, - core::HeapType::Extern => Self::Extern, - core::HeapType::Exn | core::HeapType::NoExn => { - todo!("encoding of exceptions proposal types not yet implemented") - } + core::HeapType::Abstract { shared, ty } => match ty { + core::AbstractHeapType::Func => Self::Abstract { shared, ty: Func }, + core::AbstractHeapType::Extern => Self::Abstract { shared, ty: Extern }, + core::AbstractHeapType::Exn | core::AbstractHeapType::NoExn => { + todo!("encoding of exceptions proposal types not yet implemented") + } + core::AbstractHeapType::Any + | core::AbstractHeapType::Eq + | core::AbstractHeapType::Struct + | core::AbstractHeapType::Array + | core::AbstractHeapType::NoFunc + | core::AbstractHeapType::NoExtern + | core::AbstractHeapType::None + | core::AbstractHeapType::I31 => { + todo!("encoding of GC proposal types not yet implemented") + } + }, core::HeapType::Concrete(Index::Num(i, _)) => Self::Concrete(i), core::HeapType::Concrete(_) => panic!("unresolved index"), - core::HeapType::Any - | core::HeapType::Eq - | core::HeapType::Struct - | core::HeapType::Array - | core::HeapType::NoFunc - | core::HeapType::NoExtern - | core::HeapType::None - | core::HeapType::I31 => { - todo!("encoding of GC proposal types not yet implemented") - } } } } diff --git a/crates/wast/src/component/resolve.rs b/crates/wast/src/component/resolve.rs index cd7fb7e3df..ac072c5bf1 100644 --- a/crates/wast/src/component/resolve.rs +++ b/crates/wast/src/component/resolve.rs @@ -513,18 +513,7 @@ impl<'a> Resolver<'a> { match &mut r.rep { ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => {} ValType::Ref(r) => match &mut r.heap { - core::HeapType::Func - | core::HeapType::Extern - | core::HeapType::Exn - | core::HeapType::Any - | core::HeapType::Eq - | core::HeapType::Array - | core::HeapType::I31 - | core::HeapType::Struct - | core::HeapType::None - | core::HeapType::NoFunc - | core::HeapType::NoExtern - | core::HeapType::NoExn => {} + core::HeapType::Abstract { .. } => {} core::HeapType::Concrete(id) => { self.resolve_ns(id, Ns::Type)?; } diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs index 84d128fa25..8899c81d04 100644 --- a/crates/wast/src/core/binary.rs +++ b/crates/wast/src/core/binary.rs @@ -285,18 +285,12 @@ impl<'a> Encode for ValType<'a> { impl<'a> Encode for HeapType<'a> { fn encode(&self, e: &mut Vec) { match self { - HeapType::Func => e.push(0x70), - HeapType::Extern => e.push(0x6f), - HeapType::Exn => e.push(0x69), - HeapType::Any => e.push(0x6e), - HeapType::Eq => e.push(0x6d), - HeapType::Struct => e.push(0x6b), - HeapType::Array => e.push(0x6a), - HeapType::I31 => e.push(0x6c), - HeapType::NoFunc => e.push(0x73), - HeapType::NoExtern => e.push(0x72), - HeapType::NoExn => e.push(0x74), - HeapType::None => e.push(0x71), + HeapType::Abstract { shared, ty } => { + if *shared { + e.push(0x65); + } + ty.encode(e) + } // Note that this is encoded as a signed leb128 so be sure to cast // to an i64 first HeapType::Concrete(Index::Num(n, _)) => i64::from(*n).encode(e), @@ -307,61 +301,42 @@ impl<'a> Encode for HeapType<'a> { } } +impl<'a> Encode for AbstractHeapType { + fn encode(&self, e: &mut Vec) { + use AbstractHeapType::*; + match self { + Func => e.push(0x70), + Extern => e.push(0x6f), + Exn => e.push(0x69), + Any => e.push(0x6e), + Eq => e.push(0x6d), + Struct => e.push(0x6b), + Array => e.push(0x6a), + I31 => e.push(0x6c), + NoFunc => e.push(0x73), + NoExtern => e.push(0x72), + NoExn => e.push(0x74), + None => e.push(0x71), + } + } +} + impl<'a> Encode for RefType<'a> { fn encode(&self, e: &mut Vec) { match self { - // The 'funcref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::Func, - } => e.push(0x70), - // The 'externref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::Extern, - } => e.push(0x6f), - // The 'exnref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::Exn, - } => e.push(0x69), - // The 'eqref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::Eq, - } => e.push(0x6d), - // The 'structref' binary abbreviation + // Binary abbreviations (i.e., short form), for when the ref is + // nullable. RefType { nullable: true, - heap: HeapType::Struct, - } => e.push(0x6b), - // The 'i31ref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::I31, - } => e.push(0x6c), - // The 'nullfuncref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::NoFunc, - } => e.push(0x73), - // The 'nullexternref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::NoExtern, - } => e.push(0x72), - // The 'nullexnref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::NoExn, - } => e.push(0x74), - // The 'nullref' binary abbreviation - RefType { - nullable: true, - heap: HeapType::None, - } => e.push(0x71), + heap: HeapType::Abstract { shared, ty }, + } => { + if *shared { + e.push(0x65); + } + ty.encode(e); + } - // Generic 'ref null ' encoding + // Generic 'ref null ' encoding (i.e., long form). RefType { nullable: true, heap, @@ -369,7 +344,8 @@ impl<'a> Encode for RefType<'a> { e.push(0x63); heap.encode(e); } - // Generic 'ref ' encoding + + // Generic 'ref ' encoding. RefType { nullable: false, heap, @@ -606,7 +582,11 @@ impl Encode for Elem<'_> { ty: RefType { nullable: true, - heap: HeapType::Func, + heap: + HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Func, + }, }, .. }, diff --git a/crates/wast/src/core/types.rs b/crates/wast/src/core/types.rs index 266bf1d12f..b149f33d25 100644 --- a/crates/wast/src/core/types.rs +++ b/crates/wast/src/core/types.rs @@ -1,5 +1,6 @@ use crate::core::*; use crate::kw; +use crate::parser::Lookahead1; use crate::parser::{Cursor, Parse, Parser, Peek, Result}; use crate::token::{Id, Index, LParen, NameAnnotation, Span}; use crate::Error; @@ -57,10 +58,56 @@ impl<'a> Peek for ValType<'a> { } } -/// A heap type for a reference type +/// A heap type for a reference type. #[allow(missing_docs)] #[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] pub enum HeapType<'a> { + /// An abstract reference. With the shared-everything-threads proposal, + /// these types can also be marked `shared`. + Abstract { shared: bool, ty: AbstractHeapType }, + /// A reference to a concrete function, struct, or array type defined by + /// Wasm: `ref T`. This is part of the function references and GC proposals. + Concrete(Index<'a>), +} + +impl<'a> Parse<'a> for HeapType<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut l = parser.lookahead1(); + if l.peek::()? { + Ok(HeapType::Concrete(parser.parse()?)) + } else if l.peek::()? { + parser.parens(|p| { + p.parse::()?; + Ok(HeapType::Abstract { + shared: true, + ty: p.parse()?, + }) + }) + } else if l.peek::()? { + Ok(HeapType::Abstract { + shared: false, + ty: parser.parse()?, + }) + } else { + Err(l.error()) + } + } +} + +impl<'a> Peek for HeapType<'a> { + fn peek(cursor: Cursor<'_>) -> Result { + Ok(AbstractHeapType::peek(cursor)? + || (LParen::peek(cursor)? && kw::shared::peek2(cursor)?) + || (LParen::peek(cursor)? && kw::r#type::peek2(cursor)?)) + } + fn display() -> &'static str { + "heaptype" + } +} + +/// An abstract heap type. +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +pub enum AbstractHeapType { /// An untyped function reference: funcref. This is part of the reference /// types proposal. Func, @@ -89,59 +136,54 @@ pub enum HeapType<'a> { None, /// The bottom type of the exnref hierarchy. Part of the exceptions proposal. NoExn, - /// A reference to a concrete function, struct, or array type defined by - /// Wasm: `ref T`. This is part of the function references and GC proposals. - Concrete(Index<'a>), } -impl<'a> Parse<'a> for HeapType<'a> { +impl<'a> Parse<'a> for AbstractHeapType { fn parse(parser: Parser<'a>) -> Result { let mut l = parser.lookahead1(); if l.peek::()? { parser.parse::()?; - Ok(HeapType::Func) + Ok(AbstractHeapType::Func) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::Extern) + Ok(AbstractHeapType::Extern) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::Exn) + Ok(AbstractHeapType::Exn) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::Any) + Ok(AbstractHeapType::Any) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::Eq) + Ok(AbstractHeapType::Eq) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::Struct) + Ok(AbstractHeapType::Struct) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::Array) + Ok(AbstractHeapType::Array) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::I31) + Ok(AbstractHeapType::I31) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::NoFunc) + Ok(AbstractHeapType::NoFunc) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::NoExtern) + Ok(AbstractHeapType::NoExtern) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::NoExn) + Ok(AbstractHeapType::NoExn) } else if l.peek::()? { parser.parse::()?; - Ok(HeapType::None) - } else if l.peek::()? { - Ok(HeapType::Concrete(parser.parse()?)) + Ok(AbstractHeapType::None) } else { Err(l.error()) } } } -impl<'a> Peek for HeapType<'a> { +impl<'a> Peek for AbstractHeapType { fn peek(cursor: Cursor<'_>) -> Result { Ok(kw::func::peek(cursor)? || kw::r#extern::peek(cursor)? @@ -154,11 +196,10 @@ impl<'a> Peek for HeapType<'a> { || kw::nofunc::peek(cursor)? || kw::noextern::peek(cursor)? || kw::noexn::peek(cursor)? - || kw::none::peek(cursor)? - || (LParen::peek(cursor)? && kw::r#type::peek2(cursor)?)) + || kw::none::peek(cursor)?) } fn display() -> &'static str { - "heaptype" + "absheaptype" } } @@ -175,7 +216,10 @@ impl<'a> RefType<'a> { pub fn func() -> Self { RefType { nullable: true, - heap: HeapType::Func, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Func, + }, } } @@ -183,7 +227,10 @@ impl<'a> RefType<'a> { pub fn r#extern() -> Self { RefType { nullable: true, - heap: HeapType::Extern, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Extern, + }, } } @@ -191,7 +238,10 @@ impl<'a> RefType<'a> { pub fn exn() -> Self { RefType { nullable: true, - heap: HeapType::Exn, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Exn, + }, } } @@ -199,7 +249,10 @@ impl<'a> RefType<'a> { pub fn any() -> Self { RefType { nullable: true, - heap: HeapType::Any, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Any, + }, } } @@ -207,7 +260,10 @@ impl<'a> RefType<'a> { pub fn eq() -> Self { RefType { nullable: true, - heap: HeapType::Eq, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Eq, + }, } } @@ -215,7 +271,10 @@ impl<'a> RefType<'a> { pub fn r#struct() -> Self { RefType { nullable: true, - heap: HeapType::Struct, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Struct, + }, } } @@ -223,7 +282,10 @@ impl<'a> RefType<'a> { pub fn array() -> Self { RefType { nullable: true, - heap: HeapType::Array, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::Array, + }, } } @@ -231,7 +293,10 @@ impl<'a> RefType<'a> { pub fn i31() -> Self { RefType { nullable: true, - heap: HeapType::I31, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::I31, + }, } } @@ -239,7 +304,10 @@ impl<'a> RefType<'a> { pub fn nullfuncref() -> Self { RefType { nullable: true, - heap: HeapType::NoFunc, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::NoFunc, + }, } } @@ -247,7 +315,10 @@ impl<'a> RefType<'a> { pub fn nullexternref() -> Self { RefType { nullable: true, - heap: HeapType::NoExtern, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::NoExtern, + }, } } @@ -255,7 +326,10 @@ impl<'a> RefType<'a> { pub fn nullref() -> Self { RefType { nullable: true, - heap: HeapType::None, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::None, + }, } } @@ -263,14 +337,46 @@ impl<'a> RefType<'a> { pub fn nullexnref() -> Self { RefType { nullable: true, - heap: HeapType::NoExn, + heap: HeapType::Abstract { + shared: false, + ty: AbstractHeapType::NoExn, + }, } } -} -impl<'a> Parse<'a> for RefType<'a> { - fn parse(parser: Parser<'a>) -> Result { - let mut l = parser.lookahead1(); + /// Make the reference type a `shared` one. + /// + /// Note that this is not possible for concrete references (e.g., `(ref + /// $t)`) so `None` is returned in that case. + pub fn shared(self) -> Option { + match self.heap { + HeapType::Abstract { ty, .. } => Some(RefType { + nullable: self.nullable, + heap: HeapType::Abstract { shared: true, ty }, + }), + _ => None, + } + } + + /// Helper for checking if shorthand forms of reference types can be parsed + /// next; e.g., `funcref`. + fn peek_shorthand(l: &mut Lookahead1) -> Result { + Ok(l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()? + || l.peek::()?) + } + + /// Helper for parsing shorthand forms of reference types; e.g., `funcref`. + fn parse_shorthand(mut l: Lookahead1, parser: Parser<'a>) -> Result { if l.peek::()? { parser.parse::()?; Ok(RefType::func()) @@ -307,10 +413,23 @@ impl<'a> Parse<'a> for RefType<'a> { } else if l.peek::()? { parser.parse::()?; Ok(RefType::nullref()) + } else { + Err(l.error()) + } + } +} + +impl<'a> Parse<'a> for RefType<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut l = parser.lookahead1(); + if RefType::peek_shorthand(&mut l)? { + // I.e., `*ref`. + RefType::parse_shorthand(l, parser) } else if l.peek::()? { parser.parens(|p| { let mut l = parser.lookahead1(); if l.peek::()? { + // I.e., `(ref null? ...)`. p.parse::()?; let mut nullable = false; @@ -323,6 +442,11 @@ impl<'a> Parse<'a> for RefType<'a> { nullable, heap: parser.parse()?, }) + } else if l.peek::()? { + // I.e., `(shared *ref)`. + p.parse::()?; + let reftype = RefType::parse_shorthand(l, parser)?; + Ok(reftype.shared().expect("only abstract heap types are used")) } else { Err(l.error()) } @@ -347,6 +471,7 @@ impl<'a> Peek for RefType<'a> { || kw::nullexternref::peek(cursor)? || kw::nullexnref::peek(cursor)? || kw::nullref::peek(cursor)? + || (LParen::peek(cursor)? && kw::shared::peek2(cursor)?) || (LParen::peek(cursor)? && kw::r#ref::peek2(cursor)?)) } fn display() -> &'static str { diff --git a/crates/wit-component/Cargo.toml b/crates/wit-component/Cargo.toml index 8bd969c4db..c05f13e652 100644 --- a/crates/wit-component/Cargo.toml +++ b/crates/wit-component/Cargo.toml @@ -18,7 +18,7 @@ workspace = true [dependencies] wasmparser = { workspace = true } -wasm-encoder = { workspace = true } +wasm-encoder = { workspace = true, features = ["wasmparser"] } wasm-metadata = { workspace = true } wit-parser = { workspace = true, features = ['decoding', 'serde'] } anyhow = { workspace = true } diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs index 6f0a5b6154..bb857f94e2 100644 --- a/crates/wit-component/src/gc.rs +++ b/crates/wit-component/src/gc.rs @@ -486,18 +486,7 @@ impl<'a> Module<'a> { fn heapty(&mut self, ty: HeapType) { match ty { - HeapType::Func - | HeapType::Extern - | HeapType::Any - | HeapType::None - | HeapType::NoExtern - | HeapType::NoFunc - | HeapType::Eq - | HeapType::Struct - | HeapType::Array - | HeapType::I31 - | HeapType::Exn - | HeapType::NoExn => {} + HeapType::Abstract { .. } => {} HeapType::Concrete(i) => self.ty(i.as_module_index().unwrap()), } } @@ -1132,21 +1121,13 @@ impl Encoder { fn heapty(&self, ht: wasmparser::HeapType) -> wasm_encoder::HeapType { match ht { - HeapType::Func => wasm_encoder::HeapType::Func, - HeapType::Extern => wasm_encoder::HeapType::Extern, - HeapType::Any => wasm_encoder::HeapType::Any, - HeapType::None => wasm_encoder::HeapType::None, - HeapType::NoExtern => wasm_encoder::HeapType::NoExtern, - HeapType::NoFunc => wasm_encoder::HeapType::NoFunc, - HeapType::Eq => wasm_encoder::HeapType::Eq, - HeapType::Struct => wasm_encoder::HeapType::Struct, - HeapType::Array => wasm_encoder::HeapType::Array, - HeapType::I31 => wasm_encoder::HeapType::I31, - HeapType::Exn => wasm_encoder::HeapType::Exn, - HeapType::NoExn => wasm_encoder::HeapType::NoExn, HeapType::Concrete(idx) => { wasm_encoder::HeapType::Concrete(self.types.remap(idx.as_module_index().unwrap())) } + HeapType::Abstract { shared, ty } => { + let ty = ty.into(); + wasm_encoder::HeapType::Abstract { shared, ty } + } } } } diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs index d47aca56e1..e3923c1e3c 100644 --- a/crates/wit-component/src/linking.rs +++ b/crates/wit-component/src/linking.rs @@ -35,9 +35,9 @@ use { }, wasm_encoder::{ CodeSection, ConstExpr, DataSection, ElementSection, Elements, EntityType, ExportKind, - ExportSection, Function, FunctionSection, GlobalSection, HeapType, ImportSection, - Instruction as Ins, MemArg, MemorySection, MemoryType, Module, RawCustomSection, RefType, - StartSection, TableSection, TableType, TypeSection, ValType, + ExportSection, Function, FunctionSection, GlobalSection, ImportSection, Instruction as Ins, + MemArg, MemorySection, MemoryType, Module, RawCustomSection, RefType, StartSection, + TableSection, TableType, TypeSection, ValType, }, wasmparser::SymbolFlags, }; @@ -464,10 +464,7 @@ fn make_env_module<'a>( { let mut tables = TableSection::new(); tables.table(TableType { - element_type: RefType { - nullable: true, - heap_type: HeapType::Func, - }, + element_type: RefType::FUNCREF, minimum: table_offset.into(), maximum: None, table64: false, @@ -559,10 +556,7 @@ fn make_init_module( "env", "__indirect_function_table", TableType { - element_type: RefType { - nullable: true, - heap_type: HeapType::Func, - }, + element_type: RefType::FUNCREF, minimum: 0, maximum: None, table64: false, diff --git a/src/bin/wasm-tools/json_from_wast.rs b/src/bin/wasm-tools/json_from_wast.rs index c85cb946c2..afb99243db 100644 --- a/src/bin/wasm-tools/json_from_wast.rs +++ b/src/bin/wasm-tools/json_from_wast.rs @@ -1,7 +1,7 @@ use anyhow::{bail, Context, Result}; use clap::Parser; use std::path::{Path, PathBuf}; -use wast::core::{HeapType, NanPattern, V128Const, V128Pattern, WastRetCore}; +use wast::core::{AbstractHeapType, HeapType, NanPattern, V128Const, V128Pattern, WastRetCore}; use wast::lexer::Lexer; use wast::parser::{self, ParseBuffer}; use wast::token::{Span, F32, F64}; @@ -506,21 +506,30 @@ fn f64_to_string(f: F64) -> String { fn null_heap_ty(ty: HeapType<'_>) -> Result { Ok(match ty { - HeapType::Func => json::Const::FuncRef { - value: Some("null".to_string()), - }, - HeapType::Extern => json::Const::ExternRef { - value: "null".to_string(), - }, - HeapType::Any => json::Const::AnyRef { - value: Some("null".to_string()), - }, - HeapType::None => json::Const::NullRef, - HeapType::NoFunc => json::Const::NullFuncRef, - HeapType::NoExtern => json::Const::NullExternRef, - HeapType::Exn => json::Const::ExnRef { - value: Some("null".to_string()), - }, + HeapType::Abstract { shared, ty } => { + use AbstractHeapType::*; + if shared { + bail!("shared abstract types are not supported in `ref.null`") + } + match ty { + Func => json::Const::FuncRef { + value: Some("null".to_string()), + }, + Extern => json::Const::ExternRef { + value: "null".to_string(), + }, + Any => json::Const::AnyRef { + value: Some("null".to_string()), + }, + None => json::Const::NullRef, + NoFunc => json::Const::NullFuncRef, + NoExtern => json::Const::NullExternRef, + Exn => json::Const::ExnRef { + value: Some("null".to_string()), + }, + _ => bail!("unsupported abstract type found in `ref.null`"), + } + } _ => bail!("unsupported heap type found in `ref.null`"), }) } diff --git a/tests/local/shared-everything-threads/global-heap-types.wast b/tests/local/shared-everything-threads/global-heap-types.wast new file mode 100644 index 0000000000..4b887fc126 --- /dev/null +++ b/tests/local/shared-everything-threads/global-heap-types.wast @@ -0,0 +1,311 @@ +;; Check shared attributes for global heap types. + +;; Concrete heap types cannot be marked shared yet (TODO: this is only possible +;; once composite types can be marked shared). +;; +;; (assert_invalid +;; (module +;; (type $t (shared anyref)) +;; (global (shared (ref null (shared $t)))))) +;; "shared value type") + +;; `func` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_funcref") (shared (shared funcref))) + (global (import "spectest" "global_mut_funcref") (shared mut (shared funcref))) + (global (import "spectest" "global_ref_null_func") (shared (ref null (shared func)))) + (global (import "spectest" "global_mut_ref_null_func") (shared mut (ref null (shared func)))) + (global (import "spectest" "global_ref_func") (shared (ref (shared func)))) + (global (import "spectest" "global_mut_ref_func") (shared mut (ref (shared func)))) + + ;; Initialized (long/short form). + (global (shared (shared funcref)) (ref.null (shared func))) + (global (shared (ref null (shared func))) (ref.null (shared func))) +) + +(assert_invalid + (module (global (shared funcref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null func)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared func)))) + "type mismatch") + +;; `extern` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_externref") (shared (shared externref))) + (global (import "spectest" "global_mut_externref") (shared mut (shared externref))) + (global (import "spectest" "global_ref_null_extern") (shared (ref null (shared extern)))) + (global (import "spectest" "global_mut_ref_null_extern") (shared mut (ref null (shared extern)))) + (global (import "spectest" "global_ref_extern") (shared (ref (shared extern)))) + (global (import "spectest" "global_mut_ref_extern") (shared mut (ref (shared extern)))) + + ;; Initialized (long/short form). + (global (shared (shared externref)) (ref.null (shared extern))) + (global (shared (ref null (shared extern))) (ref.null (shared extern))) +) + +(assert_invalid + (module (global (shared externref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null extern)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared extern)))) + "type mismatch") + +;; `exn` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_exnref") (shared (shared exnref))) + (global (import "spectest" "global_mut_exnref") (shared mut (shared exnref))) + (global (import "spectest" "global_ref_null_exn") (shared (ref null (shared exn)))) + (global (import "spectest" "global_mut_ref_null_exn") (shared mut (ref null (shared exn)))) + (global (import "spectest" "global_ref_exn") (shared (ref (shared exn)))) + (global (import "spectest" "global_mut_ref_exn") (shared mut (ref (shared exn)))) + + ;; Initialized (long/short form). + (global (shared (shared exnref)) (ref.null (shared exn))) + (global (shared (ref null (shared exn))) (ref.null (shared exn))) +) + +(assert_invalid + (module (global (shared exnref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null exn)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared exn)))) + "type mismatch") + +;; `any` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_anyref") (shared (shared anyref))) + (global (import "spectest" "global_mut_anyref") (shared mut (shared anyref))) + (global (import "spectest" "global_ref_null_any") (shared (ref null (shared any)))) + (global (import "spectest" "global_mut_ref_null_any") (shared mut (ref null (shared any)))) + (global (import "spectest" "global_ref_any") (shared (ref (shared any)))) + (global (import "spectest" "global_mut_ref_any") (shared mut (ref (shared any)))) + + ;; Initialized (long/short form). + (global (shared (shared anyref)) (ref.null (shared any))) + (global (shared (ref null (shared any))) (ref.null (shared any))) +) + +(assert_invalid + (module (global (shared anyref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null any)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared any)))) + "type mismatch") + +;; `eq` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_eqref") (shared (shared eqref))) + (global (import "spectest" "global_mut_eqref") (shared mut (shared eqref))) + (global (import "spectest" "global_ref_null_eq") (shared (ref null (shared eq)))) + (global (import "spectest" "global_mut_ref_null_eq") (shared mut (ref null (shared eq)))) + (global (import "spectest" "global_ref_eq") (shared (ref (shared eq)))) + (global (import "spectest" "global_mut_ref_eq") (shared mut (ref (shared eq)))) + + ;; Initialized (long/short form). + (global (shared (shared eqref)) (ref.null (shared eq))) + (global (shared (ref null (shared eq))) (ref.null (shared eq))) +) + +(assert_invalid + (module (global (shared eqref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null eq)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared eq)))) + "type mismatch") + +;; `struct` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_structref") (shared (shared structref))) + (global (import "spectest" "global_mut_structref") (shared mut (shared structref))) + (global (import "spectest" "global_ref_null_struct") (shared (ref null (shared struct)))) + (global (import "spectest" "global_mut_ref_null_struct") (shared mut (ref null (shared struct)))) + (global (import "spectest" "global_ref_struct") (shared (ref (shared struct)))) + (global (import "spectest" "global_mut_ref_struct") (shared mut (ref (shared struct)))) + + ;; Initialized (long/short form). + (global (shared (shared structref)) (ref.null (shared struct))) + (global (shared (ref null (shared struct))) (ref.null (shared struct))) +) + +(assert_invalid + (module (global (shared structref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null struct)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared struct)))) + "type mismatch") + +;; `array` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_arrayref") (shared (shared arrayref))) + (global (import "spectest" "global_mut_arrayref") (shared mut (shared arrayref))) + (global (import "spectest" "global_ref_null_array") (shared (ref null (shared array)))) + (global (import "spectest" "global_mut_ref_null_array") (shared mut (ref null (shared array)))) + (global (import "spectest" "global_ref_array") (shared (ref (shared array)))) + (global (import "spectest" "global_mut_ref_array") (shared mut (ref (shared array)))) + + ;; Initialized (long/short form). + (global (shared (shared arrayref)) (ref.null (shared array))) + (global (shared (ref null (shared array))) (ref.null (shared array))) +) + +(assert_invalid + (module (global (shared arrayref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null array)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared array)))) + "type mismatch") + +;; `i31` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_i31ref") (shared (shared i31ref))) + (global (import "spectest" "global_mut_i31ref") (shared mut (shared i31ref))) + (global (import "spectest" "global_ref_null_i31") (shared (ref null (shared i31)))) + (global (import "spectest" "global_mut_ref_null_i31") (shared mut (ref null (shared i31)))) + (global (import "spectest" "global_ref_i31") (shared (ref (shared i31)))) + (global (import "spectest" "global_mut_ref_i31") (shared mut (ref (shared i31)))) + + ;; Initialized (long/short form). + (global (shared (shared i31ref)) (ref.null (shared i31))) + (global (shared (ref null (shared i31))) (ref.null (shared i31))) +) + +(assert_invalid + (module (global (shared i31ref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null i31)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared i31)))) + "type mismatch") + +;; `nofunc` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_nullfuncref") (shared (shared nullfuncref))) + (global (import "spectest" "global_mut_nullfuncref") (shared mut (shared nullfuncref))) + (global (import "spectest" "global_ref_null_nofunc") (shared (ref null (shared nofunc)))) + (global (import "spectest" "global_mut_ref_null_nofunc") (shared mut (ref null (shared nofunc)))) + (global (import "spectest" "global_ref_nofunc") (shared (ref (shared nofunc)))) + (global (import "spectest" "global_mut_ref_nofunc") (shared mut (ref (shared nofunc)))) + + ;; Initialized (long/short form). + (global (shared (shared nullfuncref)) (ref.null (shared nofunc))) + (global (shared (ref null (shared nofunc))) (ref.null (shared nofunc))) +) + +(assert_invalid + (module (global (shared nullfuncref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null nofunc)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared nofunc)))) + "type mismatch") + +;; `noextern` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_nullexternref") (shared (shared nullexternref))) + (global (import "spectest" "global_mut_nullexternref") (shared mut (shared nullexternref))) + (global (import "spectest" "global_ref_null_noextern") (shared (ref null (shared noextern)))) + (global (import "spectest" "global_mut_ref_null_noextern") (shared mut (ref null (shared noextern)))) + (global (import "spectest" "global_ref_noextern") (shared (ref (shared noextern)))) + (global (import "spectest" "global_mut_ref_noextern") (shared mut (ref (shared noextern)))) + + ;; Initialized (long/short form). + (global (shared (shared nullexternref)) (ref.null (shared noextern))) + (global (shared (ref null (shared noextern))) (ref.null (shared noextern))) +) + +(assert_invalid + (module (global (shared nullexternref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null noextern)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared noextern)))) + "type mismatch") + +;; `noexn` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_nullexnref") (shared (shared nullexnref))) + (global (import "spectest" "global_mut_nullexnref") (shared mut (shared nullexnref))) + (global (import "spectest" "global_ref_null_noexn") (shared (ref null (shared noexn)))) + (global (import "spectest" "global_mut_ref_null_noexn") (shared mut (ref null (shared noexn)))) + (global (import "spectest" "global_ref_noexn") (shared (ref (shared noexn)))) + (global (import "spectest" "global_mut_ref_noexn") (shared mut (ref (shared noexn)))) + + ;; Initialized (long/short form). + (global (shared (shared nullexnref)) (ref.null (shared noexn))) + (global (shared (ref null (shared noexn))) (ref.null (shared noexn))) +) + +(assert_invalid + (module (global (shared nullexnref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null noexn)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared noexn)))) + "type mismatch") + +;; `none` references. +(module + ;; Imported (long/short forms, mut, null). + (global (import "spectest" "global_nullref") (shared (shared nullref))) + (global (import "spectest" "global_mut_nullref") (shared mut (shared nullref))) + (global (import "spectest" "global_ref_null_none") (shared (ref null (shared none)))) + (global (import "spectest" "global_mut_ref_null_none") (shared mut (ref null (shared none)))) + (global (import "spectest" "global_ref_none") (shared (ref (shared none)))) + (global (import "spectest" "global_mut_ref_none") (shared mut (ref (shared none)))) + + ;; Initialized (long/short form). + (global (shared (shared nullref)) (ref.null (shared none))) + (global (shared (ref null (shared none))) (ref.null (shared none))) +) + +(assert_invalid + (module (global (shared nullref))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (shared (ref null none)))) + "shared globals must have a shared value type") +(assert_invalid + (module (global (ref (shared none)))) + "type mismatch") + diff --git a/tests/local/shared-everything-threads/global.wast b/tests/local/shared-everything-threads/global.wast index ad33bf2bae..9d47be9ff0 100644 --- a/tests/local/shared-everything-threads/global.wast +++ b/tests/local/shared-everything-threads/global.wast @@ -35,7 +35,6 @@ "shared value type") ;; Check global.atomic.get, global.atomic.set. - (module (global $a (import "spectest" "global_i32") (shared i32)) (global $b (import "spectest" "global_i64") (shared mut i64)) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json new file mode 100644 index 0000000000..526b9da758 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json @@ -0,0 +1,317 @@ +{ + "source_filename": "tests/local/shared-everything-threads/global-heap-types.wast", + "commands": [ + { + "type": "module", + "line": 13, + "filename": "global-heap-types.0.wasm" + }, + { + "type": "assert_invalid", + "line": 28, + "filename": "global-heap-types.1.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 31, + "filename": "global-heap-types.2.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 34, + "filename": "global-heap-types.3.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 38, + "filename": "global-heap-types.4.wasm" + }, + { + "type": "assert_invalid", + "line": 53, + "filename": "global-heap-types.5.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 56, + "filename": "global-heap-types.6.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 59, + "filename": "global-heap-types.7.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 63, + "filename": "global-heap-types.8.wasm" + }, + { + "type": "assert_invalid", + "line": 78, + "filename": "global-heap-types.9.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 81, + "filename": "global-heap-types.10.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 84, + "filename": "global-heap-types.11.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 88, + "filename": "global-heap-types.12.wasm" + }, + { + "type": "assert_invalid", + "line": 103, + "filename": "global-heap-types.13.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 106, + "filename": "global-heap-types.14.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 109, + "filename": "global-heap-types.15.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 113, + "filename": "global-heap-types.16.wasm" + }, + { + "type": "assert_invalid", + "line": 128, + "filename": "global-heap-types.17.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 131, + "filename": "global-heap-types.18.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 134, + "filename": "global-heap-types.19.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 138, + "filename": "global-heap-types.20.wasm" + }, + { + "type": "assert_invalid", + "line": 153, + "filename": "global-heap-types.21.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 156, + "filename": "global-heap-types.22.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 159, + "filename": "global-heap-types.23.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 163, + "filename": "global-heap-types.24.wasm" + }, + { + "type": "assert_invalid", + "line": 178, + "filename": "global-heap-types.25.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 181, + "filename": "global-heap-types.26.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 184, + "filename": "global-heap-types.27.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 188, + "filename": "global-heap-types.28.wasm" + }, + { + "type": "assert_invalid", + "line": 203, + "filename": "global-heap-types.29.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 206, + "filename": "global-heap-types.30.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 209, + "filename": "global-heap-types.31.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 213, + "filename": "global-heap-types.32.wasm" + }, + { + "type": "assert_invalid", + "line": 228, + "filename": "global-heap-types.33.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 231, + "filename": "global-heap-types.34.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 234, + "filename": "global-heap-types.35.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 238, + "filename": "global-heap-types.36.wasm" + }, + { + "type": "assert_invalid", + "line": 253, + "filename": "global-heap-types.37.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 256, + "filename": "global-heap-types.38.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 259, + "filename": "global-heap-types.39.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 263, + "filename": "global-heap-types.40.wasm" + }, + { + "type": "assert_invalid", + "line": 278, + "filename": "global-heap-types.41.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 281, + "filename": "global-heap-types.42.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 284, + "filename": "global-heap-types.43.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "module", + "line": 288, + "filename": "global-heap-types.44.wasm" + }, + { + "type": "assert_invalid", + "line": 303, + "filename": "global-heap-types.45.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 306, + "filename": "global-heap-types.46.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 309, + "filename": "global-heap-types.47.wasm", + "text": "type mismatch", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/0.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/0.print new file mode 100644 index 0000000000..c9606e272f --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/0.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_funcref" (global (;0;) (shared (ref null (shared func))))) + (import "spectest" "global_mut_funcref" (global (;1;) (shared mut (ref null (shared func))))) + (import "spectest" "global_ref_null_func" (global (;2;) (shared (ref null (shared func))))) + (import "spectest" "global_mut_ref_null_func" (global (;3;) (shared mut (ref null (shared func))))) + (import "spectest" "global_ref_func" (global (;4;) (shared (ref (shared func))))) + (import "spectest" "global_mut_ref_func" (global (;5;) (shared mut (ref (shared func))))) + (global (;6;) (shared (ref null (shared func))) ref.null (shared func)) + (global (;7;) (shared (ref null (shared func))) ref.null (shared func)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/12.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/12.print new file mode 100644 index 0000000000..0c408cd830 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/12.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_anyref" (global (;0;) (shared (ref null (shared any))))) + (import "spectest" "global_mut_anyref" (global (;1;) (shared mut (ref null (shared any))))) + (import "spectest" "global_ref_null_any" (global (;2;) (shared (ref null (shared any))))) + (import "spectest" "global_mut_ref_null_any" (global (;3;) (shared mut (ref null (shared any))))) + (import "spectest" "global_ref_any" (global (;4;) (shared (ref (shared any))))) + (import "spectest" "global_mut_ref_any" (global (;5;) (shared mut (ref (shared any))))) + (global (;6;) (shared (ref null (shared any))) ref.null (shared any)) + (global (;7;) (shared (ref null (shared any))) ref.null (shared any)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/16.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/16.print new file mode 100644 index 0000000000..63238fb9f1 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/16.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_eqref" (global (;0;) (shared (ref null (shared eq))))) + (import "spectest" "global_mut_eqref" (global (;1;) (shared mut (ref null (shared eq))))) + (import "spectest" "global_ref_null_eq" (global (;2;) (shared (ref null (shared eq))))) + (import "spectest" "global_mut_ref_null_eq" (global (;3;) (shared mut (ref null (shared eq))))) + (import "spectest" "global_ref_eq" (global (;4;) (shared (ref (shared eq))))) + (import "spectest" "global_mut_ref_eq" (global (;5;) (shared mut (ref (shared eq))))) + (global (;6;) (shared (ref null (shared eq))) ref.null (shared eq)) + (global (;7;) (shared (ref null (shared eq))) ref.null (shared eq)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/20.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/20.print new file mode 100644 index 0000000000..364673e097 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/20.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_structref" (global (;0;) (shared (ref null (shared struct))))) + (import "spectest" "global_mut_structref" (global (;1;) (shared mut (ref null (shared struct))))) + (import "spectest" "global_ref_null_struct" (global (;2;) (shared (ref null (shared struct))))) + (import "spectest" "global_mut_ref_null_struct" (global (;3;) (shared mut (ref null (shared struct))))) + (import "spectest" "global_ref_struct" (global (;4;) (shared (ref (shared struct))))) + (import "spectest" "global_mut_ref_struct" (global (;5;) (shared mut (ref (shared struct))))) + (global (;6;) (shared (ref null (shared struct))) ref.null (shared struct)) + (global (;7;) (shared (ref null (shared struct))) ref.null (shared struct)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/24.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/24.print new file mode 100644 index 0000000000..834454e7c4 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/24.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_arrayref" (global (;0;) (shared (ref null (shared array))))) + (import "spectest" "global_mut_arrayref" (global (;1;) (shared mut (ref null (shared array))))) + (import "spectest" "global_ref_null_array" (global (;2;) (shared (ref null (shared array))))) + (import "spectest" "global_mut_ref_null_array" (global (;3;) (shared mut (ref null (shared array))))) + (import "spectest" "global_ref_array" (global (;4;) (shared (ref (shared array))))) + (import "spectest" "global_mut_ref_array" (global (;5;) (shared mut (ref (shared array))))) + (global (;6;) (shared (ref null (shared array))) ref.null (shared array)) + (global (;7;) (shared (ref null (shared array))) ref.null (shared array)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/28.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/28.print new file mode 100644 index 0000000000..b2ececf7b1 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/28.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_i31ref" (global (;0;) (shared (ref null (shared i31))))) + (import "spectest" "global_mut_i31ref" (global (;1;) (shared mut (ref null (shared i31))))) + (import "spectest" "global_ref_null_i31" (global (;2;) (shared (ref null (shared i31))))) + (import "spectest" "global_mut_ref_null_i31" (global (;3;) (shared mut (ref null (shared i31))))) + (import "spectest" "global_ref_i31" (global (;4;) (shared (ref (shared i31))))) + (import "spectest" "global_mut_ref_i31" (global (;5;) (shared mut (ref (shared i31))))) + (global (;6;) (shared (ref null (shared i31))) ref.null (shared i31)) + (global (;7;) (shared (ref null (shared i31))) ref.null (shared i31)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/32.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/32.print new file mode 100644 index 0000000000..8a79cdb6c1 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/32.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_nullfuncref" (global (;0;) (shared (ref null (shared nofunc))))) + (import "spectest" "global_mut_nullfuncref" (global (;1;) (shared mut (ref null (shared nofunc))))) + (import "spectest" "global_ref_null_nofunc" (global (;2;) (shared (ref null (shared nofunc))))) + (import "spectest" "global_mut_ref_null_nofunc" (global (;3;) (shared mut (ref null (shared nofunc))))) + (import "spectest" "global_ref_nofunc" (global (;4;) (shared (ref (shared nofunc))))) + (import "spectest" "global_mut_ref_nofunc" (global (;5;) (shared mut (ref (shared nofunc))))) + (global (;6;) (shared (ref null (shared nofunc))) ref.null (shared nofunc)) + (global (;7;) (shared (ref null (shared nofunc))) ref.null (shared nofunc)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/36.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/36.print new file mode 100644 index 0000000000..a4665230d4 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/36.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_nullexternref" (global (;0;) (shared (ref null (shared noextern))))) + (import "spectest" "global_mut_nullexternref" (global (;1;) (shared mut (ref null (shared noextern))))) + (import "spectest" "global_ref_null_noextern" (global (;2;) (shared (ref null (shared noextern))))) + (import "spectest" "global_mut_ref_null_noextern" (global (;3;) (shared mut (ref null (shared noextern))))) + (import "spectest" "global_ref_noextern" (global (;4;) (shared (ref (shared noextern))))) + (import "spectest" "global_mut_ref_noextern" (global (;5;) (shared mut (ref (shared noextern))))) + (global (;6;) (shared (ref null (shared noextern))) ref.null (shared noextern)) + (global (;7;) (shared (ref null (shared noextern))) ref.null (shared noextern)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/4.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/4.print new file mode 100644 index 0000000000..be075a7290 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/4.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_externref" (global (;0;) (shared (ref null (shared extern))))) + (import "spectest" "global_mut_externref" (global (;1;) (shared mut (ref null (shared extern))))) + (import "spectest" "global_ref_null_extern" (global (;2;) (shared (ref null (shared extern))))) + (import "spectest" "global_mut_ref_null_extern" (global (;3;) (shared mut (ref null (shared extern))))) + (import "spectest" "global_ref_extern" (global (;4;) (shared (ref (shared extern))))) + (import "spectest" "global_mut_ref_extern" (global (;5;) (shared mut (ref (shared extern))))) + (global (;6;) (shared (ref null (shared extern))) ref.null (shared extern)) + (global (;7;) (shared (ref null (shared extern))) ref.null (shared extern)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/40.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/40.print new file mode 100644 index 0000000000..c1270ef906 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/40.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_nullexnref" (global (;0;) (shared (ref null (shared noexn))))) + (import "spectest" "global_mut_nullexnref" (global (;1;) (shared mut (ref null (shared noexn))))) + (import "spectest" "global_ref_null_noexn" (global (;2;) (shared (ref null (shared noexn))))) + (import "spectest" "global_mut_ref_null_noexn" (global (;3;) (shared mut (ref null (shared noexn))))) + (import "spectest" "global_ref_noexn" (global (;4;) (shared (ref (shared noexn))))) + (import "spectest" "global_mut_ref_noexn" (global (;5;) (shared mut (ref (shared noexn))))) + (global (;6;) (shared (ref null (shared noexn))) ref.null (shared noexn)) + (global (;7;) (shared (ref null (shared noexn))) ref.null (shared noexn)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/44.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/44.print new file mode 100644 index 0000000000..b141dfd3c2 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/44.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_nullref" (global (;0;) (shared (ref null (shared none))))) + (import "spectest" "global_mut_nullref" (global (;1;) (shared mut (ref null (shared none))))) + (import "spectest" "global_ref_null_none" (global (;2;) (shared (ref null (shared none))))) + (import "spectest" "global_mut_ref_null_none" (global (;3;) (shared mut (ref null (shared none))))) + (import "spectest" "global_ref_none" (global (;4;) (shared (ref (shared none))))) + (import "spectest" "global_mut_ref_none" (global (;5;) (shared mut (ref (shared none))))) + (global (;6;) (shared (ref null (shared none))) ref.null (shared none)) + (global (;7;) (shared (ref null (shared none))) ref.null (shared none)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/8.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/8.print new file mode 100644 index 0000000000..293ad9db43 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/8.print @@ -0,0 +1,10 @@ +(module + (import "spectest" "global_exnref" (global (;0;) (shared (ref null (shared exn))))) + (import "spectest" "global_mut_exnref" (global (;1;) (shared mut (ref null (shared exn))))) + (import "spectest" "global_ref_null_exn" (global (;2;) (shared (ref null (shared exn))))) + (import "spectest" "global_mut_ref_null_exn" (global (;3;) (shared mut (ref null (shared exn))))) + (import "spectest" "global_ref_exn" (global (;4;) (shared (ref (shared exn))))) + (import "spectest" "global_mut_ref_exn" (global (;5;) (shared mut (ref (shared exn))))) + (global (;6;) (shared (ref null (shared exn))) ref.null (shared exn)) + (global (;7;) (shared (ref null (shared exn))) ref.null (shared exn)) +) diff --git a/tests/snapshots/local/shared-everything-threads/global.wast.json b/tests/snapshots/local/shared-everything-threads/global.wast.json index 250e98b15b..3d4ab677aa 100644 --- a/tests/snapshots/local/shared-everything-threads/global.wast.json +++ b/tests/snapshots/local/shared-everything-threads/global.wast.json @@ -22,150 +22,150 @@ }, { "type": "module", - "line": 39, + "line": 38, "filename": "global.3.wasm" }, { "type": "assert_invalid", - "line": 52, + "line": 51, "filename": "global.4.wasm", "text": "global is immutable", "module_type": "binary" }, { "type": "assert_invalid", - "line": 59, + "line": 58, "filename": "global.5.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 66, + "line": 65, "filename": "global.6.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 73, + "line": 72, "filename": "global.7.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 80, + "line": 79, "filename": "global.8.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 87, + "line": 86, "filename": "global.9.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 94, + "line": 93, "filename": "global.10.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 101, + "line": 100, "filename": "global.11.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 108, + "line": 107, "filename": "global.12.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 115, + "line": 114, "filename": "global.13.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 122, + "line": 121, "filename": "global.14.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 129, + "line": 128, "filename": "global.15.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 136, + "line": 135, "filename": "global.16.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 143, + "line": 142, "filename": "global.17.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 150, + "line": 149, "filename": "global.18.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 157, + "line": 156, "filename": "global.19.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 164, + "line": 163, "filename": "global.20.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 171, + "line": 170, "filename": "global.21.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 178, + "line": 177, "filename": "global.22.wasm", "text": "invalid type", "module_type": "binary" }, { "type": "module", - "line": 185, + "line": 184, "filename": "global.23.wasm" }, { "type": "module", - "line": 278, + "line": 277, "filename": "global.24.wasm" } ] From 10e4e2c057ff8374c197261e4180a5f1b658d72a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 12 Jun 2024 12:30:12 -0500 Subject: [PATCH 09/58] Improve filtering for return_call_indirect with wasm-smith (#1613) * Improve filtering for `return_call_indirect` with `wasm-smith` Add to the `call_indirect` filter that the selected function type must have the exact same return signature as the function doing the call. Closes #1612 * Add back in tail-call filter --- crates/wasm-smith/src/core/code_builder.rs | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index aef756b402..d93ce0c012 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -2044,6 +2044,14 @@ fn call_ref( #[inline] fn call_indirect_valid(module: &Module, builder: &mut CodeBuilder) -> bool { + call_indirect_valid_impl(module, builder, false) +} + +fn call_indirect_valid_impl( + module: &Module, + builder: &mut CodeBuilder, + is_return_call: bool, +) -> bool { if module.config.disallow_traps { // We have no way to reflect, at run time, on a `funcref` in // the `i`th slot in a table and dynamically avoid trapping @@ -2059,9 +2067,10 @@ fn call_indirect_valid(module: &Module, builder: &mut CodeBuilder) -> bool { return false; } let ty = builder.allocs.operands.pop().unwrap(); - let is_valid = module - .func_types() - .any(|(_, ty)| builder.types_on_stack(module, &ty.params)); + let is_valid = module.func_types().any(|(_, ty)| { + builder.types_on_stack(module, &ty.params) + && (!is_return_call || builder.allocs.controls[0].label_types() == &ty.results) + }); builder.allocs.operands.push(ty); is_valid } @@ -2197,8 +2206,7 @@ fn return_call_indirect_valid(module: &Module, builder: &mut CodeBuilder) -> boo if !module.config.tail_call_enabled { return false; } - - call_indirect_valid(module, builder) + call_indirect_valid_impl(module, builder, true) } fn return_call_indirect( From 38a0b166faa92697be9ad7738cbfe80034108c11 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 12 Jun 2024 16:01:34 -0500 Subject: [PATCH 10/58] Handle a few more cases for color in `wasmprinter` (#1614) Migrating more things to `start_group`, `end_group`, etc. --- crates/wasmprinter/src/lib.rs | 63 ++++++++++++++++++++--------------- 1 file changed, 36 insertions(+), 27 deletions(-) diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index 5b86607dad..8775bfddf9 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -1329,10 +1329,12 @@ impl Printer<'_, '_> { branch_hints = rest; op_printer.printer.newline(*hint_offset)?; let desc = if hint.taken { "\"\\01\"" } else { "\"\\00\"" }; + op_printer.printer.result.start_comment()?; write!( op_printer.printer.result, "(@metadata.code.branch_hint {desc})", )?; + op_printer.printer.result.reset_color()?; } } @@ -1525,9 +1527,10 @@ impl Printer<'_, '_> { } => { let table_index = table_index.unwrap_or(0); if table_index != 0 { - self.result.write_str(" (table ")?; + self.result.write_str(" ")?; + self.start_group("table ")?; self.print_idx(&state.core.table_names, table_index)?; - self.result.write_str(")")?; + self.end_group()?; } self.result.write_str(" ")?; self.print_const_expr_sugar(state, offset_expr, "offset")?; @@ -1574,9 +1577,10 @@ impl Printer<'_, '_> { offset_expr, } => { if *memory_index != 0 { - self.result.write_str("(memory ")?; + self.start_group("memory ")?; self.print_idx(&state.core.memory_names, *memory_index)?; - self.result.write_str(") ")?; + self.end_group()?; + self.result.write_str(" ")?; } self.print_const_expr_sugar(state, offset_expr, "offset")?; self.result.write_str(" ")?; @@ -1634,19 +1638,19 @@ impl Printer<'_, '_> { fn print_primitive_val_type(&mut self, ty: &PrimitiveValType) -> Result<()> { match ty { - PrimitiveValType::Bool => self.result.write_str("bool")?, - PrimitiveValType::S8 => self.result.write_str("s8")?, - PrimitiveValType::U8 => self.result.write_str("u8")?, - PrimitiveValType::S16 => self.result.write_str("s16")?, - PrimitiveValType::U16 => self.result.write_str("u16")?, - PrimitiveValType::S32 => self.result.write_str("s32")?, - PrimitiveValType::U32 => self.result.write_str("u32")?, - PrimitiveValType::S64 => self.result.write_str("s64")?, - PrimitiveValType::U64 => self.result.write_str("u64")?, - PrimitiveValType::F32 => self.result.write_str("f32")?, - PrimitiveValType::F64 => self.result.write_str("f64")?, - PrimitiveValType::Char => self.result.write_str("char")?, - PrimitiveValType::String => self.result.write_str("string")?, + PrimitiveValType::Bool => self.print_type_keyword("bool")?, + PrimitiveValType::S8 => self.print_type_keyword("s8")?, + PrimitiveValType::U8 => self.print_type_keyword("u8")?, + PrimitiveValType::S16 => self.print_type_keyword("s16")?, + PrimitiveValType::U16 => self.print_type_keyword("u16")?, + PrimitiveValType::S32 => self.print_type_keyword("s32")?, + PrimitiveValType::U32 => self.print_type_keyword("u32")?, + PrimitiveValType::S64 => self.print_type_keyword("s64")?, + PrimitiveValType::U64 => self.print_type_keyword("u64")?, + PrimitiveValType::F32 => self.print_type_keyword("f32")?, + PrimitiveValType::F64 => self.print_type_keyword("f64")?, + PrimitiveValType::Char => self.print_type_keyword("char")?, + PrimitiveValType::String => self.print_type_keyword("string")?, } Ok(()) } @@ -1985,14 +1989,17 @@ impl Printer<'_, '_> { } ComponentType::Resource { rep, dtor } => { self.result.write_str(" ")?; - self.start_group("resource")?; - self.result.write_str(" (rep ")?; + self.start_group("resource ")?; + self.start_group("rep ")?; self.print_valtype(states.last().unwrap(), rep)?; - self.result.write_str(")")?; + self.end_group()?; if let Some(dtor) = dtor { - self.result.write_str(" (dtor (func ")?; + self.result.write_str(" ")?; + self.start_group("dtor ")?; + self.start_group("func ")?; self.print_idx(&states.last().unwrap().core.func_names, dtor)?; - self.result.write_str("))")?; + self.end_group()?; + self.end_group()?; } self.end_group()?; } @@ -2087,7 +2094,7 @@ impl Printer<'_, '_> { self.end_group()?; } ComponentTypeRef::Type(bounds) => { - self.result.write_str("(type ")?; + self.start_group("type ")?; if index { self.print_name(&state.component.type_names, state.component.types)?; self.result.write_str(" ")?; @@ -2095,15 +2102,17 @@ impl Printer<'_, '_> { } match bounds { TypeBounds::Eq(idx) => { - self.result.write_str("(eq ")?; + self.start_group("eq ")?; self.print_idx(&state.component.type_names, *idx)?; - self.result.write_str(")")?; + self.end_group()?; } TypeBounds::SubResource => { - self.result.write_str("(sub resource)")?; + self.start_group("sub ")?; + self.print_type_keyword("resource")?; + self.end_group()?; } }; - self.result.write_str(")")?; + self.end_group()?; } ComponentTypeRef::Instance(idx) => { self.start_group("instance ")?; From d6436035fefa4861e64b27d0c416cfe9f309369d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 12 Jun 2024 17:45:46 -0500 Subject: [PATCH 11/58] Use quoted identifiers in `wasmprinter` by default (#1615) * Use quoted identifiers in `wasmprinter` by default This commit updates `wasmprinter` to use quoted identifiers of the form `$"foo"` when necessary instead of synthesizing identifiers such as `$#func4`. This helps produce more readable modules by default when names are synthesized since if a name is unique but otherwise has non-identifier characters then the quoted string form can be used. While here I've additionally changed the way that non-printable characters in strings are printed to using `\u{XXX}` syntax instead of `\NN` syntax. This makes it a bit more obvious in unicode contexts that a single character is present and not multiple. * Fix some test expectations * Migrate a number of `wasmprinter` unit tests to `tests/cli/*.wat` This makes them easier to update as the output changes over time and additionally easier to add new files here too. --- crates/wasmprinter/src/lib.rs | 114 ++- crates/wasmprinter/src/operator.rs | 4 +- crates/wasmprinter/tests/all.rs | 248 ----- .../components/export-resource/component.wat | 4 +- .../component.wat | 4 +- tests/cli/dangling_if.wat | 5 + tests/cli/dangling_if.wat.stdout | 7 + tests/cli/demangle1.wat.stdout | 2 +- tests/cli/label_shadowing_block.wat | 11 + tests/cli/label_shadowing_block.wat.stdout | 11 + tests/cli/label_shadowing_block_confusion.wat | 12 + ...label_shadowing_block_confusion.wat.stdout | 10 + tests/cli/label_shadowing_locals.wat | 8 + tests/cli/label_shadowing_locals.wat.stdout | 7 + tests/cli/no_panic_non_func_type.wat | 6 + tests/cli/no_panic_non_func_type.wat.stdout | 4 + tests/cli/print-code-section-overflow.wat | 7 + .../print-code-section-overflow.wat.stderr | 1 + .../print-code-section-overflow.wat.stdout | 1 + tests/cli/print-dont-reserve-the-world.wat | 9 + .../print-dont-reserve-the-world.wat.stderr | 1 + .../print-dont-reserve-the-world.wat.stdout | 1 + tests/cli/print-locals-overflow.wat | 23 + tests/cli/print-locals-overflow.wat.stderr | 1 + tests/cli/print-locals-overflow.wat.stdout | 3 + tests/cli/print-memarg-too-big.wat | 15 + tests/cli/print-memarg-too-big.wat.stderr | 1 + tests/cli/print-memarg-too-big.wat.stdout | 2 + tests/cli/print-no-panic-bad-data-segment.wat | 5 + ...print-no-panic-bad-data-segment.wat.stdout | 3 + tests/cli/print-no-panic-dangling-else.wat | 5 + .../print-no-panic-dangling-else.wat.stdout | 6 + tests/cli/print-no-panic-double-end.wat | 5 + .../cli/print-no-panic-double-end.wat.stdout | 4 + tests/cli/shared_global.wat | 4 + tests/cli/shared_global.wat.stdout | 3 + tests/snapshots/local/names.wast/0.print | 2 +- tests/snapshots/local/names.wast/2.print | 2 +- tests/snapshots/local/names.wast/3.print | 2 +- tests/snapshots/local/names.wast/4.print | 2 +- tests/snapshots/local/names.wast/5.print | 2 +- tests/snapshots/testsuite/custom.wast/0.print | 6 +- tests/snapshots/testsuite/names.wast/4.print | 920 +++++++++--------- .../proposals/annotations/id.wast/0.print | 24 +- .../function-references/custom.wast/0.print | 6 +- 45 files changed, 734 insertions(+), 789 deletions(-) create mode 100644 tests/cli/dangling_if.wat create mode 100644 tests/cli/dangling_if.wat.stdout create mode 100644 tests/cli/label_shadowing_block.wat create mode 100644 tests/cli/label_shadowing_block.wat.stdout create mode 100644 tests/cli/label_shadowing_block_confusion.wat create mode 100644 tests/cli/label_shadowing_block_confusion.wat.stdout create mode 100644 tests/cli/label_shadowing_locals.wat create mode 100644 tests/cli/label_shadowing_locals.wat.stdout create mode 100644 tests/cli/no_panic_non_func_type.wat create mode 100644 tests/cli/no_panic_non_func_type.wat.stdout create mode 100644 tests/cli/print-code-section-overflow.wat create mode 100644 tests/cli/print-code-section-overflow.wat.stderr create mode 100644 tests/cli/print-code-section-overflow.wat.stdout create mode 100644 tests/cli/print-dont-reserve-the-world.wat create mode 100644 tests/cli/print-dont-reserve-the-world.wat.stderr create mode 100644 tests/cli/print-dont-reserve-the-world.wat.stdout create mode 100644 tests/cli/print-locals-overflow.wat create mode 100644 tests/cli/print-locals-overflow.wat.stderr create mode 100644 tests/cli/print-locals-overflow.wat.stdout create mode 100644 tests/cli/print-memarg-too-big.wat create mode 100644 tests/cli/print-memarg-too-big.wat.stderr create mode 100644 tests/cli/print-memarg-too-big.wat.stdout create mode 100644 tests/cli/print-no-panic-bad-data-segment.wat create mode 100644 tests/cli/print-no-panic-bad-data-segment.wat.stdout create mode 100644 tests/cli/print-no-panic-dangling-else.wat create mode 100644 tests/cli/print-no-panic-dangling-else.wat.stdout create mode 100644 tests/cli/print-no-panic-double-end.wat create mode 100644 tests/cli/print-no-panic-double-end.wat.stdout create mode 100644 tests/cli/shared_global.wat create mode 100644 tests/cli/shared_global.wat.stdout diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index 8775bfddf9..a6dd257539 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -147,8 +147,14 @@ impl State { } struct Naming { - identifier: Option, name: String, + kind: NamingKind, +} + +enum NamingKind { + DollarName, + DollarQuotedName, + SyntheticPrefix(String), } impl Config { @@ -410,7 +416,7 @@ impl Printer<'_, '_> { if len == 1 { if let Some(name) = state.name.as_ref() { self.result.write_str(" ")?; - name.write(self.result)?; + name.write(self)?; } } } @@ -925,7 +931,10 @@ impl Printer<'_, '_> { self.result.write_str(" ")?; if let Some(idxs @ (_, field_idx)) = ty_field_idx { match state.core.field_names.index_to_name.get(&idxs) { - Some(name) => write!(self.result, "${} ", name.identifier())?, + Some(name) => { + name.write_identifier(self)?; + self.result.write_str(" ")?; + } None if self.config.name_unnamed => write!(self.result, "$#field{field_idx} ")?, None => {} } @@ -1453,7 +1462,7 @@ impl Printer<'_, '_> { fn _print_idx(&mut self, names: &HashMap, idx: u32, desc: &str) -> Result<()> { self.result.start_name()?; match names.get(&idx) { - Some(name) => write!(self.result, "${}", name.identifier())?, + Some(name) => name.write_identifier(self)?, None if self.config.name_unnamed => write!(self.result, "$#{desc}{idx}")?, None => write!(self.result, "{idx}")?, } @@ -1464,7 +1473,7 @@ impl Printer<'_, '_> { fn print_local_idx(&mut self, state: &State, func: u32, idx: u32) -> Result<()> { self.result.start_name()?; match state.core.local_names.index_to_name.get(&(func, idx)) { - Some(name) => write!(self.result, "${}", name.identifier())?, + Some(name) => name.write_identifier(self)?, None if self.config.name_unnamed => write!(self.result, "$#local{idx}")?, None => write!(self.result, "{}", idx)?, } @@ -1475,7 +1484,7 @@ impl Printer<'_, '_> { fn print_field_idx(&mut self, state: &State, ty: u32, idx: u32) -> Result<()> { self.result.start_name()?; match state.core.field_names.index_to_name.get(&(ty, idx)) { - Some(name) => write!(self.result, "${}", name.identifier())?, + Some(name) => name.write_identifier(self)?, None if self.config.name_unnamed => write!(self.result, "$#field{idx}")?, None => write!(self.result, "{}", idx)?, } @@ -1499,7 +1508,7 @@ impl Printer<'_, '_> { self.result.start_name()?; match names.get(&cur_idx) { Some(name) => { - name.write(self.result)?; + name.write(self)?; self.result.write_str(" ")?; } None if self.config.name_unnamed => { @@ -1911,7 +1920,7 @@ impl Printer<'_, '_> { let outer = Self::outer_state(states, count)?; self.start_group("alias outer ")?; if let Some(name) = outer.name.as_ref() { - name.write(self.result)?; + name.write(self)?; } else { write!(self.result, "{count}")?; } @@ -2592,7 +2601,7 @@ impl Printer<'_, '_> { let outer = Self::outer_state(states, count)?; self.start_group("alias outer ")?; if let Some(name) = outer.name.as_ref() { - name.write(self.result)?; + name.write(self)?; } else { write!(self.result, "{count}")?; } @@ -2643,20 +2652,22 @@ impl Printer<'_, '_> { fn print_str(&mut self, name: &str) -> Result<()> { self.result.start_literal()?; - let mut bytes = [0; 4]; self.result.write_str("\"")?; + self.print_str_contents(name)?; + self.result.write_str("\"")?; + self.result.reset_color()?; + Ok(()) + } + + fn print_str_contents(&mut self, name: &str) -> Result<()> { for c in name.chars() { let v = c as u32; if (0x20..0x7f).contains(&v) && c != '"' && c != '\\' && v < 0xff { write!(self.result, "{c}")?; } else { - for byte in c.encode_utf8(&mut bytes).as_bytes() { - self.hex_byte(*byte)?; - } + write!(self.result, "\\u{{{v:x}}}",)?; } } - self.result.write_str("\"")?; - self.result.reset_color()?; Ok(()) } @@ -2917,7 +2928,7 @@ impl NamedLocalPrinter { // Print the optional name if given... match name { Some(name) => { - name.write(dst.result)?; + name.write(dst)?; dst.result.write_str(" ")?; self.end_group_after_local = true; } @@ -3057,7 +3068,10 @@ impl Naming { group: &str, used: Option<&mut HashSet<&'a str>>, ) -> Naming { - let mut identifier = None; + let mut kind = NamingKind::DollarName; + if name.chars().any(|c| !is_idchar(c)) { + kind = NamingKind::DollarQuotedName; + } // If the `name` provided can't be used as the raw identifier for the // item that it's describing then a synthetic name must be made. The @@ -3078,17 +3092,13 @@ impl Naming { // valid identifier characters of `name` still appear in the returned // name). if name.is_empty() - || name.chars().any(|c| !is_idchar(c)) || name.starts_with('#') || used.map(|set| !set.insert(name)).unwrap_or(false) { - let mut id = format!("#{group}{index}<"); - id.extend(name.chars().map(|c| if is_idchar(c) { c } else { '_' })); - id.push('>'); - identifier = Some(id); + kind = NamingKind::SyntheticPrefix(format!("#{group}{index}")); } return Naming { - identifier, + kind, name: name.to_string(), }; @@ -3126,37 +3136,39 @@ impl Naming { } } - fn identifier(&self) -> &str { - match &self.identifier { - Some(s) => s, - None => &self.name, + fn write_identifier(&self, printer: &mut Printer<'_, '_>) -> Result<()> { + match &self.kind { + NamingKind::DollarName => { + printer.result.write_str("$")?; + printer.result.write_str(&self.name)?; + } + NamingKind::DollarQuotedName => { + printer.result.write_str("$\"")?; + printer.print_str_contents(&self.name)?; + printer.result.write_str("\"")?; + } + NamingKind::SyntheticPrefix(prefix) => { + printer.result.write_str("$\"")?; + printer.result.write_str(&prefix)?; + printer.result.write_str(" ")?; + printer.print_str_contents(&self.name)?; + printer.result.write_str("\"")?; + } } + Ok(()) } - fn write(&self, dst: &mut dyn Print) -> Result<()> { - match &self.identifier { - Some(alternate) => { - assert!(*alternate != self.name); - write!(dst, "${alternate} (@name \"")?; - // https://webassembly.github.io/spec/core/text/values.html#text-string - for c in self.name.chars() { - match c { - '\t' => write!(dst, "\\t")?, - '\n' => write!(dst, "\\n")?, - '\r' => write!(dst, "\\r")?, - '"' => write!(dst, "\\\"")?, - '\'' => write!(dst, "\\'")?, - '\\' => write!(dst, "\\\\")?, - c if (c as u32) < 0x20 || c as u32 == 0x7f => { - write!(dst, "\\u{{{:x}}}", c as u32)?; - } - other => write!(dst, "{other}")?, - } - } - write!(dst, "\")")?; - } - None => { - write!(dst, "${}", self.name)?; + fn write(&self, dst: &mut Printer<'_, '_>) -> Result<()> { + self.write_identifier(dst)?; + match &self.kind { + NamingKind::DollarName | NamingKind::DollarQuotedName => {} + + NamingKind::SyntheticPrefix(_) => { + dst.result.write_str(" ")?; + dst.start_group("@name \"")?; + dst.print_str_contents(&self.name)?; + dst.result.write_str("\"")?; + dst.end_group()?; } } Ok(()) diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs index 41205a9fe1..8aac9020a3 100644 --- a/crates/wasmprinter/src/operator.rs +++ b/crates/wasmprinter/src/operator.rs @@ -86,7 +86,7 @@ impl<'printer, 'state, 'a, 'b> PrintOperator<'printer, 'state, 'a, 'b> { let has_name = match self.state.core.label_names.index_to_name.get(&key) { Some(name) => { write!(self.printer.result, " ")?; - name.write(self.printer.result)?; + name.write(self.printer)?; true } None if self.printer.config.name_unnamed => { @@ -176,7 +176,7 @@ impl<'printer, 'state, 'a, 'b> PrintOperator<'printer, 'state, 'a, 'b> { match name { // Only print the name if one is found and there's also no // name conflict. - Some(name) if !name_conflict => name.write(self.printer.result)?, + Some(name) if !name_conflict => name.write(self.printer)?, // If there's no name conflict, and we're synthesizing // names, and this isn't targetting the function itself then diff --git a/crates/wasmprinter/tests/all.rs b/crates/wasmprinter/tests/all.rs index 66de146162..114f316124 100644 --- a/crates/wasmprinter/tests/all.rs +++ b/crates/wasmprinter/tests/all.rs @@ -1,137 +1,3 @@ -#[test] -fn no_panic() { - let bytes = wat::parse_str( - r#" - (module - (data (br 4294967295) "") - ) - "#, - ) - .unwrap(); - wasmprinter::print_bytes(&bytes).unwrap(); - - let bytes = wat::parse_str( - r#" - (module - (func end) - ) - "#, - ) - .unwrap(); - wasmprinter::print_bytes(&bytes).unwrap(); -} - -#[test] -fn code_section_overflow() { - let bytes = wat::parse_str( - r#" - (module binary - "\00asm" - "\01\00\00\00" - "\0a\10\01" - ) - "#, - ) - .unwrap(); - let err = wasmprinter::print_bytes(&bytes).unwrap_err(); - assert!( - err.to_string().contains("unexpected end-of-file"), - "{:?}", - err - ); -} - -#[test] -fn locals_overflow() { - let bytes = wat::parse_str( - r#" - (module binary - "\00asm" "\01\00\00\00" ;; module header - - "\01" ;; type section - "\04" ;; size of section - "\01" ;; one type - "\60\00\00" ;; function, no parameters or results - - "\03" ;; function section - "\02" ;; size of function section - "\01" ;; one function - "\00" ;; type 0 - - "\0a" ;; code section - "\09" ;; size of code section - "\01" ;; 1 function - "\07" ;; size of function - "\01" ;; one local - "\ff\ff\ff\ff\00" ;; lots of this type - "\70" ;; type - ) - "#, - ) - .unwrap(); - let err = wasmprinter::print_bytes(&bytes).unwrap_err(); - assert!( - err.to_string().contains("maximum number of locals"), - "{:?}", - err - ); -} - -#[test] -fn memarg_too_big() { - let bytes = wat::parse_str( - r#" - (module binary - "\00asm" "\01\00\00\00" ;; module header - - "\0b" ;; data section - "\07" ;; size of section - "\01" ;; number of segments - "\00" ;; flags=active - "\2e" ;; i32.load16_s - "\3f" ;; alignment - "\00" ;; offset - "\0b" ;; end - "\00" ;; data size - ) - "#, - ) - .unwrap(); - let err = wasmprinter::print_bytes(&bytes).unwrap_err(); - assert!( - err.to_string().contains("alignment in memarg too large"), - "{:?}", - err - ); -} - -#[test] -fn no_panic_dangling_else() { - let bytes = wat::parse_str( - r#" - (module - (func else) - ) - "#, - ) - .unwrap(); - wasmprinter::print_bytes(&bytes).unwrap(); -} - -#[test] -fn dangling_if() { - let bytes = wat::parse_str( - r#" - (module - (func if) - ) - "#, - ) - .unwrap(); - let wat = wasmprinter::print_bytes(&bytes).unwrap(); - wat::parse_str(&wat).unwrap(); -} - #[test] fn no_oom() { // Whatever is printed here, it shouldn't take more than 500MB to print @@ -147,94 +13,6 @@ fn no_oom() { assert!(wat.len() < 500_000_000); } -#[test] -fn dont_reserve_the_world() { - let bytes = wat::parse_str( - r#" - (module binary - "\00asm" "\01\00\00\00" ;; module header - - "\03" ;; function section - "\05" ;; section size - "\ff\ff\ff\ff\0f" ;; number of functions (u32::MAX) - ) - "#, - ) - .unwrap(); - let err = wasmprinter::print_bytes(&bytes).unwrap_err(); - assert!( - err.to_string() - .contains("functions which exceeds the limit"), - "{:?}", - err - ); -} - -#[test] -fn label_shadowing_block() { - const MODULE: &str = r#" - (module - (type (;0;) (func)) - (func (;0;) (type 0) - block $a - br $a - end - block $a - br $a - end - ) - ) - "#; - let bytes = wat::parse_str(MODULE).unwrap(); - let result = wasmprinter::print_bytes(&bytes).unwrap(); - assert_eq!( - result.replace(" ", "").trim(), - MODULE.replace(" ", "").trim() - ); -} - -#[test] -fn label_shadowing_block_confusion() { - // Make sure we don’t refer to a shadowed label via a name. - const MODULE: &str = r#" - (module - (type (;0;) (func)) - (func (;0;) (type 0) - block $a - block $a - br 1 - end - end - ) - ) - "#; - let bytes = wat::parse_str(MODULE).unwrap(); - let result = wasmprinter::print_bytes(&bytes).unwrap(); - assert_eq!( - result.replace(" ", "").trim(), - MODULE.replace(" ", "").trim() - ); -} - -#[test] -fn label_shadowing_locals() { - const MODULE: &str = r#" - (module - (type (;0;) (func (param i32) (result i32))) - (func (;0;) (type 0) (param $l i32) (result i32) - (local $#local1 (@name "l") i32) (local $#local2 (@name "l") i32) - local.get $l - ) - ) - "#; - let bytes = wat::parse_str(MODULE).unwrap(); - let result = wasmprinter::print_bytes(&bytes).unwrap(); - assert_eq!( - result.replace(" ", "").trim(), - MODULE.replace(" ", "").trim() - ); -} - #[test] fn offsets_and_lines_smoke_test() { const MODULE: &str = r#" @@ -268,29 +46,3 @@ fn offsets_and_lines_smoke_test() { assert_eq!(actual, expected); } - -#[test] -fn no_panic_non_func_type() { - let bytes = wat::parse_str( - "(module - (type (struct)) - (func (type 0)) - )", - ) - .unwrap(); - wasmprinter::print_bytes(&bytes).unwrap(); -} - -#[test] -fn shared_global() { - const MODULE: &str = r#" - (module - (global (;0;) (shared f32)) - )"#; - let bytes = wat::parse_str(MODULE).unwrap(); - let result = wasmprinter::print_bytes(&bytes).unwrap(); - assert_eq!( - result.replace(" ", "").trim(), - MODULE.replace(" ", "").trim() - ); -} diff --git a/crates/wit-component/tests/components/export-resource/component.wat b/crates/wit-component/tests/components/export-resource/component.wat index ff2dff3774..d6a3b4a052 100644 --- a/crates/wit-component/tests/components/export-resource/component.wat +++ b/crates/wit-component/tests/components/export-resource/component.wat @@ -25,13 +25,13 @@ ) (core module (;1;) (type (;0;) (func (param i32))) - (func $#func0 (@name "dtor-[export]foo-a") (;0;) (type 0) (param i32) + (func $"dtor-[export]foo-a" (;0;) (type 0) (param i32) local.get 0 i32.const 0 call_indirect (type 0) ) (table (;0;) 1 1 funcref) - (export "0" (func $#func0)) + (export "0" (func $"dtor-[export]foo-a")) (export "$imports" (table 0)) (@producers (processed-by "wit-component" "$CARGO_PKG_VERSION") diff --git a/crates/wit-component/tests/components/import-in-adapter-and-main-module/component.wat b/crates/wit-component/tests/components/import-in-adapter-and-main-module/component.wat index 47dce20f50..1bd0dfbde1 100644 --- a/crates/wit-component/tests/components/import-in-adapter-and-main-module/component.wat +++ b/crates/wit-component/tests/components/import-in-adapter-and-main-module/component.wat @@ -100,7 +100,7 @@ i32.const 1 call_indirect (type 0) ) - (func $#func2 (@name "indirect-foo:shared-dependency/doc-g1") (;2;) (type 0) (param i32) + (func $"#func2 indirect-foo:shared-dependency/doc-g1" (@name "indirect-foo:shared-dependency/doc-g1") (;2;) (type 0) (param i32) local.get 0 i32.const 2 call_indirect (type 0) @@ -117,7 +117,7 @@ (table (;0;) 5 5 funcref) (export "0" (func $indirect-foo:shared-dependency/doc-g1)) (export "1" (func $indirect-foo:shared-dependency/doc-g2)) - (export "2" (func $#func2)) + (export "2" (func $"#func2 indirect-foo:shared-dependency/doc-g1")) (export "3" (func $indirect-foo:shared-dependency/doc-g3)) (export "4" (func $adapt-old-adapter-f1)) (export "$imports" (table 0)) diff --git a/tests/cli/dangling_if.wat b/tests/cli/dangling_if.wat new file mode 100644 index 0000000000..420cb6da9c --- /dev/null +++ b/tests/cli/dangling_if.wat @@ -0,0 +1,5 @@ +;; RUN: print % | parse -t + +(module + (func if) +) diff --git a/tests/cli/dangling_if.wat.stdout b/tests/cli/dangling_if.wat.stdout new file mode 100644 index 0000000000..f7cec4aaab --- /dev/null +++ b/tests/cli/dangling_if.wat.stdout @@ -0,0 +1,7 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + if ;; label = @1 + + ) +) diff --git a/tests/cli/demangle1.wat.stdout b/tests/cli/demangle1.wat.stdout index feee6de87c..7171d9a1f6 100644 --- a/tests/cli/demangle1.wat.stdout +++ b/tests/cli/demangle1.wat.stdout @@ -2,5 +2,5 @@ (type (;0;) (func)) (func $do_no_demangle_me (;0;) (type 0)) (func $rust (;1;) (type 0)) - (func $#func2 (@name "foo(double)") (;2;) (type 0)) + (func $"foo(double)" (;2;) (type 0)) ) diff --git a/tests/cli/label_shadowing_block.wat b/tests/cli/label_shadowing_block.wat new file mode 100644 index 0000000000..2d2357465e --- /dev/null +++ b/tests/cli/label_shadowing_block.wat @@ -0,0 +1,11 @@ +;; RUN: print % +(module + (func + block $a + br $a + end + block $a + br $a + end + ) +) diff --git a/tests/cli/label_shadowing_block.wat.stdout b/tests/cli/label_shadowing_block.wat.stdout new file mode 100644 index 0000000000..b438c6989b --- /dev/null +++ b/tests/cli/label_shadowing_block.wat.stdout @@ -0,0 +1,11 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + block $a + br $a + end + block $a + br $a + end + ) +) diff --git a/tests/cli/label_shadowing_block_confusion.wat b/tests/cli/label_shadowing_block_confusion.wat new file mode 100644 index 0000000000..f2f566e1f5 --- /dev/null +++ b/tests/cli/label_shadowing_block_confusion.wat @@ -0,0 +1,12 @@ +;; RUN: print % + +(module + (type (;0;) (func)) + (func (;0;) (type 0) + block $a + block $a + br 1 + end + end + ) +) diff --git a/tests/cli/label_shadowing_block_confusion.wat.stdout b/tests/cli/label_shadowing_block_confusion.wat.stdout new file mode 100644 index 0000000000..4b2bbd8589 --- /dev/null +++ b/tests/cli/label_shadowing_block_confusion.wat.stdout @@ -0,0 +1,10 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + block $a + block $a + br 1 + end + end + ) +) diff --git a/tests/cli/label_shadowing_locals.wat b/tests/cli/label_shadowing_locals.wat new file mode 100644 index 0000000000..5823adfbda --- /dev/null +++ b/tests/cli/label_shadowing_locals.wat @@ -0,0 +1,8 @@ +;; RUN: print % +(module + (type (func (param i32) (result i32))) + (func (type 0) (param $l i32) (result i32) + (local $#local1 (@name "l") i32) (local $#local2 (@name "l") i32) + local.get $l + ) +) diff --git a/tests/cli/label_shadowing_locals.wat.stdout b/tests/cli/label_shadowing_locals.wat.stdout new file mode 100644 index 0000000000..db3adf4e2c --- /dev/null +++ b/tests/cli/label_shadowing_locals.wat.stdout @@ -0,0 +1,7 @@ +(module + (type (;0;) (func (param i32) (result i32))) + (func (;0;) (type 0) (param $l i32) (result i32) + (local $"#local1 l" (@name "l") i32) (local $"#local2 l" (@name "l") i32) + local.get $l + ) +) diff --git a/tests/cli/no_panic_non_func_type.wat b/tests/cli/no_panic_non_func_type.wat new file mode 100644 index 0000000000..fcbe1b0d98 --- /dev/null +++ b/tests/cli/no_panic_non_func_type.wat @@ -0,0 +1,6 @@ +;; RUN: print % + +(module + (type (struct)) + (func (type 0)) +) diff --git a/tests/cli/no_panic_non_func_type.wat.stdout b/tests/cli/no_panic_non_func_type.wat.stdout new file mode 100644 index 0000000000..ab571b739b --- /dev/null +++ b/tests/cli/no_panic_non_func_type.wat.stdout @@ -0,0 +1,4 @@ +(module + (type (;0;) (struct)) + (func (;0;) (type 0)) +) diff --git a/tests/cli/print-code-section-overflow.wat b/tests/cli/print-code-section-overflow.wat new file mode 100644 index 0000000000..08859c8ee0 --- /dev/null +++ b/tests/cli/print-code-section-overflow.wat @@ -0,0 +1,7 @@ +;; FAIL: print % + +(module binary + "\00asm" + "\01\00\00\00" + "\0a\10\01" +) diff --git a/tests/cli/print-code-section-overflow.wat.stderr b/tests/cli/print-code-section-overflow.wat.stderr new file mode 100644 index 0000000000..e01193c6c9 --- /dev/null +++ b/tests/cli/print-code-section-overflow.wat.stderr @@ -0,0 +1 @@ +error: unexpected end-of-file (at offset 0xb) diff --git a/tests/cli/print-code-section-overflow.wat.stdout b/tests/cli/print-code-section-overflow.wat.stdout new file mode 100644 index 0000000000..356532d63f --- /dev/null +++ b/tests/cli/print-code-section-overflow.wat.stdout @@ -0,0 +1 @@ +(module \ No newline at end of file diff --git a/tests/cli/print-dont-reserve-the-world.wat b/tests/cli/print-dont-reserve-the-world.wat new file mode 100644 index 0000000000..df36f9855e --- /dev/null +++ b/tests/cli/print-dont-reserve-the-world.wat @@ -0,0 +1,9 @@ +;; FAIL: print % + +(module binary + "\00asm" "\01\00\00\00" ;; module header + + "\03" ;; function section + "\05" ;; section size + "\ff\ff\ff\ff\0f" ;; number of functions (u32::MAX) +) diff --git a/tests/cli/print-dont-reserve-the-world.wat.stderr b/tests/cli/print-dont-reserve-the-world.wat.stderr new file mode 100644 index 0000000000..ac95ab81ab --- /dev/null +++ b/tests/cli/print-dont-reserve-the-world.wat.stderr @@ -0,0 +1 @@ +error: module contains 4294967295 functions which exceeds the limit of 1000000 diff --git a/tests/cli/print-dont-reserve-the-world.wat.stdout b/tests/cli/print-dont-reserve-the-world.wat.stdout new file mode 100644 index 0000000000..356532d63f --- /dev/null +++ b/tests/cli/print-dont-reserve-the-world.wat.stdout @@ -0,0 +1 @@ +(module \ No newline at end of file diff --git a/tests/cli/print-locals-overflow.wat b/tests/cli/print-locals-overflow.wat new file mode 100644 index 0000000000..66c20a3f48 --- /dev/null +++ b/tests/cli/print-locals-overflow.wat @@ -0,0 +1,23 @@ +;; FAIL: print % + +(module binary + "\00asm" "\01\00\00\00" ;; module header + + "\01" ;; type section + "\04" ;; size of section + "\01" ;; one type + "\60\00\00" ;; function, no parameters or results + + "\03" ;; function section + "\02" ;; size of function section + "\01" ;; one function + "\00" ;; type 0 + + "\0a" ;; code section + "\09" ;; size of code section + "\01" ;; 1 function + "\07" ;; size of function + "\01" ;; one local + "\ff\ff\ff\ff\00" ;; lots of this type + "\70" ;; type +) diff --git a/tests/cli/print-locals-overflow.wat.stderr b/tests/cli/print-locals-overflow.wat.stderr new file mode 100644 index 0000000000..42167d7e9d --- /dev/null +++ b/tests/cli/print-locals-overflow.wat.stderr @@ -0,0 +1 @@ +error: function exceeds the maximum number of locals that can be printed diff --git a/tests/cli/print-locals-overflow.wat.stdout b/tests/cli/print-locals-overflow.wat.stdout new file mode 100644 index 0000000000..564f603c03 --- /dev/null +++ b/tests/cli/print-locals-overflow.wat.stdout @@ -0,0 +1,3 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) \ No newline at end of file diff --git a/tests/cli/print-memarg-too-big.wat b/tests/cli/print-memarg-too-big.wat new file mode 100644 index 0000000000..ede83106c1 --- /dev/null +++ b/tests/cli/print-memarg-too-big.wat @@ -0,0 +1,15 @@ +;; FAIL: print % + +(module binary + "\00asm" "\01\00\00\00" ;; module header + + "\0b" ;; data section + "\07" ;; size of section + "\01" ;; number of segments + "\00" ;; flags=active + "\2e" ;; i32.load16_s + "\3f" ;; alignment + "\00" ;; offset + "\0b" ;; end + "\00" ;; data size +) diff --git a/tests/cli/print-memarg-too-big.wat.stderr b/tests/cli/print-memarg-too-big.wat.stderr new file mode 100644 index 0000000000..e53e788230 --- /dev/null +++ b/tests/cli/print-memarg-too-big.wat.stderr @@ -0,0 +1 @@ +error: alignment in memarg too large diff --git a/tests/cli/print-memarg-too-big.wat.stdout b/tests/cli/print-memarg-too-big.wat.stdout new file mode 100644 index 0000000000..ef9290ef1c --- /dev/null +++ b/tests/cli/print-memarg-too-big.wat.stdout @@ -0,0 +1,2 @@ +(module + (data (;0;) (i32.load16_s \ No newline at end of file diff --git a/tests/cli/print-no-panic-bad-data-segment.wat b/tests/cli/print-no-panic-bad-data-segment.wat new file mode 100644 index 0000000000..35f44ad0c5 --- /dev/null +++ b/tests/cli/print-no-panic-bad-data-segment.wat @@ -0,0 +1,5 @@ +;; RUN: print % + +(module + (data (br 4294967295) "") +) diff --git a/tests/cli/print-no-panic-bad-data-segment.wat.stdout b/tests/cli/print-no-panic-bad-data-segment.wat.stdout new file mode 100644 index 0000000000..9ceb5d72af --- /dev/null +++ b/tests/cli/print-no-panic-bad-data-segment.wat.stdout @@ -0,0 +1,3 @@ +(module + (data (;0;) (br 4294967295 (; INVALID ;)) "") +) diff --git a/tests/cli/print-no-panic-dangling-else.wat b/tests/cli/print-no-panic-dangling-else.wat new file mode 100644 index 0000000000..7c6307f1af --- /dev/null +++ b/tests/cli/print-no-panic-dangling-else.wat @@ -0,0 +1,5 @@ +;; RUN: print % + +(module + (func else) +) diff --git a/tests/cli/print-no-panic-dangling-else.wat.stdout b/tests/cli/print-no-panic-dangling-else.wat.stdout new file mode 100644 index 0000000000..e43ca79fee --- /dev/null +++ b/tests/cli/print-no-panic-dangling-else.wat.stdout @@ -0,0 +1,6 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + else + ) +) diff --git a/tests/cli/print-no-panic-double-end.wat b/tests/cli/print-no-panic-double-end.wat new file mode 100644 index 0000000000..4d7b65d450 --- /dev/null +++ b/tests/cli/print-no-panic-double-end.wat @@ -0,0 +1,5 @@ +;; RUN: print % + +(module + (func end) +) diff --git a/tests/cli/print-no-panic-double-end.wat.stdout b/tests/cli/print-no-panic-double-end.wat.stdout new file mode 100644 index 0000000000..828cd76866 --- /dev/null +++ b/tests/cli/print-no-panic-double-end.wat.stdout @@ -0,0 +1,4 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0)end) +) diff --git a/tests/cli/shared_global.wat b/tests/cli/shared_global.wat new file mode 100644 index 0000000000..fa8544f0e7 --- /dev/null +++ b/tests/cli/shared_global.wat @@ -0,0 +1,4 @@ +;; RUN: print % +(module + (global (shared f32)) +) diff --git a/tests/cli/shared_global.wat.stdout b/tests/cli/shared_global.wat.stdout new file mode 100644 index 0000000000..9a52b283a9 --- /dev/null +++ b/tests/cli/shared_global.wat.stdout @@ -0,0 +1,3 @@ +(module + (global (;0;) (shared f32) ) +) diff --git a/tests/snapshots/local/names.wast/0.print b/tests/snapshots/local/names.wast/0.print index 32691869c8..0229697422 100644 --- a/tests/snapshots/local/names.wast/0.print +++ b/tests/snapshots/local/names.wast/0.print @@ -1 +1 @@ -(module $#module0<> (@name "")) +(module $"#module0 " (@name "")) diff --git a/tests/snapshots/local/names.wast/2.print b/tests/snapshots/local/names.wast/2.print index 5088302380..ec6fb82d7e 100644 --- a/tests/snapshots/local/names.wast/2.print +++ b/tests/snapshots/local/names.wast/2.print @@ -1,6 +1,6 @@ (module (type (;0;) (func)) (func $foo (;0;) (type 0)) - (func $#func1 (@name "foo") (;1;) (type 0)) + (func $"#func1 foo" (@name "foo") (;1;) (type 0)) (func $foo_1 (;2;) (type 0)) ) diff --git a/tests/snapshots/local/names.wast/3.print b/tests/snapshots/local/names.wast/3.print index 4153850dde..8e21ace805 100644 --- a/tests/snapshots/local/names.wast/3.print +++ b/tests/snapshots/local/names.wast/3.print @@ -1,6 +1,6 @@ (module (type (;0;) (func)) (func (;0;) (type 0) - (local $foo i32) (local $#local1 (@name "foo") i32) (local $foo_1 i32) + (local $foo i32) (local $"#local1 foo" (@name "foo") i32) (local $foo_1 i32) ) ) diff --git a/tests/snapshots/local/names.wast/4.print b/tests/snapshots/local/names.wast/4.print index 6e3b5ed285..5e70567e8b 100644 --- a/tests/snapshots/local/names.wast/4.print +++ b/tests/snapshots/local/names.wast/4.print @@ -1,4 +1,4 @@ (module (type (;0;) (func)) - (func $#func0<> (@name "") (;0;) (type 0)) + (func $"#func0 " (@name "") (;0;) (type 0)) ) diff --git a/tests/snapshots/local/names.wast/5.print b/tests/snapshots/local/names.wast/5.print index 8d22ad1eef..b6d74372af 100644 --- a/tests/snapshots/local/names.wast/5.print +++ b/tests/snapshots/local/names.wast/5.print @@ -1,6 +1,6 @@ (module (type (;0;) (func)) (func (;0;) (type 0) - (local $#local0<> (@name "") i32) + (local $"#local0 " (@name "") i32) ) ) diff --git a/tests/snapshots/testsuite/custom.wast/0.print b/tests/snapshots/testsuite/custom.wast/0.print index 00e35eaea7..d0cea5e656 100644 --- a/tests/snapshots/testsuite/custom.wast/0.print +++ b/tests/snapshots/testsuite/custom.wast/0.print @@ -4,8 +4,8 @@ (@custom "a custom section" (before first) "") (@custom "" (before first) "this is payload") (@custom "" (before first) "") - (@custom "\00\00custom sectio\00" (before first) "this is the payload") - (@custom "\ef\bb\bfa custom sect" (before first) "this is the payload") - (@custom "a custom sect\e2\8c\a3" (before first) "this is the payload") + (@custom "\u{0}\u{0}custom sectio\u{0}" (before first) "this is the payload") + (@custom "\u{feff}a custom sect" (before first) "this is the payload") + (@custom "a custom sect\u{2323}" (before first) "this is the payload") (@custom "module within a module" (before first) "\00asm\01\00\00\00") ) diff --git a/tests/snapshots/testsuite/names.wast/4.print b/tests/snapshots/testsuite/names.wast/4.print index bb6d6702fb..a329008281 100644 --- a/tests/snapshots/testsuite/names.wast/4.print +++ b/tests/snapshots/testsuite/names.wast/4.print @@ -1443,7 +1443,7 @@ (export "_" (func 3)) (export "$" (func 4)) (export "@" (func 5)) - (export "~!@#$%^&*()_+`-={}|[]\5c:\22;'<>?,./ " (func 6)) + (export "~!@#$%^&*()_+`-={}|[]\u{5c}:\u{22};'<>?,./ " (func 6)) (export "NaN" (func 7)) (export "Infinity" (func 8)) (export "if" (func 9)) @@ -1452,468 +1452,468 @@ (export "__malloc" (func 12)) (export "a" (func 13)) (export "A" (func 14)) - (export "\ef\bb\bf" (func 15)) - (export "\c3\85" (func 16)) - (export "A\cc\8a" (func 17)) - (export "\e2\84\ab" (func 18)) - (export "\ef\ac\83" (func 19)) - (export "f\ef\ac\81" (func 20)) + (export "\u{feff}" (func 15)) + (export "\u{c5}" (func 16)) + (export "A\u{30a}" (func 17)) + (export "\u{212b}" (func 18)) + (export "\u{fb03}" (func 19)) + (export "f\u{fb01}" (func 20)) (export "ffi" (func 21)) - (export "\00\01\02\03\04\05\06\07\08\09\0a\0b\0c\0d\0e\0f" (func 22)) - (export "\10\11\12\13\14\15\16\17\18\19\1a\1b\1c\1d\1e\1f" (func 23)) - (export " \7f" (func 24)) - (export "\c2\80\c2\81\c2\82\c2\83\c2\84\c2\85\c2\86\c2\87\c2\88\c2\89\c2\8a\c2\8b\c2\8c\c2\8d\c2\8e\c2\8f" (func 25)) - (export "\c2\90\c2\91\c2\92\c2\93\c2\94\c2\95\c2\96\c2\97\c2\98\c2\99\c2\9a\c2\9b\c2\9c\c2\9d\c2\9e\c2\9f" (func 26)) - (export "\ef\bf\b0\ef\bf\b1\ef\bf\b2\ef\bf\b3\ef\bf\b4\ef\bf\b5\ef\bf\b6\ef\bf\b7" (func 27)) - (export "\ef\bf\b8\ef\bf\b9\ef\bf\ba\ef\bf\bb\ef\bf\bc\ef\bf\bd\ef\bf\be\ef\bf\bf" (func 28)) - (export "\e2\90\80\e2\90\81\e2\90\82\e2\90\83\e2\90\84\e2\90\85\e2\90\86\e2\90\87\e2\90\88\e2\90\89\e2\90\8a\e2\90\8b\e2\90\8c\e2\90\8d\e2\90\8e\e2\90\8f" (func 29)) - (export "\e2\90\90\e2\90\91\e2\90\92\e2\90\93\e2\90\94\e2\90\95\e2\90\96\e2\90\97\e2\90\98\e2\90\99\e2\90\9a\e2\90\9b\e2\90\9c\e2\90\9d\e2\90\9e\e2\90\9f" (func 30)) - (export "\e2\90\a0\e2\90\a1" (func 31)) - (export "\ef\bf\b0\ef\bf\b1\ef\bf\b2\ef\bf\b3\ef\bf\b4\ef\bf\b5\ef\bf\b6\ef\bf\b7\ef\bf\b8\ef\bf\b9\ef\bf\ba\ef\bf\bb\ef\bf\bc\ef\bf\bd" (func 32)) - (export "\e2\80\8d" (func 33)) - (export "\e2\80\8c" (func 34)) - (export "\cd\8f" (func 35)) - (export "\e2\81\a0" (func 36)) - (export "\e2\b5\bf" (func 37)) - (export "\f0\91\81\bf" (func 38)) - (export "\e1\a0\8e" (func 39)) - (export "\ef\bf\af\e2\80\8b\c2\a0\c2\ad\e2\81\a0\e1\9a\80\e2\80\ae\e2\80\ad" (func 40)) - (export "\e2\80\8e\e2\80\8f\e2\80\91\e2\80\a8\e2\80\a9\e2\80\aa\e2\80\ab\e2\80\ac\e2\80\af\e2\81\a6\e2\81\a7\e2\81\a8\e2\81\a9" (func 41)) - (export "\e2\81\aa\e2\81\ab\e2\81\ac\e2\81\ad\e2\81\ae\e2\81\af" (func 42)) - (export "\e2\81\a1\e2\81\a2\e2\81\a3\e2\81\a4" (func 43)) - (export "\f0\90\80\80\f3\9f\bf\bf\f4\8f\bf\bf" (func 44)) - (export "Z\cc\8f\cd\86\cc\86\cd\9b\cd\8c\cc\b4\cd\98\cd\9e\cd\87\cc\ab\cc\a5\cc\aa\cd\93\cd\88\cd\94\cd\8e\cc\97\cc\9e\cc\ba\cc\af\cc\b1\cc\9e\cc\99\cc\b1\cc\9c\cc\96\cc\a0a\cd\97\cd\a8\cc\8e\cc\84\cc\86\cd\97\cc\bf\cd\a1\cd\9f\cd\80\cc\b6\cd\81\cc\a5\cc\b0\cc\b3\cc\ad\cd\99\cc\b2\cc\b1\cc\b9\cc\9d\cd\8e\cc\bcl\cd\84\cd\8a\cc\9a\cd\97\cd\a6\cd\84\cd\ab\cc\87\cd\81\cc\b6\cc\b7\cd\89\cc\a9\cc\b9\cc\ab\cc\9d\cd\96\cd\85\cc\99\cc\b2\cc\bc\cd\87\cd\9a\cd\8d\cc\ae\cd\8e\cc\a5\cd\85\cc\9eg\cd\83\cc\90\cc\85\cd\ae\cc\94\cc\90\cc\8e\cc\82\cc\8f\cc\be\cd\8a\cc\8d\cd\8b\cd\8a\cd\a7\cc\81\cc\86\cd\a6\cd\9e\cc\b6\cd\95\cd\94\cd\9a\cc\a9o\cd\8b\cc\94\cd\90\cd\aa\cd\a9\cc\a1\cd\8f\cc\a2\cc\a7\cd\81\cc\ab\cc\99\cc\a4\cc\ae\cd\96\cd\99\cd\93\cc\ba\cc\9c\cc\a9\cc\bc\cc\98\cc\a0" (func 45)) - (export "\e1\85\9f\e1\85\a0\e3\85\a4\ef\be\a0" (func 46)) - (export "\ef\b8\80" (func 47)) - (export "\ef\b8\84" (func 48)) - (export "\f3\a0\84\80" (func 49)) - (export "\f3\a0\87\af" (func 50)) - (export "\cc\88" (func 51)) - (export "\0a" (func 52)) - (export "\e2\90\a4" (func 53)) - (export "\e2\80\a8" (func 54)) - (export "\0d" (func 55)) - (export "\0d\0a" (func 56)) - (export "\0a\0d" (func 57)) - (export "\1e" (func 58)) - (export "\0b" (func 59)) - (export "\0c" (func 60)) - (export "\c2\85" (func 61)) - (export "\e2\80\a9" (func 62)) - (export "\e2\80\a6" (func 63)) - (export "\e2\8f\8e" (func 64)) - (export "\c2\8b" (func 65)) - (export "\c2\8c" (func 66)) - (export "\c2\8d" (func 67)) - (export "\e2\86\b5" (func 68)) - (export "\e2\86\a9" (func 69)) - (export "\e2\8c\a4" (func 70)) - (export "\e2\a4\b6" (func 71)) - (export "\e2\86\b2" (func 72)) - (export "\e2\ae\a8" (func 73)) - (export "\e2\ae\b0" (func 74)) - (export "\ef\bf\bd" (func 75)) - (export "\ef\b7\90" (func 76)) - (export "\ef\b7\91" (func 77)) - (export "\ef\b7\92" (func 78)) - (export "\ef\b7\93" (func 79)) - (export "\ef\b7\94" (func 80)) - (export "\ef\b7\95" (func 81)) - (export "\ef\b7\96" (func 82)) - (export "\ef\b7\97" (func 83)) - (export "\ef\b7\98" (func 84)) - (export "\ef\b7\99" (func 85)) - (export "\ef\b7\9a" (func 86)) - (export "\ef\b7\9b" (func 87)) - (export "\ef\b7\9c" (func 88)) - (export "\ef\b7\9d" (func 89)) - (export "\ef\b7\9e" (func 90)) - (export "\ef\b7\9f" (func 91)) - (export "\ef\b7\a0" (func 92)) - (export "\ef\b7\a1" (func 93)) - (export "\ef\b7\a2" (func 94)) - (export "\ef\b7\a3" (func 95)) - (export "\ef\b7\a4" (func 96)) - (export "\ef\b7\a5" (func 97)) - (export "\ef\b7\a6" (func 98)) - (export "\ef\b7\a7" (func 99)) - (export "\ef\b7\a8" (func 100)) - (export "\ef\b7\a9" (func 101)) - (export "\ef\b7\aa" (func 102)) - (export "\ef\b7\ab" (func 103)) - (export "\ef\b7\ac" (func 104)) - (export "\ef\b7\ad" (func 105)) - (export "\ef\b7\ae" (func 106)) - (export "\ef\b7\af" (func 107)) - (export "\ef\bf\be" (func 108)) - (export "\ef\bf\bf" (func 109)) - (export "\f0\9f\bf\be" (func 110)) - (export "\f0\9f\bf\bf" (func 111)) - (export "\f0\af\bf\be" (func 112)) - (export "\f0\af\bf\bf" (func 113)) - (export "\f0\bf\bf\be" (func 114)) - (export "\f0\bf\bf\bf" (func 115)) - (export "\f1\8f\bf\be" (func 116)) - (export "\f1\8f\bf\bf" (func 117)) - (export "\f1\9f\bf\be" (func 118)) - (export "\f1\9f\bf\bf" (func 119)) - (export "\f1\af\bf\be" (func 120)) - (export "\f1\af\bf\bf" (func 121)) - (export "\f1\bf\bf\be" (func 122)) - (export "\f1\bf\bf\bf" (func 123)) - (export "\f2\8f\bf\be" (func 124)) - (export "\f2\8f\bf\bf" (func 125)) - (export "\f2\9f\bf\be" (func 126)) - (export "\f2\9f\bf\bf" (func 127)) - (export "\f2\af\bf\be" (func 128)) - (export "\f2\af\bf\bf" (func 129)) - (export "\f2\bf\bf\be" (func 130)) - (export "\f2\bf\bf\bf" (func 131)) - (export "\f3\8f\bf\be" (func 132)) - (export "\f3\8f\bf\bf" (func 133)) - (export "\f3\9f\bf\be" (func 134)) - (export "\f3\9f\bf\bf" (func 135)) - (export "\f3\af\bf\be" (func 136)) - (export "\f3\af\bf\bf" (func 137)) - (export "\f3\bf\bf\be" (func 138)) - (export "\f3\bf\bf\bf" (func 139)) - (export "\f4\8f\bf\be" (func 140)) - (export "\f4\8f\bf\bf" (func 141)) - (export "\cc\88\e2\80\bd\cc\88\cc\89" (func 142)) + (export "\u{0}\u{1}\u{2}\u{3}\u{4}\u{5}\u{6}\u{7}\u{8}\u{9}\u{a}\u{b}\u{c}\u{d}\u{e}\u{f}" (func 22)) + (export "\u{10}\u{11}\u{12}\u{13}\u{14}\u{15}\u{16}\u{17}\u{18}\u{19}\u{1a}\u{1b}\u{1c}\u{1d}\u{1e}\u{1f}" (func 23)) + (export " \u{7f}" (func 24)) + (export "\u{80}\u{81}\u{82}\u{83}\u{84}\u{85}\u{86}\u{87}\u{88}\u{89}\u{8a}\u{8b}\u{8c}\u{8d}\u{8e}\u{8f}" (func 25)) + (export "\u{90}\u{91}\u{92}\u{93}\u{94}\u{95}\u{96}\u{97}\u{98}\u{99}\u{9a}\u{9b}\u{9c}\u{9d}\u{9e}\u{9f}" (func 26)) + (export "\u{fff0}\u{fff1}\u{fff2}\u{fff3}\u{fff4}\u{fff5}\u{fff6}\u{fff7}" (func 27)) + (export "\u{fff8}\u{fff9}\u{fffa}\u{fffb}\u{fffc}\u{fffd}\u{fffe}\u{ffff}" (func 28)) + (export "\u{2400}\u{2401}\u{2402}\u{2403}\u{2404}\u{2405}\u{2406}\u{2407}\u{2408}\u{2409}\u{240a}\u{240b}\u{240c}\u{240d}\u{240e}\u{240f}" (func 29)) + (export "\u{2410}\u{2411}\u{2412}\u{2413}\u{2414}\u{2415}\u{2416}\u{2417}\u{2418}\u{2419}\u{241a}\u{241b}\u{241c}\u{241d}\u{241e}\u{241f}" (func 30)) + (export "\u{2420}\u{2421}" (func 31)) + (export "\u{fff0}\u{fff1}\u{fff2}\u{fff3}\u{fff4}\u{fff5}\u{fff6}\u{fff7}\u{fff8}\u{fff9}\u{fffa}\u{fffb}\u{fffc}\u{fffd}" (func 32)) + (export "\u{200d}" (func 33)) + (export "\u{200c}" (func 34)) + (export "\u{34f}" (func 35)) + (export "\u{2060}" (func 36)) + (export "\u{2d7f}" (func 37)) + (export "\u{1107f}" (func 38)) + (export "\u{180e}" (func 39)) + (export "\u{ffef}\u{200b}\u{a0}\u{ad}\u{2060}\u{1680}\u{202e}\u{202d}" (func 40)) + (export "\u{200e}\u{200f}\u{2011}\u{2028}\u{2029}\u{202a}\u{202b}\u{202c}\u{202f}\u{2066}\u{2067}\u{2068}\u{2069}" (func 41)) + (export "\u{206a}\u{206b}\u{206c}\u{206d}\u{206e}\u{206f}" (func 42)) + (export "\u{2061}\u{2062}\u{2063}\u{2064}" (func 43)) + (export "\u{10000}\u{dffff}\u{10ffff}" (func 44)) + (export "Z\u{30f}\u{346}\u{306}\u{35b}\u{34c}\u{334}\u{358}\u{35e}\u{347}\u{32b}\u{325}\u{32a}\u{353}\u{348}\u{354}\u{34e}\u{317}\u{31e}\u{33a}\u{32f}\u{331}\u{31e}\u{319}\u{331}\u{31c}\u{316}\u{320}a\u{357}\u{368}\u{30e}\u{304}\u{306}\u{357}\u{33f}\u{361}\u{35f}\u{340}\u{336}\u{341}\u{325}\u{330}\u{333}\u{32d}\u{359}\u{332}\u{331}\u{339}\u{31d}\u{34e}\u{33c}l\u{344}\u{34a}\u{31a}\u{357}\u{366}\u{344}\u{36b}\u{307}\u{341}\u{336}\u{337}\u{349}\u{329}\u{339}\u{32b}\u{31d}\u{356}\u{345}\u{319}\u{332}\u{33c}\u{347}\u{35a}\u{34d}\u{32e}\u{34e}\u{325}\u{345}\u{31e}g\u{343}\u{310}\u{305}\u{36e}\u{314}\u{310}\u{30e}\u{302}\u{30f}\u{33e}\u{34a}\u{30d}\u{34b}\u{34a}\u{367}\u{301}\u{306}\u{366}\u{35e}\u{336}\u{355}\u{354}\u{35a}\u{329}o\u{34b}\u{314}\u{350}\u{36a}\u{369}\u{321}\u{34f}\u{322}\u{327}\u{341}\u{32b}\u{319}\u{324}\u{32e}\u{356}\u{359}\u{353}\u{33a}\u{31c}\u{329}\u{33c}\u{318}\u{320}" (func 45)) + (export "\u{115f}\u{1160}\u{3164}\u{ffa0}" (func 46)) + (export "\u{fe00}" (func 47)) + (export "\u{fe04}" (func 48)) + (export "\u{e0100}" (func 49)) + (export "\u{e01ef}" (func 50)) + (export "\u{308}" (func 51)) + (export "\u{a}" (func 52)) + (export "\u{2424}" (func 53)) + (export "\u{2028}" (func 54)) + (export "\u{d}" (func 55)) + (export "\u{d}\u{a}" (func 56)) + (export "\u{a}\u{d}" (func 57)) + (export "\u{1e}" (func 58)) + (export "\u{b}" (func 59)) + (export "\u{c}" (func 60)) + (export "\u{85}" (func 61)) + (export "\u{2029}" (func 62)) + (export "\u{2026}" (func 63)) + (export "\u{23ce}" (func 64)) + (export "\u{8b}" (func 65)) + (export "\u{8c}" (func 66)) + (export "\u{8d}" (func 67)) + (export "\u{21b5}" (func 68)) + (export "\u{21a9}" (func 69)) + (export "\u{2324}" (func 70)) + (export "\u{2936}" (func 71)) + (export "\u{21b2}" (func 72)) + (export "\u{2ba8}" (func 73)) + (export "\u{2bb0}" (func 74)) + (export "\u{fffd}" (func 75)) + (export "\u{fdd0}" (func 76)) + (export "\u{fdd1}" (func 77)) + (export "\u{fdd2}" (func 78)) + (export "\u{fdd3}" (func 79)) + (export "\u{fdd4}" (func 80)) + (export "\u{fdd5}" (func 81)) + (export "\u{fdd6}" (func 82)) + (export "\u{fdd7}" (func 83)) + (export "\u{fdd8}" (func 84)) + (export "\u{fdd9}" (func 85)) + (export "\u{fdda}" (func 86)) + (export "\u{fddb}" (func 87)) + (export "\u{fddc}" (func 88)) + (export "\u{fddd}" (func 89)) + (export "\u{fdde}" (func 90)) + (export "\u{fddf}" (func 91)) + (export "\u{fde0}" (func 92)) + (export "\u{fde1}" (func 93)) + (export "\u{fde2}" (func 94)) + (export "\u{fde3}" (func 95)) + (export "\u{fde4}" (func 96)) + (export "\u{fde5}" (func 97)) + (export "\u{fde6}" (func 98)) + (export "\u{fde7}" (func 99)) + (export "\u{fde8}" (func 100)) + (export "\u{fde9}" (func 101)) + (export "\u{fdea}" (func 102)) + (export "\u{fdeb}" (func 103)) + (export "\u{fdec}" (func 104)) + (export "\u{fded}" (func 105)) + (export "\u{fdee}" (func 106)) + (export "\u{fdef}" (func 107)) + (export "\u{fffe}" (func 108)) + (export "\u{ffff}" (func 109)) + (export "\u{1fffe}" (func 110)) + (export "\u{1ffff}" (func 111)) + (export "\u{2fffe}" (func 112)) + (export "\u{2ffff}" (func 113)) + (export "\u{3fffe}" (func 114)) + (export "\u{3ffff}" (func 115)) + (export "\u{4fffe}" (func 116)) + (export "\u{4ffff}" (func 117)) + (export "\u{5fffe}" (func 118)) + (export "\u{5ffff}" (func 119)) + (export "\u{6fffe}" (func 120)) + (export "\u{6ffff}" (func 121)) + (export "\u{7fffe}" (func 122)) + (export "\u{7ffff}" (func 123)) + (export "\u{8fffe}" (func 124)) + (export "\u{8ffff}" (func 125)) + (export "\u{9fffe}" (func 126)) + (export "\u{9ffff}" (func 127)) + (export "\u{afffe}" (func 128)) + (export "\u{affff}" (func 129)) + (export "\u{bfffe}" (func 130)) + (export "\u{bffff}" (func 131)) + (export "\u{cfffe}" (func 132)) + (export "\u{cffff}" (func 133)) + (export "\u{dfffe}" (func 134)) + (export "\u{dffff}" (func 135)) + (export "\u{efffe}" (func 136)) + (export "\u{effff}" (func 137)) + (export "\u{ffffe}" (func 138)) + (export "\u{fffff}" (func 139)) + (export "\u{10fffe}" (func 140)) + (export "\u{10ffff}" (func 141)) + (export "\u{308}\u{203d}\u{308}\u{309}" (func 142)) (export "abc" (func 143)) - (export "\e2\80\adabc" (func 144)) - (export "\e2\80\aecba" (func 145)) - (export "\e2\80\adabc\e2\80\ae" (func 146)) - (export "\e2\80\aecba\e2\80\ad" (func 147)) - (export "\f0\9d\91\a8" (func 148)) - (export "\f0\9d\90\b4" (func 149)) - (export "\f0\9d\98\88" (func 150)) - (export "\f0\9d\98\bc" (func 151)) - (export "\f0\9d\90\80" (func 152)) - (export "\f0\9d\93\90" (func 153)) - (export "\f0\9d\95\ac" (func 154)) - (export "\f0\9d\97\94" (func 155)) - (export "\f0\9d\92\9c" (func 156)) - (export "\f0\9d\94\84" (func 157)) - (export "\f0\9d\94\b8" (func 158)) - (export "\f0\9d\96\a0" (func 159)) - (export "\f0\9d\99\b0" (func 160)) - (export "\e1\b4\80" (func 161)) - (export "\e1\b4\ac" (func 162)) - (export "\e2\92\b6" (func 163)) - (export "\ef\bc\a1" (func 164)) - (export "\f0\9f\84\90" (func 165)) - (export "\f0\9f\84\b0" (func 166)) - (export "\f3\a0\81\81" (func 167)) + (export "\u{202d}abc" (func 144)) + (export "\u{202e}cba" (func 145)) + (export "\u{202d}abc\u{202e}" (func 146)) + (export "\u{202e}cba\u{202d}" (func 147)) + (export "\u{1d468}" (func 148)) + (export "\u{1d434}" (func 149)) + (export "\u{1d608}" (func 150)) + (export "\u{1d63c}" (func 151)) + (export "\u{1d400}" (func 152)) + (export "\u{1d4d0}" (func 153)) + (export "\u{1d56c}" (func 154)) + (export "\u{1d5d4}" (func 155)) + (export "\u{1d49c}" (func 156)) + (export "\u{1d504}" (func 157)) + (export "\u{1d538}" (func 158)) + (export "\u{1d5a0}" (func 159)) + (export "\u{1d670}" (func 160)) + (export "\u{1d00}" (func 161)) + (export "\u{1d2c}" (func 162)) + (export "\u{24b6}" (func 163)) + (export "\u{ff21}" (func 164)) + (export "\u{1f110}" (func 165)) + (export "\u{1f130}" (func 166)) + (export "\u{e0041}" (func 167)) (export "U+0041" (func 168)) - (export "A\e2\80\8b" (func 169)) - (export "\d0\90" (func 170)) - (export "\ea\99\96" (func 171)) - (export "\e2\b7\bc" (func 172)) - (export "\e2\b7\b6" (func 173)) - (export "\e2\b1\af" (func 174)) - (export "\f0\9f\85\90" (func 175)) - (export "\f0\9f\85\b0" (func 176)) - (export "\e2\b0\ad" (func 177)) - (export "\f0\90\90\82" (func 178)) - (export "\f0\90\90\88" (func 179)) - (export "\f0\90\92\b0" (func 180)) - (export "\c3\80" (func 181)) - (export "\c3\81" (func 182)) - (export "\c3\82" (func 183)) - (export "\c3\83" (func 184)) - (export "\c3\84" (func 185)) - (export "\c4\80" (func 186)) - (export "\c4\82" (func 187)) - (export "\c4\84" (func 188)) - (export "\c7\8d" (func 189)) - (export "\c7\9e" (func 190)) - (export "\c7\a0" (func 191)) - (export "\c7\ba" (func 192)) - (export "\c8\80" (func 193)) - (export "\c8\82" (func 194)) - (export "\c8\a6" (func 195)) - (export "\c8\ba" (func 196)) - (export "\d3\90" (func 197)) - (export "\d3\92" (func 198)) - (export "\df\8a" (func 199)) - (export "\e0\a0\a1" (func 200)) - (export "\e0\a0\a2" (func 201)) - (export "\e0\a0\a3" (func 202)) - (export "\e0\a0\a4" (func 203)) - (export "\e0\a0\a5" (func 204)) - (export "\e0\a4\84" (func 205)) - (export "\e0\a4\85" (func 206)) - (export "\e0\a5\b2" (func 207)) - (export "\e0\a6\85" (func 208)) - (export "\e0\a8\85" (func 209)) - (export "\e0\aa\85" (func 210)) - (export "\e0\ac\85" (func 211)) - (export "\e0\ae\85" (func 212)) - (export "\e0\b0\85" (func 213)) - (export "\e0\b2\85" (func 214)) - (export "\e0\b4\85" (func 215)) - (export "\e0\b8\b0" (func 216)) - (export "\e0\ba\b0" (func 217)) - (export "\e0\bc\81" (func 218)) - (export "\e0\bd\a8" (func 219)) - (export "\e0\be\b8" (func 220)) - (export "\e1\80\a1" (func 221)) - (export "\e1\80\a2" (func 222)) - (export "\e1\82\9c" (func 223)) - (export "\e1\85\a1" (func 224)) - (export "\e1\8a\a0" (func 225)) - (export "\e1\8b\90" (func 226)) - (export "\e1\8e\a0" (func 227)) - (export "\e1\90\8a" (func 228)) - (export "\e1\96\b3" (func 229)) - (export "\e1\9a\a8" (func 230)) - (export "\e1\9a\aa" (func 231)) - (export "\e1\9b\86" (func 232)) - (export "\e1\9c\80" (func 233)) - (export "\e1\9c\a0" (func 234)) - (export "\e1\9d\80" (func 235)) - (export "\e1\9d\a0" (func 236)) - (export "\e1\a0\a0" (func 237)) - (export "\e1\a2\87" (func 238)) - (export "\e1\a4\a0" (func 239)) - (export "\e1\a5\a3" (func 240)) - (export "\e1\a8\95" (func 241)) - (export "\e1\a9\8b" (func 242)) - (export "\e1\a9\a1" (func 243)) - (export "\e1\ae\83" (func 244)) - (export "\e1\af\80" (func 245)) - (export "\e1\af\81" (func 246)) - (export "\e1\b0\a3" (func 247)) - (export "\e1\b8\80" (func 248)) - (export "\e1\ba\a0" (func 249)) - (export "\e1\ba\a2" (func 250)) - (export "\e1\ba\a4" (func 251)) - (export "\e1\ba\a6" (func 252)) - (export "\e1\ba\a8" (func 253)) - (export "\e1\ba\aa" (func 254)) - (export "\e1\ba\ac" (func 255)) - (export "\e1\ba\ae" (func 256)) - (export "\e1\ba\b0" (func 257)) - (export "\e1\ba\b2" (func 258)) - (export "\e1\ba\b4" (func 259)) - (export "\e1\ba\b6" (func 260)) - (export "\e3\81\82" (func 261)) - (export "\e3\82\a2" (func 262)) - (export "\e3\84\9a" (func 263)) - (export "\e3\85\8f" (func 264)) - (export "\e3\88\8e" (func 265)) - (export "\e3\88\8f" (func 266)) - (export "\e3\88\90" (func 267)) - (export "\e3\88\91" (func 268)) - (export "\e3\88\92" (func 269)) - (export "\e3\88\93" (func 270)) - (export "\e3\88\94" (func 271)) - (export "\e3\88\95" (func 272)) - (export "\e3\88\96" (func 273)) - (export "\e3\88\97" (func 274)) - (export "\e3\88\98" (func 275)) - (export "\e3\88\99" (func 276)) - (export "\e3\88\9a" (func 277)) - (export "\e3\88\9b" (func 278)) - (export "\e3\89\ae" (func 279)) - (export "\e3\89\af" (func 280)) - (export "\e3\89\b0" (func 281)) - (export "\e3\89\b1" (func 282)) - (export "\e3\89\b2" (func 283)) - (export "\e3\89\b3" (func 284)) - (export "\e3\89\b4" (func 285)) - (export "\e3\89\b5" (func 286)) - (export "\e3\89\b6" (func 287)) - (export "\e3\89\b7" (func 288)) - (export "\e3\89\b8" (func 289)) - (export "\e3\89\b9" (func 290)) - (export "\e3\89\ba" (func 291)) - (export "\e3\89\bb" (func 292)) - (export "\e3\8b\90" (func 293)) - (export "\ea\80\8a" (func 294)) - (export "\ea\93\ae" (func 295)) - (export "\ea\95\89" (func 296)) - (export "\ea\9a\a0" (func 297)) - (export "\ea\a0\80" (func 298)) - (export "\ea\a0\a3" (func 299)) - (export "\ea\a1\9d" (func 300)) - (export "\ea\a2\82" (func 301)) - (export "\ea\a3\aa" (func 302)) - (export "\ea\a4\a2" (func 303)) - (export "\ea\a5\86" (func 304)) - (export "\ea\a6\84" (func 305)) - (export "\ea\a8\80" (func 306)) - (export "\ef\bd\b1" (func 307)) - (export "\ef\bf\82" (func 308)) - (export "\f0\90\80\80" (func 309)) - (export "\f0\90\8a\80" (func 310)) - (export "\f0\90\8a\a0" (func 311)) - (export "\f0\90\8c\80" (func 312)) - (export "\f0\90\8e\a0" (func 313)) - (export "\f0\90\92\96" (func 314)) - (export "\f0\90\94\80" (func 315)) - (export "\f0\90\9d\80" (func 316)) - (export "\f0\90\a0\80" (func 317)) - (export "\f0\90\a4\a0" (func 318)) - (export "\f0\90\a6\80" (func 319)) - (export "\f0\90\a6\a0" (func 320)) - (export "\f0\90\a8\80" (func 321)) - (export "\f0\90\ac\80" (func 322)) - (export "\f0\90\b0\80" (func 323)) - (export "\f0\90\b0\81" (func 324)) - (export "\f0\90\b2\80" (func 325)) - (export "\f0\91\80\85" (func 326)) - (export "\f0\91\82\83" (func 327)) - (export "\f0\91\84\a7" (func 328)) - (export "\f0\91\85\90" (func 329)) - (export "\f0\91\86\83" (func 330)) - (export "\f0\91\88\80" (func 331)) - (export "\f0\91\8a\80" (func 332)) - (export "\f0\91\8a\b0" (func 333)) - (export "\f0\91\8c\85" (func 334)) - (export "\f0\91\8d\b0" (func 335)) - (export "\f0\91\90\80" (func 336)) - (export "\f0\91\92\81" (func 337)) - (export "\f0\91\96\80" (func 338)) - (export "\f0\91\98\80" (func 339)) - (export "\f0\91\9a\80" (func 340)) - (export "\f0\91\9c\92" (func 341)) - (export "\f0\91\9c\a0" (func 342)) - (export "\f0\91\a2\a1" (func 343)) - (export "\f0\91\ab\95" (func 344)) - (export "\f0\91\b0\80" (func 345)) - (export "\f0\91\b2\8f" (func 346)) - (export "\f0\91\b2\af" (func 347)) - (export "\f0\92\80\80" (func 348)) - (export "\f0\96\a7\95" (func 349)) - (export "\f0\96\a9\86" (func 350)) - (export "\f0\96\ab\a7" (func 351)) - (export "\f0\96\bd\94" (func 352)) - (export "\f0\9b\b1\81" (func 353)) - (export "\f0\9b\b1\a4" (func 354)) - (export "\f0\9e\a0\a3" (func 355)) - (export "\f0\9f\87\a6" (func 356)) - (export "\e2\b1\ad" (func 357)) - (export "\ce\9b" (func 358)) - (export "\e2\b1\b0" (func 359)) - (export "\c2\aa" (func 360)) - (export "\e2\88\80" (func 361)) - (export "\e2\82\b3" (func 362)) - (export "\f0\90\a4\80" (func 363)) - (export "\e2\b2\80" (func 364)) - (export "\f0\90\8c\b0" (func 365)) - (export "\ce\86" (func 366)) - (export "\ce\91" (func 367)) - (export "\e1\bc\88" (func 368)) - (export "\e1\bc\89" (func 369)) - (export "\e1\bc\8a" (func 370)) - (export "\e1\bc\8b" (func 371)) - (export "\e1\bc\8c" (func 372)) - (export "\e1\bc\8d" (func 373)) - (export "\e1\bc\8e" (func 374)) - (export "\e1\bc\8f" (func 375)) - (export "\e1\be\88" (func 376)) - (export "\e1\be\89" (func 377)) - (export "\e1\be\8a" (func 378)) - (export "\e1\be\8b" (func 379)) - (export "\e1\be\8c" (func 380)) - (export "\e1\be\8d" (func 381)) - (export "\e1\be\8e" (func 382)) - (export "\e1\be\8f" (func 383)) - (export "\e1\be\b8" (func 384)) - (export "\e1\be\b9" (func 385)) - (export "\e1\be\ba" (func 386)) - (export "\e1\be\bb" (func 387)) - (export "\e1\be\bc" (func 388)) - (export "\f0\9d\9a\a8" (func 389)) - (export "\f0\9d\9b\a2" (func 390)) - (export "\f0\9d\9c\9c" (func 391)) - (export "\f0\9d\9d\96" (func 392)) - (export "\f0\9d\9e\90" (func 393)) - (export "\e2\8d\b6" (func 394)) - (export "\e2\8d\ba" (func 395)) - (export "\e2\a9\9c" (func 396)) - (export "\e1\97\85" (func 397)) - (export "\e1\8e\aa" (func 398)) - (export ")\cb\ba\cb\bc\f0\94\97\8f\f0\9d\85\b4\f0\9d\85\b6\f0\9d\85\b8\f0\9d\85\ba\e2\81\be\e2\82\8e\e2\9d\a9\e2\9d\ab\e2\9f\af\ef\b4\bf\ef\b8\b6\ef\b9\9a\ef\bc\89\ef\bd\a0\f3\a0\80\a9\e2\9d\b3\e2\9d\b5\e2\9f\a7\e2\9f\a9\e2\9f\ab\e2\9f\ad\e2\a6\88\e2\a6\8a\e2\a6\96\e2\b8\a3\e2\b8\a5\ef\b8\98\ef\b8\b8\ef\b8\ba\ef\b8\bc\ef\b8\be\ef\b9\80\ef\b9\82\ef\b9\84\ef\b9\88\ef\b9\9c\ef\b9\9e\ef\bc\bd\ef\bd\9d\ef\bd\a3\f3\a0\81\9d\f3\a0\81\bd\c2\bb\e2\80\99\e2\80\9d\e2\80\ba\e2\9d\af" (func 399)) - (export "(\cb\b9\cb\bb\f0\94\97\8e\f0\9d\85\b3\f0\9d\85\b5\f0\9d\85\b7\f0\9d\85\b9\e2\81\bd\e2\82\8d\e2\9d\a8\e2\9d\aa\e2\9f\ae\ef\b4\be\ef\b8\b5\ef\b9\99\ef\bc\88\ef\bd\9f\f3\a0\80\a8\e2\9d\b2\e2\9d\b4\e2\9f\a6\e2\9f\a8\e2\9f\aa\e2\9f\ac\e2\a6\87\e2\a6\89\e2\a6\95\e2\b8\a2\e2\b8\a4\ef\b8\97\ef\b8\b7\ef\b8\b9\ef\b8\bb\ef\b8\bd\ef\b8\bf\ef\b9\81\ef\b9\83\ef\b9\87\ef\b9\9b\ef\b9\9d\ef\bc\bb\ef\bd\9b\ef\bd\a2\f3\a0\81\9b\f3\a0\81\bb\c2\ab\e2\80\98\e2\80\9c\e2\80\b9\e2\9d\ae" (func 400)) - (export "\f0\9d\aa\8b\f0\9d\aa\a4" (func 401)) - (export "\f0\9d\aa\8b" (func 402)) - (export "\c2\bd" (func 403)) - (export "1\e2\81\842" (func 404)) + (export "A\u{200b}" (func 169)) + (export "\u{410}" (func 170)) + (export "\u{a656}" (func 171)) + (export "\u{2dfc}" (func 172)) + (export "\u{2df6}" (func 173)) + (export "\u{2c6f}" (func 174)) + (export "\u{1f150}" (func 175)) + (export "\u{1f170}" (func 176)) + (export "\u{2c2d}" (func 177)) + (export "\u{10402}" (func 178)) + (export "\u{10408}" (func 179)) + (export "\u{104b0}" (func 180)) + (export "\u{c0}" (func 181)) + (export "\u{c1}" (func 182)) + (export "\u{c2}" (func 183)) + (export "\u{c3}" (func 184)) + (export "\u{c4}" (func 185)) + (export "\u{100}" (func 186)) + (export "\u{102}" (func 187)) + (export "\u{104}" (func 188)) + (export "\u{1cd}" (func 189)) + (export "\u{1de}" (func 190)) + (export "\u{1e0}" (func 191)) + (export "\u{1fa}" (func 192)) + (export "\u{200}" (func 193)) + (export "\u{202}" (func 194)) + (export "\u{226}" (func 195)) + (export "\u{23a}" (func 196)) + (export "\u{4d0}" (func 197)) + (export "\u{4d2}" (func 198)) + (export "\u{7ca}" (func 199)) + (export "\u{821}" (func 200)) + (export "\u{822}" (func 201)) + (export "\u{823}" (func 202)) + (export "\u{824}" (func 203)) + (export "\u{825}" (func 204)) + (export "\u{904}" (func 205)) + (export "\u{905}" (func 206)) + (export "\u{972}" (func 207)) + (export "\u{985}" (func 208)) + (export "\u{a05}" (func 209)) + (export "\u{a85}" (func 210)) + (export "\u{b05}" (func 211)) + (export "\u{b85}" (func 212)) + (export "\u{c05}" (func 213)) + (export "\u{c85}" (func 214)) + (export "\u{d05}" (func 215)) + (export "\u{e30}" (func 216)) + (export "\u{eb0}" (func 217)) + (export "\u{f01}" (func 218)) + (export "\u{f68}" (func 219)) + (export "\u{fb8}" (func 220)) + (export "\u{1021}" (func 221)) + (export "\u{1022}" (func 222)) + (export "\u{109c}" (func 223)) + (export "\u{1161}" (func 224)) + (export "\u{12a0}" (func 225)) + (export "\u{12d0}" (func 226)) + (export "\u{13a0}" (func 227)) + (export "\u{140a}" (func 228)) + (export "\u{15b3}" (func 229)) + (export "\u{16a8}" (func 230)) + (export "\u{16aa}" (func 231)) + (export "\u{16c6}" (func 232)) + (export "\u{1700}" (func 233)) + (export "\u{1720}" (func 234)) + (export "\u{1740}" (func 235)) + (export "\u{1760}" (func 236)) + (export "\u{1820}" (func 237)) + (export "\u{1887}" (func 238)) + (export "\u{1920}" (func 239)) + (export "\u{1963}" (func 240)) + (export "\u{1a15}" (func 241)) + (export "\u{1a4b}" (func 242)) + (export "\u{1a61}" (func 243)) + (export "\u{1b83}" (func 244)) + (export "\u{1bc0}" (func 245)) + (export "\u{1bc1}" (func 246)) + (export "\u{1c23}" (func 247)) + (export "\u{1e00}" (func 248)) + (export "\u{1ea0}" (func 249)) + (export "\u{1ea2}" (func 250)) + (export "\u{1ea4}" (func 251)) + (export "\u{1ea6}" (func 252)) + (export "\u{1ea8}" (func 253)) + (export "\u{1eaa}" (func 254)) + (export "\u{1eac}" (func 255)) + (export "\u{1eae}" (func 256)) + (export "\u{1eb0}" (func 257)) + (export "\u{1eb2}" (func 258)) + (export "\u{1eb4}" (func 259)) + (export "\u{1eb6}" (func 260)) + (export "\u{3042}" (func 261)) + (export "\u{30a2}" (func 262)) + (export "\u{311a}" (func 263)) + (export "\u{314f}" (func 264)) + (export "\u{320e}" (func 265)) + (export "\u{320f}" (func 266)) + (export "\u{3210}" (func 267)) + (export "\u{3211}" (func 268)) + (export "\u{3212}" (func 269)) + (export "\u{3213}" (func 270)) + (export "\u{3214}" (func 271)) + (export "\u{3215}" (func 272)) + (export "\u{3216}" (func 273)) + (export "\u{3217}" (func 274)) + (export "\u{3218}" (func 275)) + (export "\u{3219}" (func 276)) + (export "\u{321a}" (func 277)) + (export "\u{321b}" (func 278)) + (export "\u{326e}" (func 279)) + (export "\u{326f}" (func 280)) + (export "\u{3270}" (func 281)) + (export "\u{3271}" (func 282)) + (export "\u{3272}" (func 283)) + (export "\u{3273}" (func 284)) + (export "\u{3274}" (func 285)) + (export "\u{3275}" (func 286)) + (export "\u{3276}" (func 287)) + (export "\u{3277}" (func 288)) + (export "\u{3278}" (func 289)) + (export "\u{3279}" (func 290)) + (export "\u{327a}" (func 291)) + (export "\u{327b}" (func 292)) + (export "\u{32d0}" (func 293)) + (export "\u{a00a}" (func 294)) + (export "\u{a4ee}" (func 295)) + (export "\u{a549}" (func 296)) + (export "\u{a6a0}" (func 297)) + (export "\u{a800}" (func 298)) + (export "\u{a823}" (func 299)) + (export "\u{a85d}" (func 300)) + (export "\u{a882}" (func 301)) + (export "\u{a8ea}" (func 302)) + (export "\u{a922}" (func 303)) + (export "\u{a946}" (func 304)) + (export "\u{a984}" (func 305)) + (export "\u{aa00}" (func 306)) + (export "\u{ff71}" (func 307)) + (export "\u{ffc2}" (func 308)) + (export "\u{10000}" (func 309)) + (export "\u{10280}" (func 310)) + (export "\u{102a0}" (func 311)) + (export "\u{10300}" (func 312)) + (export "\u{103a0}" (func 313)) + (export "\u{10496}" (func 314)) + (export "\u{10500}" (func 315)) + (export "\u{10740}" (func 316)) + (export "\u{10800}" (func 317)) + (export "\u{10920}" (func 318)) + (export "\u{10980}" (func 319)) + (export "\u{109a0}" (func 320)) + (export "\u{10a00}" (func 321)) + (export "\u{10b00}" (func 322)) + (export "\u{10c00}" (func 323)) + (export "\u{10c01}" (func 324)) + (export "\u{10c80}" (func 325)) + (export "\u{11005}" (func 326)) + (export "\u{11083}" (func 327)) + (export "\u{11127}" (func 328)) + (export "\u{11150}" (func 329)) + (export "\u{11183}" (func 330)) + (export "\u{11200}" (func 331)) + (export "\u{11280}" (func 332)) + (export "\u{112b0}" (func 333)) + (export "\u{11305}" (func 334)) + (export "\u{11370}" (func 335)) + (export "\u{11400}" (func 336)) + (export "\u{11481}" (func 337)) + (export "\u{11580}" (func 338)) + (export "\u{11600}" (func 339)) + (export "\u{11680}" (func 340)) + (export "\u{11712}" (func 341)) + (export "\u{11720}" (func 342)) + (export "\u{118a1}" (func 343)) + (export "\u{11ad5}" (func 344)) + (export "\u{11c00}" (func 345)) + (export "\u{11c8f}" (func 346)) + (export "\u{11caf}" (func 347)) + (export "\u{12000}" (func 348)) + (export "\u{169d5}" (func 349)) + (export "\u{16a46}" (func 350)) + (export "\u{16ae7}" (func 351)) + (export "\u{16f54}" (func 352)) + (export "\u{1bc41}" (func 353)) + (export "\u{1bc64}" (func 354)) + (export "\u{1e823}" (func 355)) + (export "\u{1f1e6}" (func 356)) + (export "\u{2c6d}" (func 357)) + (export "\u{39b}" (func 358)) + (export "\u{2c70}" (func 359)) + (export "\u{aa}" (func 360)) + (export "\u{2200}" (func 361)) + (export "\u{20b3}" (func 362)) + (export "\u{10900}" (func 363)) + (export "\u{2c80}" (func 364)) + (export "\u{10330}" (func 365)) + (export "\u{386}" (func 366)) + (export "\u{391}" (func 367)) + (export "\u{1f08}" (func 368)) + (export "\u{1f09}" (func 369)) + (export "\u{1f0a}" (func 370)) + (export "\u{1f0b}" (func 371)) + (export "\u{1f0c}" (func 372)) + (export "\u{1f0d}" (func 373)) + (export "\u{1f0e}" (func 374)) + (export "\u{1f0f}" (func 375)) + (export "\u{1f88}" (func 376)) + (export "\u{1f89}" (func 377)) + (export "\u{1f8a}" (func 378)) + (export "\u{1f8b}" (func 379)) + (export "\u{1f8c}" (func 380)) + (export "\u{1f8d}" (func 381)) + (export "\u{1f8e}" (func 382)) + (export "\u{1f8f}" (func 383)) + (export "\u{1fb8}" (func 384)) + (export "\u{1fb9}" (func 385)) + (export "\u{1fba}" (func 386)) + (export "\u{1fbb}" (func 387)) + (export "\u{1fbc}" (func 388)) + (export "\u{1d6a8}" (func 389)) + (export "\u{1d6e2}" (func 390)) + (export "\u{1d71c}" (func 391)) + (export "\u{1d756}" (func 392)) + (export "\u{1d790}" (func 393)) + (export "\u{2376}" (func 394)) + (export "\u{237a}" (func 395)) + (export "\u{2a5c}" (func 396)) + (export "\u{15c5}" (func 397)) + (export "\u{13aa}" (func 398)) + (export ")\u{2fa}\u{2fc}\u{145cf}\u{1d174}\u{1d176}\u{1d178}\u{1d17a}\u{207e}\u{208e}\u{2769}\u{276b}\u{27ef}\u{fd3f}\u{fe36}\u{fe5a}\u{ff09}\u{ff60}\u{e0029}\u{2773}\u{2775}\u{27e7}\u{27e9}\u{27eb}\u{27ed}\u{2988}\u{298a}\u{2996}\u{2e23}\u{2e25}\u{fe18}\u{fe38}\u{fe3a}\u{fe3c}\u{fe3e}\u{fe40}\u{fe42}\u{fe44}\u{fe48}\u{fe5c}\u{fe5e}\u{ff3d}\u{ff5d}\u{ff63}\u{e005d}\u{e007d}\u{bb}\u{2019}\u{201d}\u{203a}\u{276f}" (func 399)) + (export "(\u{2f9}\u{2fb}\u{145ce}\u{1d173}\u{1d175}\u{1d177}\u{1d179}\u{207d}\u{208d}\u{2768}\u{276a}\u{27ee}\u{fd3e}\u{fe35}\u{fe59}\u{ff08}\u{ff5f}\u{e0028}\u{2772}\u{2774}\u{27e6}\u{27e8}\u{27ea}\u{27ec}\u{2987}\u{2989}\u{2995}\u{2e22}\u{2e24}\u{fe17}\u{fe37}\u{fe39}\u{fe3b}\u{fe3d}\u{fe3f}\u{fe41}\u{fe43}\u{fe47}\u{fe5b}\u{fe5d}\u{ff3b}\u{ff5b}\u{ff62}\u{e005b}\u{e007b}\u{ab}\u{2018}\u{201c}\u{2039}\u{276e}" (func 400)) + (export "\u{1da8b}\u{1daa4}" (func 401)) + (export "\u{1da8b}" (func 402)) + (export "\u{bd}" (func 403)) + (export "1\u{2044}2" (func 404)) (export "1/2" (func 405)) - (export "\e0\ad\b3" (func 406)) - (export "\e0\b5\b4" (func 407)) - (export "\e2\b3\bd" (func 408)) - (export "\ea\a0\b1" (func 409)) - (export "\f0\90\85\81" (func 410)) - (export "\f0\90\85\b5" (func 411)) - (export "\f0\90\85\b6" (func 412)) - (export "\f0\90\a6\bd" (func 413)) - (export "\f0\90\b9\bb" (func 414)) - (export "\ef\bc\82" (func 415)) - (export "\7f" (func 416)) - (export "\08" (func 417)) - (export "\e2\8c\ab" (func 418)) - (export "\e2\8c\a6" (func 419)) - (export "\e2\90\88" (func 420)) - (export "\e2\90\a1" (func 421)) - (export "\e1\b7\bb" (func 422)) - (export "\0f" (func 423)) - (export "\e2\86\90" (func 424)) - (export "\e2\8c\a7" (func 425)) - (export "\e2\8d\92" (func 426)) - (export "\e2\8d\94" (func 427)) - (export "\e2\8d\a2" (func 428)) - (export "\e2\8d\ab" (func 429)) - (export "\1a" (func 430)) - (export "\e2\90\a6" (func 431)) - (export "\e2\90\9a" (func 432)) - (export "\ef\bf\bc" (func 433)) + (export "\u{b73}" (func 406)) + (export "\u{d74}" (func 407)) + (export "\u{2cfd}" (func 408)) + (export "\u{a831}" (func 409)) + (export "\u{10141}" (func 410)) + (export "\u{10175}" (func 411)) + (export "\u{10176}" (func 412)) + (export "\u{109bd}" (func 413)) + (export "\u{10e7b}" (func 414)) + (export "\u{ff02}" (func 415)) + (export "\u{7f}" (func 416)) + (export "\u{8}" (func 417)) + (export "\u{232b}" (func 418)) + (export "\u{2326}" (func 419)) + (export "\u{2408}" (func 420)) + (export "\u{2421}" (func 421)) + (export "\u{1dfb}" (func 422)) + (export "\u{f}" (func 423)) + (export "\u{2190}" (func 424)) + (export "\u{2327}" (func 425)) + (export "\u{2352}" (func 426)) + (export "\u{2354}" (func 427)) + (export "\u{2362}" (func 428)) + (export "\u{236b}" (func 429)) + (export "\u{1a}" (func 430)) + (export "\u{2426}" (func 431)) + (export "\u{241a}" (func 432)) + (export "\u{fffc}" (func 433)) (export "?" (func 434)) - (export "\c2\bf" (func 435)) - (export "\e1\a5\85" (func 436)) - (export "\cd\be" (func 437)) - (export "\d5\9e" (func 438)) - (export "\d8\9f" (func 439)) - (export "\e1\8d\a7" (func 440)) - (export "\e2\81\87" (func 441)) - (export "\e2\8d\b0" (func 442)) - (export "\e2\9d\93" (func 443)) - (export "\e2\9d\94" (func 444)) - (export "\e2\b3\ba" (func 445)) - (export "\e2\b3\bb" (func 446)) - (export "\e2\b8\ae" (func 447)) - (export "\e3\89\84" (func 448)) - (export "\ea\98\8f" (func 449)) - (export "\ea\9b\b7" (func 450)) - (export "\ef\b8\96" (func 451)) - (export "\ef\b9\96" (func 452)) - (export "\ef\bc\9f" (func 453)) - (export "\f0\91\85\83" (func 454)) - (export "\f0\9e\a5\9f" (func 455)) - (export "\f3\a0\80\bf" (func 456)) - (export "\f0\96\a1\84" (func 457)) - (export "\e2\af\91" (func 458)) - (export "\c2\b6" (func 459)) - (export "\e2\81\8b" (func 460)) - (export "\dc\80" (func 461)) - (export "\e1\83\bb" (func 462)) - (export "\e1\8d\a8" (func 463)) - (export "\e3\80\b7" (func 464)) - (export "\e2\9d\a1" (func 465)) - (export "\e2\b8\8f" (func 466)) - (export "\e2\b8\90" (func 467)) - (export "\e2\b8\91" (func 468)) - (export "\e2\b8\8e" (func 469)) - (export "\14" (func 470)) - (export "\e2\98\99" (func 471)) - (export "\e2\b8\bf" (func 472)) - (export "\e3\80\87" (func 473)) - (export "\e0\b9\9b" (func 474)) - (export "\ea\99\ae" (func 475)) - (export "\cf\93" (func 476)) - (export "\cf\94" (func 477)) - (export "\e1\ba\9b" (func 478)) + (export "\u{bf}" (func 435)) + (export "\u{1945}" (func 436)) + (export "\u{37e}" (func 437)) + (export "\u{55e}" (func 438)) + (export "\u{61f}" (func 439)) + (export "\u{1367}" (func 440)) + (export "\u{2047}" (func 441)) + (export "\u{2370}" (func 442)) + (export "\u{2753}" (func 443)) + (export "\u{2754}" (func 444)) + (export "\u{2cfa}" (func 445)) + (export "\u{2cfb}" (func 446)) + (export "\u{2e2e}" (func 447)) + (export "\u{3244}" (func 448)) + (export "\u{a60f}" (func 449)) + (export "\u{a6f7}" (func 450)) + (export "\u{fe16}" (func 451)) + (export "\u{fe56}" (func 452)) + (export "\u{ff1f}" (func 453)) + (export "\u{11143}" (func 454)) + (export "\u{1e95f}" (func 455)) + (export "\u{e003f}" (func 456)) + (export "\u{16844}" (func 457)) + (export "\u{2bd1}" (func 458)) + (export "\u{b6}" (func 459)) + (export "\u{204b}" (func 460)) + (export "\u{700}" (func 461)) + (export "\u{10fb}" (func 462)) + (export "\u{1368}" (func 463)) + (export "\u{3037}" (func 464)) + (export "\u{2761}" (func 465)) + (export "\u{2e0f}" (func 466)) + (export "\u{2e10}" (func 467)) + (export "\u{2e11}" (func 468)) + (export "\u{2e0e}" (func 469)) + (export "\u{14}" (func 470)) + (export "\u{2619}" (func 471)) + (export "\u{2e3f}" (func 472)) + (export "\u{3007}" (func 473)) + (export "\u{e5b}" (func 474)) + (export "\u{a66e}" (func 475)) + (export "\u{3d3}" (func 476)) + (export "\u{3d4}" (func 477)) + (export "\u{1e9b}" (func 478)) ) diff --git a/tests/snapshots/testsuite/proposals/annotations/id.wast/0.print b/tests/snapshots/testsuite/proposals/annotations/id.wast/0.print index 4ef0bab69e..fbd64f6164 100644 --- a/tests/snapshots/testsuite/proposals/annotations/id.wast/0.print +++ b/tests/snapshots/testsuite/proposals/annotations/id.wast/0.print @@ -12,13 +12,13 @@ (func (;5;) (type 0) call $!?@#a$%^&*b-+_.:9'`|/\<=>~ ) - (func $#func6<_random_____stuff_> (@name " random \t \n stuff ") (;6;) (type 0)) + (func $" random \u{9} \u{a} stuff " (;6;) (type 0)) (func (;7;) (type 0) - call $#func6<_random_____stuff_> + call $" random \u{9} \u{a} stuff " ) - (func $#func8<___> (@name " ") (;8;) (type 0)) + (func $" \u{f61a}\u{f4a9}" (;8;) (type 0)) (func (;9;) (type 0) - call $#func8<___> + call $" \u{f61a}\u{f4a9}" ) (func $fh (;10;) (type 0)) (func (;11;) (type 0) @@ -39,15 +39,15 @@ call $AB call $AB ) - (func $#func18<_> (@name "\t") (;18;) (type 0)) + (func $"\u{9}" (;18;) (type 0)) (func (;19;) (type 0) - call $#func18<_> - call $#func18<_> + call $"\u{9}" + call $"\u{9}" ) - (func $#func20<__> (@name "") (;20;) (type 0)) + (func $"\u{f61a}\u{f4a9}" (;20;) (type 0)) (func (;21;) (type 0) - call $#func20<__> - call $#func20<__> + call $"\u{f61a}\u{f4a9}" + call $"\u{f61a}\u{f4a9}" ) (func (;22;) (type 0) block $l1 @@ -64,11 +64,11 @@ else end i32.const 0 - if $#label4<_> (@name "\t") + if $"\u{9}" else end i32.const 0 - if $#label5<___> (@name " ") + if $"\u{f61a}\u{f4a9} " else end ) diff --git a/tests/snapshots/testsuite/proposals/function-references/custom.wast/0.print b/tests/snapshots/testsuite/proposals/function-references/custom.wast/0.print index 00e35eaea7..d0cea5e656 100644 --- a/tests/snapshots/testsuite/proposals/function-references/custom.wast/0.print +++ b/tests/snapshots/testsuite/proposals/function-references/custom.wast/0.print @@ -4,8 +4,8 @@ (@custom "a custom section" (before first) "") (@custom "" (before first) "this is payload") (@custom "" (before first) "") - (@custom "\00\00custom sectio\00" (before first) "this is the payload") - (@custom "\ef\bb\bfa custom sect" (before first) "this is the payload") - (@custom "a custom sect\e2\8c\a3" (before first) "this is the payload") + (@custom "\u{0}\u{0}custom sectio\u{0}" (before first) "this is the payload") + (@custom "\u{feff}a custom sect" (before first) "this is the payload") + (@custom "a custom sect\u{2323}" (before first) "this is the payload") (@custom "module within a module" (before first) "\00asm\01\00\00\00") ) From 78fbfd1b3b612a307d782935d5a32fc9fe40dccf Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 13 Jun 2024 10:32:36 -0700 Subject: [PATCH 12/58] `wasm-smith`: fix generation of memories with custom page sizes (#1616) * wasm-smith: fix generation of memories with custom page sizes * Also enable the proposal in our local fuzzing configs --- crates/wasm-smith/Cargo.toml | 2 +- crates/wasm-smith/src/config.rs | 24 ++++++++++++++---------- crates/wasm-smith/src/core.rs | 15 ++++++++++----- crates/wasm-smith/tests/core.rs | 19 +++++++++++++++++++ fuzz/src/lib.rs | 5 +++++ fuzz/src/no_traps.rs | 4 ++-- 6 files changed, 51 insertions(+), 18 deletions(-) diff --git a/crates/wasm-smith/Cargo.toml b/crates/wasm-smith/Cargo.toml index 16e4810d69..49d9379365 100644 --- a/crates/wasm-smith/Cargo.toml +++ b/crates/wasm-smith/Cargo.toml @@ -35,7 +35,7 @@ wat = { workspace = true, optional = true } [dev-dependencies] criterion = { workspace = true } rand = { workspace = true } -wasmparser = { workspace = true } +wasmparser = { workspace = true, features = ["validate"] } wasmprinter = { path = "../wasmprinter" } wat = { path = "../wat" } diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs index 59ec81668b..f628b7c700 100644 --- a/crates/wasm-smith/src/config.rs +++ b/crates/wasm-smith/src/config.rs @@ -379,17 +379,21 @@ define_config! { /// wasm proposal. pub max_memories: usize = 1, - /// The maximum, in 64k Wasm pages, of any 32-bit memory's initial or - /// maximum size. + /// The maximum, in bytes, of any 32-bit memory's initial or maximum + /// size. /// - /// Defaults to 2^16. - pub max_memory32_pages: u64 = 1 << 16, + /// May not be larger than `2**32`. + /// + /// Defaults to `2**32`. + pub max_memory32_bytes: u64 = u32::MAX as u64 + 1, - /// The maximum, in 64k Wasm pages, of any 64-bit memory's initial or - /// maximum size. + /// The maximum, in bytes, of any 64-bit memory's initial or maximum + /// size. + /// + /// May not be larger than `2**64`. /// - /// Defaults to 2^48. - pub max_memory64_pages: u64 = 1 << 48, + /// Defaults to `2**64`. + pub max_memory64_bytes: u128 = u64::MAX as u128 + 1, /// The maximum number of modules to use. Defaults to 10. /// @@ -664,8 +668,8 @@ impl<'a> Arbitrary<'a> for Config { max_instructions: u.int_in_range(0..=MAX_MAXIMUM)?, max_memories: u.int_in_range(0..=100)?, max_tables, - max_memory32_pages: u.int_in_range(0..=1 << 16)?, - max_memory64_pages: u.int_in_range(0..=1 << 48)?, + max_memory32_bytes: u.int_in_range(0..=u32::MAX as u64 + 1)?, + max_memory64_bytes: u.int_in_range(0..=u64::MAX as u128 + 1)?, min_uleb_size: u.int_in_range(0..=5)?, bulk_memory_enabled: u.arbitrary()?, reference_types_enabled, diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index ede06314e3..e35059ac8b 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -2558,17 +2558,22 @@ pub(crate) fn arbitrary_memtype(u: &mut Unstructured, config: &Config) -> Result // We want to favor memories <= 1gb in size, allocate at most 16k pages, // depending on the maximum number of memories. let memory64 = config.memory64_enabled && u.arbitrary()?; - let page_size = if config.custom_page_sizes_enabled && u.arbitrary()? { - Some(1 << u.int_in_range(0..=16)?) + let page_size_log2 = if config.custom_page_sizes_enabled && u.arbitrary()? { + Some(if u.arbitrary()? { 0 } else { 16 }) } else { None }; let max_inbounds = 16 * 1024 / u64::try_from(config.max_memories).unwrap(); let min_pages = if config.disallow_traps { Some(1) } else { None }; let max_pages = min_pages.unwrap_or(0).max(if memory64 { - config.max_memory64_pages + u64::try_from(config.max_memory64_bytes >> page_size_log2.unwrap_or(16)) + // Can only fail when we have a custom page size of 1 byte and a + // memory size of `2**64 == u64::MAX + 1`. In this case, just + // saturate to `u64::MAX`. + .unwrap_or(u64::MAX as u64) } else { - config.max_memory32_pages + // Unlike above, this can never fail. + u64::try_from(config.max_memory32_bytes >> page_size_log2.unwrap_or(16)).unwrap() }); let (minimum, maximum) = arbitrary_limits64( u, @@ -2582,7 +2587,7 @@ pub(crate) fn arbitrary_memtype(u: &mut Unstructured, config: &Config) -> Result maximum, memory64, shared, - page_size_log2: page_size, + page_size_log2, }) } diff --git a/crates/wasm-smith/tests/core.rs b/crates/wasm-smith/tests/core.rs index 9b18fcdeaf..d7972f2911 100644 --- a/crates/wasm-smith/tests/core.rs +++ b/crates/wasm-smith/tests/core.rs @@ -142,6 +142,25 @@ fn smoke_test_wasm_gc() { } } +#[test] +fn smoke_test_wasm_custom_page_sizes() { + let mut rng = SmallRng::seed_from_u64(0); + let mut buf = vec![0; 2048]; + for _ in 0..1024 { + rng.fill_bytes(&mut buf); + let mut u = Unstructured::new(&buf); + let config = Config { + custom_page_sizes_enabled: true, + ..Config::default() + }; + if let Ok(module) = Module::new(config, &mut u) { + let wasm_bytes = module.to_bytes(); + let mut validator = Validator::new_with_features(wasm_features()); + validate(&mut validator, &wasm_bytes); + } + } +} + fn wasm_features() -> WasmFeatures { WasmFeatures::all() } diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index 2cf513f1fa..32c2f8f682 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -28,6 +28,7 @@ pub fn generate_valid_module( config.exceptions_enabled = u.arbitrary()?; config.canonicalize_nans = u.arbitrary()?; config.tail_call_enabled = u.arbitrary()?; + config.custom_page_sizes_enabled = u.arbitrary()?; config.gc_enabled = u.arbitrary()?; config.reference_types_enabled = config.reference_types_enabled || config.gc_enabled; @@ -91,6 +92,10 @@ pub fn validator_for_config(config: &Config) -> wasmparser::Validator { features.set(WasmFeatures::MEMORY64, config.memory64_enabled); features.set(WasmFeatures::THREADS, config.threads_enabled); features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled); + features.set( + WasmFeatures::CUSTOM_PAGE_SIZES, + config.custom_page_sizes_enabled, + ); // TODO: determine our larger story for function-references in // wasm-tools and whether we should just have a Wasm GC flag since // function-references is effectively part of the Wasm GC proposal at diff --git a/fuzz/src/no_traps.rs b/fuzz/src/no_traps.rs index 90e13431e2..782ff0cafe 100644 --- a/fuzz/src/no_traps.rs +++ b/fuzz/src/no_traps.rs @@ -16,8 +16,8 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { config.threads_enabled = false; config.exceptions_enabled = false; config.gc_enabled = false; - config.max_memory32_pages = config.max_memory32_pages.min(100); - config.max_memory64_pages = config.max_memory64_pages.min(100); + config.max_memory32_bytes = config.max_memory32_bytes.min(1 << 18); + config.max_memory64_bytes = config.max_memory64_bytes.min(1 << 18); // NB: should re-enable once wasmtime implements the table64 extension // to the memory64 proposal. From b04b5989169df47e0e90f2998f7e959bec7ed21c Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 13 Jun 2024 14:08:03 -0700 Subject: [PATCH 13/58] wasm-smith: Minor fixes for generating memories with custom page sizes (#1617) --- crates/wasm-smith/src/core.rs | 69 ++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 18 deletions(-) diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index e35059ac8b..29514f7ac0 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -2472,12 +2472,27 @@ pub(crate) fn arbitrary_limits64( max_required: bool, max_inbounds: u64, ) -> Result<(u64, Option)> { + assert!( + min_minimum.unwrap_or(0) <= max_minimum, + "{} <= {max_minimum}", + min_minimum.unwrap_or(0), + ); + assert!( + min_minimum.unwrap_or(0) <= max_inbounds, + "{} <= {max_inbounds}", + min_minimum.unwrap_or(0), + ); + let min = gradually_grow(u, min_minimum.unwrap_or(0), max_inbounds, max_minimum)?; + assert!(min <= max_minimum, "{min} <= {max_minimum}"); + let max = if max_required || u.arbitrary().unwrap_or(false) { Some(u.int_in_range(min..=max_minimum)?) } else { None }; + assert!(min <= max.unwrap_or(min), "{min} <= {}", max.unwrap_or(min)); + Ok((min, max)) } @@ -2555,15 +2570,14 @@ pub(crate) fn arbitrary_memtype(u: &mut Unstructured, config: &Config) -> Result // When threads are enabled, we only want to generate shared memories about // 25% of the time. let shared = config.threads_enabled && u.ratio(1, 4)?; - // We want to favor memories <= 1gb in size, allocate at most 16k pages, - // depending on the maximum number of memories. + let memory64 = config.memory64_enabled && u.arbitrary()?; let page_size_log2 = if config.custom_page_sizes_enabled && u.arbitrary()? { Some(if u.arbitrary()? { 0 } else { 16 }) } else { None }; - let max_inbounds = 16 * 1024 / u64::try_from(config.max_memories).unwrap(); + let min_pages = if config.disallow_traps { Some(1) } else { None }; let max_pages = min_pages.unwrap_or(0).max(if memory64 { u64::try_from(config.max_memory64_bytes >> page_size_log2.unwrap_or(16)) @@ -2572,16 +2586,27 @@ pub(crate) fn arbitrary_memtype(u: &mut Unstructured, config: &Config) -> Result // saturate to `u64::MAX`. .unwrap_or(u64::MAX as u64) } else { - // Unlike above, this can never fail. - u64::try_from(config.max_memory32_bytes >> page_size_log2.unwrap_or(16)).unwrap() + u32::try_from(config.max_memory32_bytes >> page_size_log2.unwrap_or(16)) + // Similar case as above, but while we could represent `2**32` in our + // `u64` here, 32-bit memories' limits must fit in a `u32`. + .unwrap_or(u32::MAX) + .into() }); + + // We want to favor keeping the total memories <= 1gb in size. + let max_all_mems_in_bytes = 1 << 30; + let max_this_mem_in_bytes = max_all_mems_in_bytes / u64::try_from(config.max_memories).unwrap(); + let max_inbounds = max_this_mem_in_bytes >> page_size_log2.unwrap_or(16); + let max_inbounds = max_inbounds.clamp(min_pages.unwrap_or(0), max_pages); + let (minimum, maximum) = arbitrary_limits64( u, min_pages, max_pages, config.memory_max_size_required || shared, - max_inbounds.min(max_pages), + max_inbounds, )?; + Ok(MemoryType { minimum, maximum, @@ -2615,18 +2640,26 @@ fn gradually_grow(u: &mut Unstructured, min: u64, max_inbounds: u64, max: u64) - if min == max { return Ok(min); } - let min = min as f64; - let max = max as f64; - let max_inbounds = max_inbounds as f64; - let x = u.arbitrary::()?; - let x = f64::from(x); - let x = map_custom( - x, - f64::from(u32::MIN)..f64::from(u32::MAX), - min..max_inbounds, - min..max, - ); - return Ok(x.round() as u64); + let x = { + let min = min as f64; + let max = max as f64; + let max_inbounds = max_inbounds as f64; + let x = u.arbitrary::()?; + let x = f64::from(x); + let x = map_custom( + x, + f64::from(u32::MIN)..f64::from(u32::MAX), + min..max_inbounds, + min..max, + ); + assert!(min <= x, "{min} <= {x}"); + assert!(x <= max, "{x} <= {max}"); + x.round() as u64 + }; + + // Conversion between `u64` and `f64` is lossy, especially for large + // numbers, so just clamp the final result. + return Ok(x.clamp(min, max)); /// Map a value from within the input range to the output range(s). /// From 1bf990b514ad8d118ae80310b9bc8b38fa034bcb Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Fri, 14 Jun 2024 07:46:15 -0700 Subject: [PATCH 14/58] add links to playground (#1610) * add links to playground * move links to later in doc * fix table hopefully --- README.md | 42 +++++++++++++++++++++--------------------- playground/README.md | 2 +- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/README.md b/README.md index 472d3ac386..aef7c70d5e 100644 --- a/README.md +++ b/README.md @@ -130,27 +130,27 @@ The `wasm-tools` binary internally contains a number of subcommands for working with wasm modules and component. Many subcommands also come with Rust crates that can be use programmatically as well: -| CLI | Rust Crate | Description | -|------|------|------------| -| `wasm-tools validate` | [wasmparser] | Validate a WebAssembly file | -| `wasm-tools parse` | [wat] and [wast] | Translate the WebAssembly text format to binary | -| `wasm-tools print` | [wasmprinter] | Translate the WebAssembly binary format to text | -| `wasm-tools smith` | [wasm-smith] | Generate a valid WebAssembly module from an input seed | -| `wasm-tools mutate` | [wasm-mutate] | Mutate an input wasm file into a new valid wasm file | -| `wasm-tools shrink` | [wasm-shrink] | Shrink a wasm file while preserving a predicate | -| `wasm-tools dump` | | Print debugging information about the binary format | -| `wasm-tools objdump` | | Print debugging information about section headers | -| `wasm-tools strip` | | Remove custom sections from a WebAssembly file | -| `wasm-tools demangle` | | Demangle Rust and C++ symbol names in the `name` section | -| `wasm-tools compose` | [wasm-compose] | Compose wasm components together (*deprecated*) | -| `wasm-tools component new` | [wit-component] | Create a component from a core wasm binary | -| `wasm-tools component wit` | | Extract a `*.wit` interface from a component | -| `wasm-tools component embed` | | Embed a `component-type` custom section in a core wasm binary | -| `wasm-tools metadata show` | [wasm-metadata] | Show name and producer metadata in a component or module | -| `wasm-tools metadata add` | | Add name or producer metadata to a component or module | -| `wasm-tools addr2line` | | Translate wasm offsets to filename/line numbers with DWARF | -| `wasm-tools completion` | | Generate shell completion scripts for `wasm-tools` | -| `wasm-tools json-from-wast` | | Convert a `*.wast` file into JSON commands | +| CLI | Rust Crate | Playground | Description | +|------|------|--------|------------| +| `wasm-tools validate` | [wasmparser] | | Validate a WebAssembly file | +| `wasm-tools parse` | [wat] and [wast] | [parse](https://bytecodealliance.github.io/wasm-tools/parse) | Translate the WebAssembly text format to binary | +| `wasm-tools print` | [wasmprinter] | [print](https://bytecodealliance.github.io/wasm-tools/print) | Translate the WebAssembly binary format to text | +| `wasm-tools smith` | [wasm-smith] | | Generate a valid WebAssembly module from an input seed | +| `wasm-tools mutate` | [wasm-mutate] | | Mutate an input wasm file into a new valid wasm file | +| `wasm-tools shrink` | [wasm-shrink] | | Shrink a wasm file while preserving a predicate | +| `wasm-tools dump` | | | Print debugging information about the binary format | +| `wasm-tools objdump` | | | Print debugging information about section headers | +| `wasm-tools strip` | | | Remove custom sections from a WebAssembly file | +| `wasm-tools demangle` | | | Demangle Rust and C++ symbol names in the `name` section | +| `wasm-tools compose` | [wasm-compose] | | Compose wasm components together (*deprecated*) | +| `wasm-tools component new` | [wit-component] | | Create a component from a core wasm binary | +| `wasm-tools component wit` | | | Extract a `*.wit` interface from a component | +| `wasm-tools component embed` | | | Embed a `component-type` custom section in a core wasm binary | +| `wasm-tools metadata show` | [wasm-metadata] | | Show name and producer metadata in a component or module | +| `wasm-tools metadata add` | | | Add name or producer metadata to a component or module | +| `wasm-tools addr2line` | | | Translate wasm offsets to filename/line numbers with DWARF | +| `wasm-tools completion` | | | Generate shell completion scripts for `wasm-tools` | +| `wasm-tools json-from-wast` | | | Convert a `*.wast` file into JSON commands | [wasmparser]: https://crates.io/crates/wasmparser [wat]: https://crates.io/crates/wat diff --git a/playground/README.md b/playground/README.md index 1398840501..d0edd980bc 100644 --- a/playground/README.md +++ b/playground/README.md @@ -1,6 +1,6 @@ # Playground -This is a simple online playground for `wasm-tools parse`, available at https://bytecodealliance.github.io/wasm-tools/. +This is a simple online playground for `wasm-tools parse` and `print`, available at https://bytecodealliance.github.io/wasm-tools/parse / https://bytecodealliance.github.io/wasm-tools/print respectively. ## Building From 0ef50acb762120f3a548cd1fd59813427770f9e9 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 14 Jun 2024 10:09:37 -0500 Subject: [PATCH 15/58] Update spec testsuite submodule (#1618) --- tests/snapshots/testsuite/memory.wast.json | 165 ++++++++++++++++++ .../snapshots/testsuite/memory.wast/79.print | 16 ++ .../function-references/return_call.wast.json | 13 +- .../return_call_indirect.wast.json | 15 +- .../return_call_ref.wast.json | 7 + .../proposals/memory64/memory.wast.json | 165 ++++++++++++++++++ .../proposals/memory64/memory.wast/79.print | 16 ++ .../proposals/multi-memory/memory.wast.json | 165 ++++++++++++++++++ .../multi-memory/memory.wast/77.print | 16 ++ tests/testsuite | 2 +- 10 files changed, 572 insertions(+), 8 deletions(-) create mode 100644 tests/snapshots/testsuite/memory.wast/79.print create mode 100644 tests/snapshots/testsuite/proposals/memory64/memory.wast/79.print create mode 100644 tests/snapshots/testsuite/proposals/multi-memory/memory.wast/77.print diff --git a/tests/snapshots/testsuite/memory.wast.json b/tests/snapshots/testsuite/memory.wast.json index 0f7dfa794e..7830dd2c13 100644 --- a/tests/snapshots/testsuite/memory.wast.json +++ b/tests/snapshots/testsuite/memory.wast.json @@ -1093,6 +1093,171 @@ "filename": "memory.33.wat", "text": "duplicate memory", "module_type": "text" + }, + { + "type": "module", + "line": 246, + "filename": "memory.34.wasm" + }, + { + "type": "assert_return", + "line": 260, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 261, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "10000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 262, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "20000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 263, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "30000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 264, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "40000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 265, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "50000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 266, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "60000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 267, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "65535" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] } ] } \ No newline at end of file diff --git a/tests/snapshots/testsuite/memory.wast/79.print b/tests/snapshots/testsuite/memory.wast/79.print new file mode 100644 index 0000000000..20f91d9cb6 --- /dev/null +++ b/tests/snapshots/testsuite/memory.wast/79.print @@ -0,0 +1,16 @@ +(module + (type (;0;) (func (param i32) (result i32))) + (func (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.load8_u + ) + (memory (;0;) 1 1) + (global (;0;) i32 i32.const 10000) + (global (;1;) i32 i32.const 10000) + (global (;2;) i32 i32.const 10000) + (export "memory" (memory 0)) + (export "__data_end" (global 0)) + (export "__stack_top" (global 1)) + (export "__heap_base" (global 2)) + (export "load" (func 0)) +) diff --git a/tests/snapshots/testsuite/proposals/function-references/return_call.wast.json b/tests/snapshots/testsuite/proposals/function-references/return_call.wast.json index 47e987d263..5400ea4be4 100644 --- a/tests/snapshots/testsuite/proposals/function-references/return_call.wast.json +++ b/tests/snapshots/testsuite/proposals/function-references/return_call.wast.json @@ -650,17 +650,24 @@ }, { "type": "assert_invalid", - "line": 196, + "line": 192, "filename": "return_call.11.wasm", - "text": "unknown function", + "text": "type mismatch", "module_type": "binary" }, { "type": "assert_invalid", - "line": 200, + "line": 204, "filename": "return_call.12.wasm", "text": "unknown function", "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 208, + "filename": "return_call.13.wasm", + "text": "unknown function", + "module_type": "binary" } ] } \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/function-references/return_call_indirect.wast.json b/tests/snapshots/testsuite/proposals/function-references/return_call_indirect.wast.json index d516ab63ea..48bf185b9f 100644 --- a/tests/snapshots/testsuite/proposals/function-references/return_call_indirect.wast.json +++ b/tests/snapshots/testsuite/proposals/function-references/return_call_indirect.wast.json @@ -1048,22 +1048,29 @@ }, { "type": "assert_invalid", - "line": 516, + "line": 512, "filename": "return_call_indirect.25.wasm", - "text": "unknown type", + "text": "type mismatch", "module_type": "binary" }, { "type": "assert_invalid", - "line": 523, + "line": 526, "filename": "return_call_indirect.26.wasm", "text": "unknown type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 534, + "line": 533, "filename": "return_call_indirect.27.wasm", + "text": "unknown type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 544, + "filename": "return_call_indirect.28.wasm", "text": "unknown function 0", "module_type": "binary" } diff --git a/tests/snapshots/testsuite/proposals/function-references/return_call_ref.wast.json b/tests/snapshots/testsuite/proposals/function-references/return_call_ref.wast.json index 86ed2d9daa..de34636302 100644 --- a/tests/snapshots/testsuite/proposals/function-references/return_call_ref.wast.json +++ b/tests/snapshots/testsuite/proposals/function-references/return_call_ref.wast.json @@ -704,6 +704,13 @@ "filename": "return_call_ref.13.wasm", "text": "type mismatch", "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 379, + "filename": "return_call_ref.14.wasm", + "text": "type mismatch", + "module_type": "binary" } ] } \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/memory64/memory.wast.json b/tests/snapshots/testsuite/proposals/memory64/memory.wast.json index dc581c1e51..aeb90ccc27 100644 --- a/tests/snapshots/testsuite/proposals/memory64/memory.wast.json +++ b/tests/snapshots/testsuite/proposals/memory64/memory.wast.json @@ -1093,6 +1093,171 @@ "filename": "memory.33.wat", "text": "duplicate memory", "module_type": "text" + }, + { + "type": "module", + "line": 246, + "filename": "memory.34.wasm" + }, + { + "type": "assert_return", + "line": 260, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 261, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "10000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 262, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "20000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 263, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "30000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 264, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "40000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 265, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "50000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 266, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "60000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 267, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "65535" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] } ] } \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/memory64/memory.wast/79.print b/tests/snapshots/testsuite/proposals/memory64/memory.wast/79.print new file mode 100644 index 0000000000..20f91d9cb6 --- /dev/null +++ b/tests/snapshots/testsuite/proposals/memory64/memory.wast/79.print @@ -0,0 +1,16 @@ +(module + (type (;0;) (func (param i32) (result i32))) + (func (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.load8_u + ) + (memory (;0;) 1 1) + (global (;0;) i32 i32.const 10000) + (global (;1;) i32 i32.const 10000) + (global (;2;) i32 i32.const 10000) + (export "memory" (memory 0)) + (export "__data_end" (global 0)) + (export "__stack_top" (global 1)) + (export "__heap_base" (global 2)) + (export "load" (func 0)) +) diff --git a/tests/snapshots/testsuite/proposals/multi-memory/memory.wast.json b/tests/snapshots/testsuite/proposals/multi-memory/memory.wast.json index e8516de459..51af008038 100644 --- a/tests/snapshots/testsuite/proposals/multi-memory/memory.wast.json +++ b/tests/snapshots/testsuite/proposals/multi-memory/memory.wast.json @@ -1079,6 +1079,171 @@ "filename": "memory.31.wat", "text": "duplicate memory", "module_type": "text" + }, + { + "type": "module", + "line": 243, + "filename": "memory.32.wasm" + }, + { + "type": "assert_return", + "line": 257, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 258, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "10000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 259, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "20000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 260, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "30000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 261, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "40000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 262, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "50000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 263, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "60000" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 264, + "action": { + "type": "invoke", + "field": "load", + "args": [ + { + "type": "i32", + "value": "65535" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] } ] } \ No newline at end of file diff --git a/tests/snapshots/testsuite/proposals/multi-memory/memory.wast/77.print b/tests/snapshots/testsuite/proposals/multi-memory/memory.wast/77.print new file mode 100644 index 0000000000..20f91d9cb6 --- /dev/null +++ b/tests/snapshots/testsuite/proposals/multi-memory/memory.wast/77.print @@ -0,0 +1,16 @@ +(module + (type (;0;) (func (param i32) (result i32))) + (func (;0;) (type 0) (param i32) (result i32) + local.get 0 + i32.load8_u + ) + (memory (;0;) 1 1) + (global (;0;) i32 i32.const 10000) + (global (;1;) i32 i32.const 10000) + (global (;2;) i32 i32.const 10000) + (export "memory" (memory 0)) + (export "__data_end" (global 0)) + (export "__stack_top" (global 1)) + (export "__heap_base" (global 2)) + (export "load" (func 0)) +) diff --git a/tests/testsuite b/tests/testsuite index 6dfedc8b84..e05365077e 160000 --- a/tests/testsuite +++ b/tests/testsuite @@ -1 +1 @@ -Subproject commit 6dfedc8b8423a91c1dc340d3af1a7f4fbf7868b4 +Subproject commit e05365077e13a1d86ffe77acfb1a835b7aa78422 From 999fc16d1a326e4c20c30fb31480f95303dabac4 Mon Sep 17 00:00:00 2001 From: Mendy Berger <12537668+MendyBerger@users.noreply.github.com> Date: Fri, 14 Jun 2024 11:17:44 -0400 Subject: [PATCH 16/58] Add a wit-encoder tool (#1580) * init wit-encoder * implement base types for `wit-encoder` * fix warnings * tidy up * tidy world * Mostly interface updates * Interface updates * Function accessors * Result accessors * Some type updates * impl Display for inline types * Some type additions * Added render trait * TypeDef flags, enums, and variants * params construct from iterator * Added resource * Standalone functions * Removed some unused types * Added some tests * Docs * Handle intensifiers with keywords * Cleaned up some comments * Clean up render a bit * Made some things more private * Some smaller usability changes * Added serde dependency * Same derives everywhere * interface and package items should be a single Vec, to keep global order * Cargo.lock * Fix up world * Fix type * Renamed StandaloneFunction to StandaloneFunc * Revert formatting of items outside of wit-encoder * Removed serde * Not all types can be borrowed * Some small result related improvements * Added wit-encoder to workspace properly * Updated Cargo.lock * Added wit-encoder to publish list --------- Co-authored-by: Yosh --- Cargo.lock | 9 + Cargo.toml | 4 + ci/publish.rs | 1 + crates/wit-encoder/Cargo.toml | 15 + crates/wit-encoder/src/docs.rs | 37 +++ crates/wit-encoder/src/enum_.rs | 59 ++++ crates/wit-encoder/src/flags.rs | 62 ++++ crates/wit-encoder/src/function.rs | 168 +++++++++++ crates/wit-encoder/src/ident.rs | 40 +++ crates/wit-encoder/src/interface.rs | 80 +++++ crates/wit-encoder/src/lib.rs | 36 +++ crates/wit-encoder/src/package.rs | 120 ++++++++ crates/wit-encoder/src/record.rs | 64 ++++ crates/wit-encoder/src/render.rs | 50 +++ crates/wit-encoder/src/resource.rs | 96 ++++++ crates/wit-encoder/src/result.rs | 59 ++++ crates/wit-encoder/src/tuple.rs | 33 ++ crates/wit-encoder/src/ty.rs | 420 ++++++++++++++++++++++++++ crates/wit-encoder/src/variant.rs | 28 ++ crates/wit-encoder/src/world.rs | 210 +++++++++++++ crates/wit-encoder/tests/functions.rs | 88 ++++++ crates/wit-encoder/tests/type_defs.rs | 246 +++++++++++++++ crates/wit-encoder/tests/world.rs | 56 ++++ 23 files changed, 1981 insertions(+) create mode 100644 crates/wit-encoder/Cargo.toml create mode 100644 crates/wit-encoder/src/docs.rs create mode 100644 crates/wit-encoder/src/enum_.rs create mode 100644 crates/wit-encoder/src/flags.rs create mode 100644 crates/wit-encoder/src/function.rs create mode 100644 crates/wit-encoder/src/ident.rs create mode 100644 crates/wit-encoder/src/interface.rs create mode 100644 crates/wit-encoder/src/lib.rs create mode 100644 crates/wit-encoder/src/package.rs create mode 100644 crates/wit-encoder/src/record.rs create mode 100644 crates/wit-encoder/src/render.rs create mode 100644 crates/wit-encoder/src/resource.rs create mode 100644 crates/wit-encoder/src/result.rs create mode 100644 crates/wit-encoder/src/tuple.rs create mode 100644 crates/wit-encoder/src/ty.rs create mode 100644 crates/wit-encoder/src/variant.rs create mode 100644 crates/wit-encoder/src/world.rs create mode 100644 crates/wit-encoder/tests/functions.rs create mode 100644 crates/wit-encoder/tests/type_defs.rs create mode 100644 crates/wit-encoder/tests/world.rs diff --git a/Cargo.lock b/Cargo.lock index 6d25cdff74..dd46f31d47 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1653,6 +1653,7 @@ dependencies = [ "wast", "wat", "wit-component", + "wit-encoder", "wit-parser 0.210.0", "wit-smith", ] @@ -2138,6 +2139,14 @@ dependencies = [ "wit-parser 0.210.0", ] +[[package]] +name = "wit-encoder" +version = "0.210.0" +dependencies = [ + "pretty_assertions", + "semver", +] + [[package]] name = "wit-parser" version = "0.202.0" diff --git a/Cargo.toml b/Cargo.toml index ed66749879..c3d369d3e6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -37,6 +37,7 @@ members = [ 'crates/fuzz-stats', 'crates/wasm-mutate-stats', 'fuzz', + 'crates/wit-encoder', 'crates/wit-parser/fuzz', 'crates/wit-component/dl', 'playground/component', @@ -94,6 +95,7 @@ wasmprinter = { version = "0.210.0", path = "crates/wasmprinter" } wast = { version = "210.0.0", path = "crates/wast" } wat = { version = "1.210.0", path = "crates/wat" } wit-component = { version = "0.210.0", path = "crates/wit-component" } +wit-encoder = { version = "0.210.0", path = "crates/wit-encoder" } wit-parser = { version = "0.210.0", path = "crates/wit-parser" } wit-smith = { version = "0.210.0", path = "crates/wit-smith" } @@ -140,6 +142,7 @@ cpp_demangle = { version = "0.4.0", optional = true } # Dependencies of `component` wit-component = { workspace = true, optional = true, features = ['dummy-module', 'wat', 'semver-check'] } +wit-encoder = { workspace = true, optional = true } wit-parser = { workspace = true, optional = true, features = ['decoding', 'wat', 'serde'] } wast = { workspace = true, optional = true } @@ -208,6 +211,7 @@ compose = ['wasm-compose', 'dep:wasmparser'] demangle = ['rustc-demangle', 'cpp_demangle', 'dep:wasmparser', 'wasm-encoder'] component = [ 'wit-component', + 'wit-encoder', 'wit-parser', 'dep:wast', 'wasm-encoder', diff --git a/ci/publish.rs b/ci/publish.rs index 8103ead618..60418a2982 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -28,6 +28,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wit-parser", "wasm-metadata", "wit-component", + "wit-encoder", "wasm-compose", "wit-smith", "wasm-tools", diff --git a/crates/wit-encoder/Cargo.toml b/crates/wit-encoder/Cargo.toml new file mode 100644 index 0000000000..ae5da01377 --- /dev/null +++ b/crates/wit-encoder/Cargo.toml @@ -0,0 +1,15 @@ +[package] +description = "A WIT encoder for Rust" +documentation = "https://docs.rs/wit-encoder" +edition.workspace = true +license = "Apache-2.0 WITH LLVM-exception" +name = "wit-encoder" +repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wit-encoder" +version.workspace = true + +[lints] +workspace = true + +[dependencies] +semver = { workspace = true } +pretty_assertions = { workspace = true } diff --git a/crates/wit-encoder/src/docs.rs b/crates/wit-encoder/src/docs.rs new file mode 100644 index 0000000000..137fd744e7 --- /dev/null +++ b/crates/wit-encoder/src/docs.rs @@ -0,0 +1,37 @@ +use std::fmt; + +use crate::{Render, RenderOpts}; + +/// Documentation +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Docs { + contents: String, +} + +impl Docs { + pub fn new(contents: impl Into) -> Self { + Self { + contents: contents.into(), + } + } +} + +impl From for Docs +where + S: Into, +{ + fn from(value: S) -> Self { + Self { + contents: value.into(), + } + } +} + +impl Render for Docs { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + for line in self.contents.lines() { + write!(f, "{}/// {}\n", opts.spaces(), line)?; + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/enum_.rs b/crates/wit-encoder/src/enum_.rs new file mode 100644 index 0000000000..80c0afc89b --- /dev/null +++ b/crates/wit-encoder/src/enum_.rs @@ -0,0 +1,59 @@ +use crate::{Docs, Ident}; + +/// A variant without a payload +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Enum { + pub(crate) cases: Vec, +} + +impl FromIterator for Enum +where + C: Into, +{ + fn from_iter>(iter: T) -> Self { + Self { + cases: iter.into_iter().map(|c| c.into()).collect(), + } + } +} + +impl Enum { + pub fn cases(&self) -> &[EnumCase] { + &self.cases + } + + pub fn cases_mut(&mut self) -> &mut Vec { + &mut self.cases + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct EnumCase { + pub(crate) name: Ident, + pub(crate) docs: Option, +} + +impl From for EnumCase +where + N: Into, +{ + fn from(value: N) -> Self { + Self { + name: value.into(), + docs: None, + } + } +} + +impl EnumCase { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + docs: None, + } + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} diff --git a/crates/wit-encoder/src/flags.rs b/crates/wit-encoder/src/flags.rs new file mode 100644 index 0000000000..81e2fd0f4a --- /dev/null +++ b/crates/wit-encoder/src/flags.rs @@ -0,0 +1,62 @@ +use crate::{ident::Ident, Docs}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Flags { + pub(crate) flags: Vec, +} + +impl Flags { + pub fn new(flags: impl IntoIterator>) -> Self { + Self { + flags: flags.into_iter().map(|f| f.into()).collect(), + } + } + + pub fn flags(&self) -> &[Flag] { + &self.flags + } + + pub fn flags_mut(&mut self) -> &mut Vec { + &mut self.flags + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Flag { + pub(crate) name: Ident, + pub(crate) docs: Option, +} + +impl Flag { + pub fn new(name: impl Into) -> Self { + Flag { + name: name.into(), + docs: None, + } + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +impl Into for (T,) +where + T: Into, +{ + fn into(self) -> Flag { + Flag::new(self.0) + } +} + +impl Into for (T, D) +where + T: Into, + D: Into, +{ + fn into(self) -> Flag { + let mut flag = Flag::new(self.0); + flag.docs(Some(self.1)); + flag + } +} diff --git a/crates/wit-encoder/src/function.rs b/crates/wit-encoder/src/function.rs new file mode 100644 index 0000000000..fa3e81e2f2 --- /dev/null +++ b/crates/wit-encoder/src/function.rs @@ -0,0 +1,168 @@ +use std::fmt::{self, Display}; + +use crate::{ident::Ident, Docs, Type}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +pub struct Params { + items: Vec<(Ident, Type)>, +} + +impl From<(N, Type)> for Params +where + N: Into, +{ + fn from(value: (N, Type)) -> Self { + Self { + items: vec![(value.0.into(), value.1)], + } + } +} + +impl FromIterator<(N, Type)> for Params +where + N: Into, +{ + fn from_iter>(iter: T) -> Self { + Self { + items: iter.into_iter().map(|(n, t)| (n.into(), t)).collect(), + } + } +} + +impl Display for Params { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let mut peekable = self.items.iter().peekable(); + while let Some((name, type_)) = peekable.next() { + write!(f, "{}: {}", name, type_)?; + if peekable.peek().is_some() { + write!(f, ", ")?; + } + } + Ok(()) + } +} + +impl Params { + pub fn empty() -> Self { + Self::default() + } + + pub fn items(&self) -> &Vec<(Ident, Type)> { + &self.items + } + + pub fn items_mut(&mut self) -> &mut Vec<(Ident, Type)> { + &mut self.items + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Results { + Named(Params), + Anon(Type), +} + +impl Display for Results { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Results::Anon(type_) => type_.fmt(f)?, + Results::Named(vals) => { + if !vals.items.is_empty() { + write!(f, "(")?; + let mut peekable = vals.items.iter().peekable(); + while let Some((name, type_)) = peekable.next() { + write!(f, "{}: {}", name, type_)?; + if peekable.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, ")")?; + } + } + }; + Ok(()) + } +} + +impl Default for Results { + fn default() -> Self { + Results::empty() + } +} + +impl From for Results { + fn from(value: Type) -> Self { + Results::Anon(value) + } +} + +impl FromIterator<(N, Type)> for Results +where + N: Into, +{ + fn from_iter>(iter: T) -> Self { + Results::Named(Params::from_iter(iter)) + } +} + +impl Results { + // For the common case of an empty results list. + pub fn empty() -> Results { + Results::Named(Default::default()) + } + + pub fn anon(type_: Type) -> Results { + Results::Anon(type_) + } + + pub fn named(types: impl IntoIterator, Type)>) -> Results { + Results::Named( + types + .into_iter() + .map(|(name, ty)| (name.into(), ty)) + .collect(), + ) + } + + pub fn is_empty(&self) -> bool { + self.len() == 0 + } + + pub fn len(&self) -> usize { + match self { + Results::Named(params) => params.items().len(), + Results::Anon(_) => 1, + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct StandaloneFunc { + pub(crate) name: Ident, + pub(crate) params: Params, + pub(crate) results: Results, + pub(crate) docs: Option, +} + +impl StandaloneFunc { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + params: Params::empty(), + results: Results::empty(), + docs: None, + } + } + + pub fn params(&mut self, params: impl Into) { + self.params = params.into(); + } + + pub fn results(&mut self, results: impl Into) { + self.results = results.into(); + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} diff --git a/crates/wit-encoder/src/ident.rs b/crates/wit-encoder/src/ident.rs new file mode 100644 index 0000000000..0d9b86a480 --- /dev/null +++ b/crates/wit-encoder/src/ident.rs @@ -0,0 +1,40 @@ +use std::{borrow::Cow, fmt}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Ident(Cow<'static, str>); + +impl Ident { + pub fn new(s: impl Into>) -> Self { + let s: Cow<'static, str> = s.into(); + if is_keyword(&s) { + Self(Cow::Owned(format!("%{}", s))) + } else { + Self(s) + } + } +} + +impl From for Ident +where + S: Into>, +{ + fn from(value: S) -> Self { + Self::new(value) + } +} + +impl fmt::Display for Ident { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +fn is_keyword(name: &str) -> bool { + match name { + "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" | "float32" | "float64" + | "char" | "bool" | "string" | "tuple" | "list" | "option" | "result" | "use" | "type" + | "resource" | "func" | "record" | "enum" | "flags" | "variant" | "static" + | "interface" | "world" | "import" | "export" | "package" => true, + _ => false, + } +} diff --git a/crates/wit-encoder/src/interface.rs b/crates/wit-encoder/src/interface.rs new file mode 100644 index 0000000000..943a36d04f --- /dev/null +++ b/crates/wit-encoder/src/interface.rs @@ -0,0 +1,80 @@ +use std::fmt; + +use crate::{Docs, Ident, Render, RenderOpts, StandaloneFunc, TypeDef}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Interface { + /// Name of this interface. + pub(crate) name: Ident, + + // Interface items + pub(crate) items: Vec, + + /// Documentation associated with this interface. + pub(crate) docs: Option, +} + +impl Interface { + /// Create a new instance of `Interface`. + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + items: vec![], + docs: None, + } + } + + /// Add a `TypeDef` to the interface + pub fn type_def(&mut self, type_def: TypeDef) { + self.items.push(InterfaceItem::TypeDef(type_def)); + } + + /// Add an `Function` to the interface + pub fn function(&mut self, function: StandaloneFunc) { + self.items.push(InterfaceItem::Function(function)); + } + + pub fn items(&self) -> &[InterfaceItem] { + &self.items + } + + pub fn functions_mut(&mut self) -> &mut Vec { + &mut self.items + } + + /// Set the documentation of this interface. + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum InterfaceItem { + TypeDef(TypeDef), + Function(StandaloneFunc), +} + +pub type InterfaceItems = Vec; + +impl Render for InterfaceItems { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + for item in self { + match item { + InterfaceItem::TypeDef(type_def) => { + type_def.render(f, opts)?; + } + InterfaceItem::Function(func) => { + if let Some(docs) = &func.docs { + docs.render(f, opts)?; + } + write!(f, "{}{}: func({})", opts.spaces(), func.name, func.params,)?; + if !func.results.is_empty() { + write!(f, " -> {}", func.results)?; + } + write!(f, ";\n")?; + } + } + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/lib.rs b/crates/wit-encoder/src/lib.rs new file mode 100644 index 0000000000..908904da46 --- /dev/null +++ b/crates/wit-encoder/src/lib.rs @@ -0,0 +1,36 @@ +//! A WIT encoder for Rust. +//! +//! This crate is modeled after the `wasm-encoder` crate but is used to encode +//! WIT documents instead of WebAssembly modules. + +mod docs; +mod enum_; +mod flags; +mod function; +mod ident; +mod interface; +mod package; +mod record; +mod render; +mod resource; +mod result; +mod tuple; +mod ty; +mod variant; +mod world; + +pub use docs::*; +pub use enum_::*; +pub use flags::*; +pub use function::*; +pub use ident::*; +pub use interface::*; +pub use package::*; +pub use record::*; +pub use render::*; +pub use resource::*; +pub use result::*; +pub use tuple::*; +pub use ty::*; +pub use variant::*; +pub use world::*; diff --git a/crates/wit-encoder/src/package.rs b/crates/wit-encoder/src/package.rs new file mode 100644 index 0000000000..d25abfc4e8 --- /dev/null +++ b/crates/wit-encoder/src/package.rs @@ -0,0 +1,120 @@ +use std::fmt; + +use semver::Version; + +use crate::{ident::Ident, Interface, Render, RenderOpts, World}; + +/// A WIT package. +/// +/// A package is a collection of interfaces and worlds. Packages additionally +/// have a unique identifier that affects generated components and uniquely +/// identifiers this particular package. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Package { + /// A unique name corresponding to this package. + name: PackageName, + + /// World items + items: Vec, +} + +impl Package { + /// Create a new instance of `Package`. + pub fn new(name: PackageName) -> Self { + Self { + name, + items: vec![], + } + } + + /// Add an `Interface` to the package + pub fn interface(&mut self, interface: Interface) { + self.items.push(PackageItem::Interface(interface)) + } + + /// Add a `World` to the package + pub fn world(&mut self, world: World) { + self.items.push(PackageItem::World(world)) + } +} + +impl Render for Package { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + write!(f, "{}package {};\n", opts.spaces(), self.name)?; + write!(f, "\n")?; + for item in &self.items { + match item { + PackageItem::Interface(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + write!(f, "{}interface {} {{\n", opts.spaces(), interface.name)?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } + PackageItem::World(world) => { + world.render(f, opts)?; + } + } + } + Ok(()) + } +} + +impl fmt::Display for Package { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.render(f, &RenderOpts::default()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum PackageItem { + Interface(Interface), + World(World), +} + +/// A structure used to keep track of the name of a package, containing optional +/// information such as a namespace and version information. +/// +/// This is directly encoded as an "ID" in the binary component representation +/// with an interfaced tacked on as well. +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct PackageName { + /// A namespace such as `wasi` in `wasi:foo/bar` + namespace: String, + /// The kebab-name of this package, which is always specified. + name: Ident, + /// Optional major/minor version information. + version: Option, +} + +impl PackageName { + /// Create a new instance of `PackageName` + pub fn new( + namespace: impl Into, + name: impl Into, + version: Option, + ) -> Self { + Self { + namespace: namespace.into(), + name: name.into(), + version, + } + } +} + +impl From for String { + fn from(name: PackageName) -> String { + name.to_string() + } +} + +impl fmt::Display for PackageName { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}:{}", self.namespace, self.name)?; + if let Some(version) = &self.version { + write!(f, "@{version}")?; + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/record.rs b/crates/wit-encoder/src/record.rs new file mode 100644 index 0000000000..4280ca85d9 --- /dev/null +++ b/crates/wit-encoder/src/record.rs @@ -0,0 +1,64 @@ +use crate::{ident::Ident, Docs, Type}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Record { + pub(crate) fields: Vec, +} + +impl Record { + pub fn new(fields: impl IntoIterator>) -> Self { + Self { + fields: fields.into_iter().map(|f| f.into()).collect(), + } + } + + pub fn fields(&self) -> &[Field] { + &self.fields + } + + pub fn fields_mut(&mut self) -> &mut Vec { + &mut self.fields + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Field { + pub(crate) name: Ident, + pub(crate) ty: Type, + pub(crate) docs: Option, +} + +impl Field { + pub fn new(name: impl Into, ty: Type) -> Self { + Self { + name: name.into(), + ty, + docs: None, + } + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +impl Into for (N, Type) +where + N: Into, +{ + fn into(self) -> Field { + Field::new(self.0, self.1) + } +} + +impl Into for (N, Type, D) +where + N: Into, + D: Into, +{ + fn into(self) -> Field { + let mut field = Field::new(self.0, self.1); + field.docs(Some(self.2.into())); + field + } +} diff --git a/crates/wit-encoder/src/render.rs b/crates/wit-encoder/src/render.rs new file mode 100644 index 0000000000..2339e32077 --- /dev/null +++ b/crates/wit-encoder/src/render.rs @@ -0,0 +1,50 @@ +use std::fmt; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct RenderOpts { + /// width of each indent + pub indent_width: usize, + /// current indent depth + pub ident_count: usize, +} + +impl Default for RenderOpts { + fn default() -> Self { + Self { + indent_width: 4, + ident_count: 0, + } + } +} + +impl RenderOpts { + /// Indent + /// + /// This will clone Self, and increment self.ident_count. + pub fn indent(&self) -> Self { + Self { + indent_width: self.indent_width, + ident_count: self.ident_count + 1, + } + } + + /// Outdent + /// + /// This will clone Self, and decrement self.ident_count. + pub fn outdent(&self) -> Self { + Self { + indent_width: self.indent_width, + ident_count: self.ident_count - 1, + } + } + + /// Get the actual characters + pub fn spaces(&self) -> String { + let space_count = self.indent_width * self.ident_count; + " ".repeat(space_count) + } +} + +pub trait Render { + fn render(&self, f: &mut fmt::Formatter<'_>, options: &RenderOpts) -> fmt::Result; +} diff --git a/crates/wit-encoder/src/resource.rs b/crates/wit-encoder/src/resource.rs new file mode 100644 index 0000000000..0d78c4cefd --- /dev/null +++ b/crates/wit-encoder/src/resource.rs @@ -0,0 +1,96 @@ +use crate::{ident::Ident, Docs, Params, Results}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Resource { + pub funcs: Vec, +} + +impl Resource { + pub fn empty() -> Self { + Self { funcs: vec![] } + } + + pub fn func(&mut self, func: ResourceFunc) { + self.funcs.push(func); + } + + pub fn funcs(&self) -> &[ResourceFunc] { + &self.funcs + } + + pub fn funcs_mut(&mut self) -> &mut Vec { + &mut self.funcs + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct ResourceFunc { + pub(crate) kind: ResourceFuncKind, + pub(crate) params: Params, + pub(crate) docs: Option, +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum ResourceFuncKind { + Method(Ident, Results), + Static(Ident, Results), + Constructor, +} + +impl ResourceFunc { + pub fn method(name: impl Into) -> Self { + Self { + kind: ResourceFuncKind::Method(name.into(), Results::empty()), + params: Params::empty(), + docs: None, + } + } + + pub fn static_(name: impl Into) -> Self { + Self { + kind: ResourceFuncKind::Static(name.into(), Results::empty()), + params: Params::empty(), + docs: None, + } + } + + pub fn constructor() -> Self { + Self { + kind: ResourceFuncKind::Constructor, + params: Params::empty(), + docs: None, + } + } + + pub fn name(&mut self, name: impl Into) { + match &self.kind { + ResourceFuncKind::Method(_, results) => { + self.kind = ResourceFuncKind::Method(name.into(), results.clone()) + } + ResourceFuncKind::Static(_, results) => { + self.kind = ResourceFuncKind::Static(name.into(), results.clone()) + } + ResourceFuncKind::Constructor => panic!("constructors cannot have a name"), + } + } + + pub fn params(&mut self, params: impl Into) { + self.params = params.into(); + } + + pub fn results(&mut self, results: impl Into) { + match &self.kind { + ResourceFuncKind::Method(name, _) => { + self.kind = ResourceFuncKind::Method(name.clone(), results.into()) + } + ResourceFuncKind::Static(name, _) => { + self.kind = ResourceFuncKind::Static(name.clone(), results.into()) + } + ResourceFuncKind::Constructor => panic!("constructors cannot have results"), + } + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} diff --git a/crates/wit-encoder/src/result.rs b/crates/wit-encoder/src/result.rs new file mode 100644 index 0000000000..de4dc9cbfa --- /dev/null +++ b/crates/wit-encoder/src/result.rs @@ -0,0 +1,59 @@ +use std::fmt::Display; + +use crate::Type; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Result_ { + ok: Option, + err: Option, +} + +impl Result_ { + pub fn ok(type_: Type) -> Self { + Self { + ok: Some(type_), + err: None, + } + } + pub fn err(type_: Type) -> Self { + Self { + ok: None, + err: Some(type_), + } + } + pub fn both(ok: Type, err: Type) -> Self { + Self { + ok: Some(ok), + err: Some(err), + } + } + pub fn empty() -> Self { + Self { + ok: None, + err: None, + } + } + pub fn is_empty(&self) -> bool { + self.ok.is_none() && self.err.is_none() + } +} + +impl Display for Result_ { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "result")?; + if !self.is_empty() { + write!(f, "<")?; + if let Some(type_) = &self.ok { + type_.fmt(f)?; + } else { + write!(f, "_")?; + } + if let Some(type_) = &self.err { + write!(f, ", ")?; + type_.fmt(f)?; + } + write!(f, ">")?; + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/tuple.rs b/crates/wit-encoder/src/tuple.rs new file mode 100644 index 0000000000..77554ba574 --- /dev/null +++ b/crates/wit-encoder/src/tuple.rs @@ -0,0 +1,33 @@ +use std::fmt::Display; + +use crate::Type; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Tuple { + pub(crate) types: Vec, +} + +impl Tuple { + pub fn types(&self) -> &[Type] { + &self.types + } + + pub fn types_mut(&mut self) -> &mut Vec { + &mut self.types + } +} + +impl Display for Tuple { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "tuple<")?; + let mut peekable = self.types.iter().peekable(); + while let Some(type_) = peekable.next() { + type_.fmt(f)?; + if peekable.peek().is_some() { + write!(f, ", ")?; + } + } + write!(f, ">")?; + Ok(()) + } +} diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs new file mode 100644 index 0000000000..dea714e4ff --- /dev/null +++ b/crates/wit-encoder/src/ty.rs @@ -0,0 +1,420 @@ +use std::fmt::{self, Display}; + +use crate::{ + ident::Ident, Docs, Enum, EnumCase, Field, Flag, Flags, Record, Render, RenderOpts, Resource, + ResourceFunc, Result_, Tuple, Variant, +}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum Type { + Bool, + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + F32, + F64, + Char, + String, + Borrow(Ident), + Option(Box), + Result(Box), + List(Box), + Tuple(Tuple), + Named(Ident), +} + +impl Type { + pub fn borrow(name: impl Into) -> Self { + Type::Borrow(name.into()) + } + pub fn option(type_: Type) -> Self { + Type::Option(Box::new(type_)) + } + pub fn result(result: Result_) -> Self { + Type::Result(Box::new(result)) + } + pub fn result_ok(type_: Type) -> Self { + Type::Result(Box::new(Result_::ok(type_))) + } + pub fn result_err(type_: Type) -> Self { + Type::Result(Box::new(Result_::err(type_))) + } + pub fn result_both(ok: Type, err: Type) -> Self { + Type::Result(Box::new(Result_::both(ok, err))) + } + pub fn result_empty() -> Self { + Type::Result(Box::new(Result_::empty())) + } + pub fn list(type_: Type) -> Self { + Type::List(Box::new(type_)) + } + pub fn tuple(types: impl IntoIterator) -> Self { + Type::Tuple(Tuple { + types: types.into_iter().collect(), + }) + } + pub fn named(name: impl Into) -> Self { + Type::Named(name.into()) + } +} +impl From for Type { + fn from(value: Result_) -> Self { + Self::result(value) + } +} +impl From for Type { + fn from(value: Tuple) -> Self { + Type::Tuple(value) + } +} +impl From for Type { + fn from(value: Ident) -> Self { + Self::named(value) + } +} + +impl Display for Type { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Type::Bool => write!(f, "bool"), + Type::U8 => write!(f, "u8"), + Type::U16 => write!(f, "u16"), + Type::U32 => write!(f, "u32"), + Type::U64 => write!(f, "u64"), + Type::S8 => write!(f, "s8"), + Type::S16 => write!(f, "s16"), + Type::S32 => write!(f, "s32"), + Type::S64 => write!(f, "s64"), + Type::F32 => write!(f, "f32"), + Type::F64 => write!(f, "f64"), + Type::Char => write!(f, "char"), + Type::String => write!(f, "string"), + Type::Named(name) => write!(f, "{}", name), + Type::Borrow(type_) => { + write!(f, "borrow<{type_}>") + } + Type::Option(type_) => { + write!(f, "option<{type_}>") + } + Type::Result(result) => result.fmt(f), + Type::List(type_) => { + write!(f, "list<{type_}>") + } + Type::Tuple(tuple) => tuple.fmt(f), + } + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct VariantCase { + name: Ident, + ty: Option, + docs: Option, +} + +impl VariantCase { + pub fn empty(name: impl Into) -> Self { + Self { + name: name.into(), + ty: None, + docs: None, + } + } + pub fn value(name: impl Into, ty: Type) -> Self { + Self { + name: name.into(), + ty: Some(ty), + docs: None, + } + } + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +impl Into for (N,) +where + N: Into, +{ + fn into(self) -> VariantCase { + VariantCase::empty(self.0) + } +} + +impl Into for (N, Type) +where + N: Into, +{ + fn into(self) -> VariantCase { + VariantCase::value(self.0, self.1) + } +} + +impl Into for (N, Type, D) +where + N: Into, + D: Into, +{ + fn into(self) -> VariantCase { + let mut field = VariantCase::value(self.0, self.1); + field.docs(Some(self.2.into())); + field + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct TypeDef { + name: Ident, + kind: TypeDefKind, + docs: Option, +} + +impl TypeDef { + pub fn new(name: impl Into, kind: TypeDefKind) -> Self { + TypeDef { + name: name.into(), + kind, + docs: None, + } + } + + pub fn record( + name: impl Into, + fields: impl IntoIterator>, + ) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::record(fields), + docs: None, + } + } + + pub fn resource( + name: impl Into, + funcs: impl IntoIterator>, + ) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::resource(funcs), + docs: None, + } + } + + pub fn flags(name: impl Into, flags: impl IntoIterator>) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::flags(flags), + docs: None, + } + } + + pub fn variant( + name: impl Into, + cases: impl IntoIterator>, + ) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::variant(cases), + docs: None, + } + } + + pub fn enum_( + name: impl Into, + cases: impl IntoIterator>, + ) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::enum_(cases), + docs: None, + } + } + + pub fn type_(name: impl Into, type_: Type) -> Self { + TypeDef { + name: name.into(), + kind: TypeDefKind::type_(type_), + docs: None, + } + } + + pub fn name(&self) -> &Ident { + &self.name + } + + pub fn name_mut(&mut self) -> &mut Ident { + &mut self.name + } + + pub fn kind(&self) -> &TypeDefKind { + &self.kind + } + + pub fn kind_mut(&mut self) -> &mut TypeDefKind { + &mut self.kind + } + + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum TypeDefKind { + Record(Record), + Resource(Resource), + Flags(Flags), + Variant(Variant), + Enum(Enum), + Type(Type), +} + +impl TypeDefKind { + pub fn record(fields: impl IntoIterator>) -> Self { + Self::Record(Record { + fields: fields.into_iter().map(|c| c.into()).collect(), + }) + } + + pub fn resource(funcs: impl IntoIterator>) -> Self { + Self::Resource(Resource { + funcs: funcs.into_iter().map(|f| f.into()).collect(), + }) + } + + pub fn flags(flags: impl IntoIterator>) -> Self { + Self::Flags(Flags { + flags: flags.into_iter().map(|f| f.into()).collect(), + }) + } + + pub fn variant(cases: impl IntoIterator>) -> Self { + Self::Variant(Variant { + cases: cases.into_iter().map(|c| c.into()).collect(), + }) + } + + pub fn enum_(cases: impl IntoIterator>) -> Self { + Self::Enum(Enum { + cases: cases.into_iter().map(|c| c.into()).collect(), + }) + } + + pub fn type_(type_: Type) -> Self { + Self::Type(type_) + } +} + +impl Render for TypeDef { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + match &self.kind { + TypeDefKind::Record(record) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}record {} {{\n", opts.spaces(), self.name)?; + for field in &record.fields { + let opts = opts.indent(); + if let Some(docs) = &field.docs { + docs.render(f, &opts)?; + } + write!(f, "{}{}: {},\n", opts.spaces(), field.name, field.ty)?; + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Resource(resource) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}resource {} {{\n", opts.spaces(), self.name)?; + for func in &resource.funcs { + let opts = opts.indent(); + if let Some(docs) = &func.docs { + docs.render(f, &opts)?; + } + match &func.kind { + crate::ResourceFuncKind::Method(name, results) => { + write!(f, "{}{}: func({})", opts.spaces(), name, func.params)?; + if !results.is_empty() { + write!(f, " -> {}", results)?; + } + write!(f, ";\n")?; + } + crate::ResourceFuncKind::Static(name, results) => { + write!(f, "{}{}: static func({})", opts.spaces(), name, func.params)?; + if !results.is_empty() { + write!(f, " -> {}", results)?; + } + write!(f, ";\n")?; + } + crate::ResourceFuncKind::Constructor => { + write!(f, "{}constructor({});\n", opts.spaces(), func.params)?; + } + } + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Flags(flags) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}flags {} {{\n", opts.spaces(), self.name)?; + for flag in &flags.flags { + let opts = opts.indent(); + if let Some(docs) = &flag.docs { + docs.render(f, &opts)?; + } + write!(f, "{}{},\n", opts.spaces(), flag.name)?; + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Variant(variant) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}variant {} {{\n", opts.spaces(), self.name)?; + for case in &variant.cases { + let opts = opts.indent(); + if let Some(docs) = &case.docs { + docs.render(f, &opts)?; + } + match &case.ty { + Some(type_) => { + write!(f, "{}{}({}),\n", opts.spaces(), case.name, type_)?; + } + None => { + write!(f, "{}{},\n", opts.spaces(), case.name)?; + } + } + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Enum(enum_) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}enum {} {{\n", opts.spaces(), self.name)?; + for case in &enum_.cases { + let opts = opts.indent(); + if let Some(docs) = &case.docs { + docs.render(f, &opts)?; + } + write!(f, "{}{},\n", opts.spaces(), case.name)?; + } + write!(f, "{}}}\n", opts.spaces())?; + } + TypeDefKind::Type(type_) => { + if let Some(docs) = &self.docs { + docs.render(f, opts)?; + } + write!(f, "{}type {} = {};\n", opts.spaces(), self.name, type_)?; + } + } + Ok(()) + } +} diff --git a/crates/wit-encoder/src/variant.rs b/crates/wit-encoder/src/variant.rs new file mode 100644 index 0000000000..dd52ab273b --- /dev/null +++ b/crates/wit-encoder/src/variant.rs @@ -0,0 +1,28 @@ +use crate::VariantCase; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Variant { + pub(crate) cases: Vec, +} + +impl Variant { + pub fn cases(&self) -> &[VariantCase] { + &self.cases + } + + pub fn cases_mut(&mut self) -> &mut Vec { + &mut self.cases + } +} + +impl From for Variant +where + I: IntoIterator, + C: Into, +{ + fn from(value: I) -> Self { + Self { + cases: value.into_iter().map(|c| c.into()).collect(), + } + } +} diff --git a/crates/wit-encoder/src/world.rs b/crates/wit-encoder/src/world.rs new file mode 100644 index 0000000000..867d550550 --- /dev/null +++ b/crates/wit-encoder/src/world.rs @@ -0,0 +1,210 @@ +use std::fmt; + +use crate::{ident::Ident, Docs, Interface, Render, RenderOpts, StandaloneFunc}; + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct World { + /// The WIT identifier name of this world. + name: Ident, + + /// All imported and exported items into this world. + items: Vec, + + /// Documentation associated with this world declaration. + docs: Option, +} + +impl World { + /// Create a new world. + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + items: vec![], + docs: None, + } + } + + /// Add a `name` to the world + pub fn name(&mut self, name: impl Into) { + self.name = name.into(); + } + + /// Add an import or export to the world + pub fn item(&mut self, item: WorldItem) { + self.items.push(item); + } + + pub fn inline_interface_import(&mut self, value: Interface) { + self.item(WorldItem::inline_interface_import(value)); + } + pub fn inline_interface_export(&mut self, value: Interface) { + self.item(WorldItem::inline_interface_export(value)); + } + pub fn named_interface_import(&mut self, value: impl Into) { + self.item(WorldItem::named_interface_import(value)); + } + pub fn named_interface_export(&mut self, value: impl Into) { + self.item(WorldItem::named_interface_export(value)); + } + pub fn function_import(&mut self, value: StandaloneFunc) { + self.item(WorldItem::function_import(value)); + } + pub fn function_export(&mut self, value: StandaloneFunc) { + self.item(WorldItem::function_export(value)); + } + + /// Set the documentation + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} + +impl Render for World { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + fn import(f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + write!(f, "{}import ", opts.spaces()) + } + fn export(f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { + write!(f, "{}export ", opts.spaces()) + } + fn render_function( + f: &mut fmt::Formatter<'_>, + _opts: &RenderOpts, + func: &StandaloneFunc, + ) -> fmt::Result { + write!(f, "{}: func({})", func.name, func.params)?; + if !func.results.is_empty() { + write!(f, " -> {}", func.results)?; + } + write!(f, ";\n")?; + Ok(()) + } + write!(f, "{}world {} {{\n", opts.spaces(), self.name)?; + let opts = &opts.indent(); + for item in &self.items { + match item { + WorldItem::InlineInterfaceImport(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + import(f, opts)?; + write!(f, "{}: interface {{\n", interface.name)?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } + WorldItem::InlineInterfaceExport(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + export(f, opts)?; + write!(f, "{}: interface {{\n", interface.name)?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } + WorldItem::NamedInterfaceImport(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + import(f, opts)?; + write!(f, "{};\n", interface.name)?; + } + WorldItem::NamedInterfaceExport(interface) => { + if let Some(docs) = &interface.docs { + docs.render(f, opts)?; + } + export(f, opts)?; + write!(f, "{};\n", interface.name)?; + } + WorldItem::FunctionImport(function) => { + if let Some(docs) = &function.docs { + docs.render(f, opts)?; + } + import(f, opts)?; + render_function(f, opts, function)?; + } + WorldItem::FunctionExport(function) => { + if let Some(docs) = &function.docs { + docs.render(f, opts)?; + } + export(f, opts)?; + render_function(f, opts, function)?; + } + } + } + let opts = &opts.outdent(); + write!(f, "{}}}\n", opts.spaces())?; + Ok(()) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub enum WorldItem { + /// An imported inline interface + InlineInterfaceImport(Interface), + + /// An exported inline interface + InlineInterfaceExport(Interface), + + /// Refers to a named interface import + NamedInterfaceImport(WorldNamedInterface), + + /// Refers to a named interface export + NamedInterfaceExport(WorldNamedInterface), + + /// A function is being directly imported from this world. + FunctionImport(StandaloneFunc), + + /// A function is being directly exported from this world. + FunctionExport(StandaloneFunc), +} + +impl WorldItem { + pub fn inline_interface_import(value: Interface) -> Self { + Self::InlineInterfaceImport(value) + } + pub fn inline_interface_export(value: Interface) -> Self { + Self::InlineInterfaceExport(value) + } + pub fn named_interface_import(value: impl Into) -> Self { + Self::NamedInterfaceImport(value.into()) + } + pub fn named_interface_export(value: impl Into) -> Self { + Self::NamedInterfaceExport(value.into()) + } + pub fn function_import(value: StandaloneFunc) -> Self { + Self::FunctionImport(value) + } + pub fn function_export(value: StandaloneFunc) -> Self { + Self::FunctionExport(value) + } +} + +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct WorldNamedInterface { + /// Name of this interface. + pub(crate) name: Ident, + + /// Documentation associated with this interface. + pub(crate) docs: Option, +} + +impl From for WorldNamedInterface +where + N: Into, +{ + fn from(name: N) -> Self { + Self::new(name) + } +} + +impl WorldNamedInterface { + pub fn new(name: impl Into) -> Self { + Self { + name: name.into(), + docs: None, + } + } + pub fn docs(&mut self, docs: Option>) { + self.docs = docs.map(|d| d.into()); + } +} diff --git a/crates/wit-encoder/tests/functions.rs b/crates/wit-encoder/tests/functions.rs new file mode 100644 index 0000000000..fd5c5e8c6c --- /dev/null +++ b/crates/wit-encoder/tests/functions.rs @@ -0,0 +1,88 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Params, Result_, Results, StandaloneFunc, Type}; + +const PACKAGE: &str = "package foo:functions; + +interface functions { + f1: func(); + f2: func(a: u32); + f3: func() -> u32; + /// this is a documentation comment + /// for the f4 function + f4: func() -> tuple; + f5: func(a: f32, b: f32) -> tuple; + f6: func(a: option) -> result; + f7: func() -> (u: u32, f: f32); + f8: func() -> (u: u32); + f9: func() -> result; + f10: func() -> result<_, f32>; + f11: func() -> result; +} +"; + +#[test] +fn smoke() { + let name = wit_encoder::PackageName::new("foo", "functions", None); + let mut package = wit_encoder::Package::new(name); + + package.interface({ + let mut interface = wit_encoder::Interface::new("functions"); + interface.function(StandaloneFunc::new("f1")); + interface.function({ + let mut func = StandaloneFunc::new("f2"); + func.params(Params::from_iter([("a", Type::U32)])); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f3"); + func.results(Type::U32); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f4"); + func.results(Type::tuple(vec![Type::U32, Type::U32])); + func.docs(Some("this is a documentation comment\nfor the f4 function")); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f5"); + func.params(Params::from_iter([("a", Type::F32), ("b", Type::F32)])); + func.results(Type::tuple(vec![Type::U32, Type::U32])); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f6"); + func.params(Params::from_iter([("a", Type::option(Type::U32))])); + func.results(Type::result(Result_::both(Type::U32, Type::F32))); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f7"); + func.results(Results::named(vec![("u", Type::U32), ("f", Type::F32)])); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f8"); + func.results(Results::named(vec![("u", Type::U32)])); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f9"); + func.results(Type::result(Result_::ok(Type::F32))); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f10"); + func.results(Type::result(Result_::err(Type::F32))); + func + }); + interface.function({ + let mut func = StandaloneFunc::new("f11"); + func.results(Type::result(Result_::empty())); + func + }); + interface + }); + + assert_eq!(PACKAGE, package.to_string()); +} diff --git a/crates/wit-encoder/tests/type_defs.rs b/crates/wit-encoder/tests/type_defs.rs new file mode 100644 index 0000000000..c4723f9ea3 --- /dev/null +++ b/crates/wit-encoder/tests/type_defs.rs @@ -0,0 +1,246 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{ + Field, Flag, Params, ResourceFunc, Result_, Results, Type, TypeDef, TypeDefKind, VariantCase, +}; + +const PACKAGE: &str = "package wit-encoder:tests; + +/// interface documentation +interface type-defs { + type t1 = u8; + type t2 = u16; + type t3 = u32; + type t4 = u64; + type t5 = s8; + type t6 = s16; + type t7 = s32; + type t8 = s64; + type t9a = f32; + type t9b = f32; + type t10a = f64; + type t10b = f64; + type t11 = char; + type t12 = list; + type t13 = string; + type t14 = option; + type t15 = result; + type t16 = result<_, u32>; + type t17 = result; + /// this is a documentation comment + type t18 = result; + record t20 { + } + record t21 { + a: u32, + } + record t22 { + a: u32, + } + record t23 { + a: u32, + b: u64, + } + record t24 { + a: u32, + b: u64, + } + record t25 { + x: u32, + } + record %record { + a: u32, + } + type t26 = tuple<>; + type t27 = tuple; + type t29 = tuple; + flags t30 { + } + flags t31 { + /// option a + a, + /// option b + b, + /// option c + c, + } + flags t32 { + a, + b, + c, + } + variant t33 { + a, + } + variant t34 { + a, + b, + } + variant t35 { + a, + b, + } + variant t36 { + a, + b(u32), + } + variant t37 { + a, + b(option), + } + enum t41 { + a, + b, + c, + } + enum t42 { + a, + b, + c, + } + type t43 = bool; + type t44 = string; + type t45 = list>>; + type t46 = t44; + type foo = bar; + type bar = u32; + resource t50 { + } + resource t51 { + /// create a new t51 + constructor(a: u32); + /// set a + set-a: func(a: u32); + /// get a + get-a: func() -> u32; + /// do b + b: static func(); + } +} +"; + +#[test] +fn types() { + let name = wit_encoder::PackageName::new("wit-encoder", "tests", None); + let mut package = wit_encoder::Package::new(name); + package.interface({ + let mut interface = wit_encoder::Interface::new("type-defs"); + interface.docs(Some("interface documentation")); + interface.type_def(TypeDef::type_("t1", Type::U8)); + interface.type_def(TypeDef::type_("t2", Type::U16)); + interface.type_def(TypeDef::type_("t3", Type::U32)); + interface.type_def(TypeDef::type_("t4", Type::U64)); + interface.type_def(TypeDef::type_("t5", Type::S8)); + interface.type_def(TypeDef::type_("t6", Type::S16)); + interface.type_def(TypeDef::type_("t7", Type::S32)); + interface.type_def(TypeDef::type_("t8", Type::S64)); + interface.type_def(TypeDef::type_("t9a", Type::F32)); + interface.type_def(TypeDef::type_("t9b", Type::F32)); + interface.type_def(TypeDef::type_("t10a", Type::F64)); + interface.type_def(TypeDef::type_("t10b", Type::F64)); + interface.type_def(TypeDef::type_("t11", Type::Char)); + interface.type_def(TypeDef::type_("t12", Type::list(Type::Char))); + interface.type_def(TypeDef::type_("t13", Type::String)); + interface.type_def(TypeDef::type_("t14", Type::option(Type::U32))); + interface.type_def(TypeDef::type_( + "t15", + Type::result_both(Type::U32, Type::U32), + )); + interface.type_def(TypeDef::type_("t16", Type::result(Result_::err(Type::U32)))); + interface.type_def(TypeDef::type_("t17", Type::result(Result_::ok(Type::U32)))); + interface.type_def({ + let mut type_ = TypeDef::type_("t18", Type::result(Result_::empty())); + type_.docs(Some("this is a documentation comment")); + type_ + }); + + interface.type_def(TypeDef::record("t20", Vec::::new())); + interface.type_def(TypeDef::record("t21", [Field::new("a", Type::U32)])); + interface.type_def(TypeDef::record("t22", [Field::new("a", Type::U32)])); + interface.type_def(TypeDef::record( + "t23", + [Field::new("a", Type::U32), Field::new("b", Type::U64)], + )); + interface.type_def(TypeDef::record( + "t24", + [Field::new("a", Type::U32), Field::new("b", Type::U64)], + )); + interface.type_def(TypeDef::record("t25", [Field::new("x", Type::U32)])); + interface.type_def(TypeDef::record("record", [Field::new("a", Type::U32)])); + + interface.type_def(TypeDef::type_("t26", Type::tuple(Vec::::new()))); + interface.type_def(TypeDef::type_("t27", Type::tuple([Type::U32]))); + interface.type_def(TypeDef::type_("t29", Type::tuple([Type::U32, Type::U64]))); + + interface.type_def(TypeDef::flags("t30", Vec::::new())); + interface.type_def(TypeDef::flags( + "t31", + [("a", "option a"), ("b", "option b"), ("c", "option c")], + )); + interface.type_def(TypeDef::flags("t32", [("a",), ("b",), ("c",)])); + + interface.type_def(TypeDef::variant("t33", [("a",)])); + interface.type_def(TypeDef::variant("t34", [("a",), ("b",)])); + interface.type_def(TypeDef::variant("t35", [("a",), ("b",)])); + interface.type_def(TypeDef::new( + "t36", + TypeDefKind::Variant( + [VariantCase::empty("a"), VariantCase::value("b", Type::U32)].into(), + ), + )); + interface.type_def(TypeDef::new( + "t37", + TypeDefKind::Variant( + [ + VariantCase::empty("a"), + VariantCase::value("b", Type::option(Type::U32)), + ] + .into(), + ), + )); + + interface.type_def(TypeDef::enum_("t41", ["a", "b", "c"])); + interface.type_def(TypeDef::enum_("t42", ["a", "b", "c"])); + + interface.type_def(TypeDef::type_("t43", Type::Bool)); + interface.type_def(TypeDef::type_("t44", Type::String)); + interface.type_def(TypeDef::type_( + "t45", + Type::list(Type::list(Type::list(Type::named("t32")))), + )); + interface.type_def(TypeDef::type_("t46", Type::named("t44"))); + interface.type_def(TypeDef::type_("foo", Type::named("bar"))); + interface.type_def(TypeDef::type_("bar", Type::U32)); + + interface.type_def(TypeDef::resource("t50", Vec::::new())); + interface.type_def(TypeDef::resource( + "t51", + [ + { + let mut func = ResourceFunc::constructor(); + func.params(Params::from_iter([("a", Type::U32)])); + func.docs(Some("create a new t51")); + func + }, + { + let mut func = ResourceFunc::method("set-a"); + func.params(Params::from_iter([("a", Type::U32)])); + func.docs(Some("set a")); + func + }, + { + let mut func = ResourceFunc::method("get-a"); + func.results(Results::anon(Type::U32)); + func.docs(Some("get a")); + func + }, + { + let mut func = ResourceFunc::static_("b"); + func.docs(Some("do b")); + func + }, + ], + )); + interface + }); + assert_eq!(PACKAGE, package.to_string()); +} diff --git a/crates/wit-encoder/tests/world.rs b/crates/wit-encoder/tests/world.rs new file mode 100644 index 0000000000..38e71bb9d7 --- /dev/null +++ b/crates/wit-encoder/tests/world.rs @@ -0,0 +1,56 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Interface, StandaloneFunc, Type}; + +const PACKAGE: &str = "package foo:functions; + +interface error-reporter { +} +world %world { + /// inline interface + export example: interface { + /// func docs + do-nothing: func(); + } + /// scan stuff + export scan: func() -> list; + import error-reporter; + import print: func(s: string); +} +"; + +#[test] +fn worlds() { + let name = wit_encoder::PackageName::new("foo", "functions", None); + let mut package = wit_encoder::Package::new(name); + + package.interface(wit_encoder::Interface::new("error-reporter")); + + package.world({ + let mut world = wit_encoder::World::new("world"); + world.inline_interface_export({ + let mut interface = Interface::new("example"); + interface.docs(Some("inline interface")); + interface.function({ + let mut func = StandaloneFunc::new("do-nothing"); + func.docs(Some("func docs")); + func + }); + interface + }); + world.function_export({ + let mut func = StandaloneFunc::new("scan"); + func.results(Type::list(Type::U8)); + func.docs(Some("scan stuff")); + func + }); + world.named_interface_import("error-reporter"); + world.function_import({ + let mut func: StandaloneFunc = StandaloneFunc::new("print"); + func.params(("s", Type::String)); + func + }); + world + }); + + assert_eq!(PACKAGE, package.to_string()); +} From 2b51f7cc3087ebae4e372ca2e33a95ea80ea2b4b Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Mon, 17 Jun 2024 23:01:52 -0700 Subject: [PATCH 17/58] Add `lann/wasm-wave` to crates (#1606) * import wasm-wave 0.6.0 with minor fixes to fit in workspace, delete dependency on wasmtime, and fix some benign warnings Co-authored-by: Lann Martin * import tests from wasm-wave 0.6.0 wasmtime test removed from these sources, since that depends on wasm_wave::wasmtime which was not imported here. Co-authored-by: Lann Martin * wave ui tests: upgrade snapbox, depend on tryfn, use new syntax * wasm-wave: remove wasmtime test dep, and dead test code * wasm-wave: latest wit-parser api changes * wasm-wave: remove use of wasmtime from rustdocs * fixes for rebase * values and types: derive Eq in addition to PartialEq This is due to a bug that only occurs in MSRV (1.76.0) where, for consts such as Ty::BOOL be used in patterns, the type must derive not just PartialEq but also Eq. This restriction is relaxed in later rust releases for consts constructed directly rather than with const fns. * replace snapbox with by-hand implementation snapbox had a pretty trivial incompatibility with wasm32-wasi, and once i fixed that I found one of its deps did as well, so rather than shave all those yaks just replace the bit we need with this trivial code * readme: fix url to wit * url fixes * delete duplicate license * add wasm-wave to crates to publish * add comment of todo * add todo in comment * gitattributes: wave test input and output are text but newlines should not be messed with --------- Co-authored-by: Lann Martin --- .gitattributes | 2 + Cargo.lock | 62 ++ Cargo.toml | 1 + ci/publish.rs | 1 + crates/wasm-wave/Cargo.toml | 24 + crates/wasm-wave/README.md | 274 +++++++ crates/wasm-wave/contrib/vscode/.gitignore | 1 + .../contrib/vscode/.vscode/launch.json | 17 + crates/wasm-wave/contrib/vscode/.vscodeignore | 1 + crates/wasm-wave/contrib/vscode/README.md | 8 + .../vscode/language-configuration.json | 28 + crates/wasm-wave/contrib/vscode/package.json | 29 + .../vscode/syntaxes/wave.tmLanguage.json | 80 +++ crates/wasm-wave/src/ast.rs | 473 ++++++++++++ crates/wasm-wave/src/lex.rs | 137 ++++ crates/wasm-wave/src/lib.rs | 69 ++ crates/wasm-wave/src/parser.rs | 673 ++++++++++++++++++ crates/wasm-wave/src/strings.rs | 202 ++++++ crates/wasm-wave/src/untyped.rs | 245 +++++++ crates/wasm-wave/src/value/convert.rs | 260 +++++++ crates/wasm-wave/src/value/func.rs | 63 ++ crates/wasm-wave/src/value/mod.rs | 553 ++++++++++++++ crates/wasm-wave/src/value/tests.rs | 154 ++++ crates/wasm-wave/src/value/ty.rs | 267 +++++++ crates/wasm-wave/src/value/wit.rs | 218 ++++++ crates/wasm-wave/src/wasm/fmt.rs | 225 ++++++ crates/wasm-wave/src/wasm/func.rs | 33 + crates/wasm-wave/src/wasm/mod.rs | 78 ++ crates/wasm-wave/src/wasm/ty.rs | 137 ++++ crates/wasm-wave/src/wasm/val.rs | 329 +++++++++ crates/wasm-wave/src/writer.rs | 200 ++++++ crates/wasm-wave/tests/nan.rs | 61 ++ crates/wasm-wave/tests/types.wit | 44 ++ crates/wasm-wave/tests/ui.rs | 119 ++++ crates/wasm-wave/tests/ui/README.md | 13 + crates/wasm-wave/tests/ui/accept-chars.out | 6 + crates/wasm-wave/tests/ui/accept-chars.waves | 6 + crates/wasm-wave/tests/ui/accept-comments.out | 2 + .../wasm-wave/tests/ui/accept-comments.waves | 19 + crates/wasm-wave/tests/ui/accept-enums.out | 10 + crates/wasm-wave/tests/ui/accept-enums.waves | 10 + crates/wasm-wave/tests/ui/accept-flags.out | 7 + crates/wasm-wave/tests/ui/accept-flags.waves | 7 + crates/wasm-wave/tests/ui/accept-floats.out | 13 + crates/wasm-wave/tests/ui/accept-floats.waves | 13 + crates/wasm-wave/tests/ui/accept-records.out | 14 + .../wasm-wave/tests/ui/accept-records.waves | 14 + crates/wasm-wave/tests/ui/accept-strings.out | 13 + .../wasm-wave/tests/ui/accept-strings.waves | 25 + crates/wasm-wave/tests/ui/reject-chars.out | 27 + crates/wasm-wave/tests/ui/reject-chars.waves | 28 + crates/wasm-wave/tests/ui/reject-comments.out | 5 + .../wasm-wave/tests/ui/reject-comments.waves | 14 + crates/wasm-wave/tests/ui/reject-enums.out | 8 + crates/wasm-wave/tests/ui/reject-enums.waves | 8 + crates/wasm-wave/tests/ui/reject-flags.out | 6 + crates/wasm-wave/tests/ui/reject-flags.waves | 6 + crates/wasm-wave/tests/ui/reject-floats.out | 19 + crates/wasm-wave/tests/ui/reject-floats.waves | 19 + crates/wasm-wave/tests/ui/reject-lists.out | 7 + crates/wasm-wave/tests/ui/reject-lists.waves | 7 + crates/wasm-wave/tests/ui/reject-options.out | 11 + .../wasm-wave/tests/ui/reject-options.waves | 11 + crates/wasm-wave/tests/ui/reject-records.out | 18 + .../wasm-wave/tests/ui/reject-records.waves | 18 + crates/wasm-wave/tests/ui/reject-results.out | 20 + .../wasm-wave/tests/ui/reject-results.waves | 20 + crates/wasm-wave/tests/ui/reject-strings.out | 32 + .../wasm-wave/tests/ui/reject-strings.waves | 46 ++ crates/wasm-wave/tests/ui/ui.wit | 65 ++ crates/wasm-wave/wave_ebnf.md | 84 +++ 71 files changed, 5719 insertions(+) create mode 100644 .gitattributes create mode 100644 crates/wasm-wave/Cargo.toml create mode 100644 crates/wasm-wave/README.md create mode 100644 crates/wasm-wave/contrib/vscode/.gitignore create mode 100644 crates/wasm-wave/contrib/vscode/.vscode/launch.json create mode 100644 crates/wasm-wave/contrib/vscode/.vscodeignore create mode 100644 crates/wasm-wave/contrib/vscode/README.md create mode 100644 crates/wasm-wave/contrib/vscode/language-configuration.json create mode 100644 crates/wasm-wave/contrib/vscode/package.json create mode 100644 crates/wasm-wave/contrib/vscode/syntaxes/wave.tmLanguage.json create mode 100644 crates/wasm-wave/src/ast.rs create mode 100644 crates/wasm-wave/src/lex.rs create mode 100644 crates/wasm-wave/src/lib.rs create mode 100644 crates/wasm-wave/src/parser.rs create mode 100644 crates/wasm-wave/src/strings.rs create mode 100644 crates/wasm-wave/src/untyped.rs create mode 100644 crates/wasm-wave/src/value/convert.rs create mode 100644 crates/wasm-wave/src/value/func.rs create mode 100644 crates/wasm-wave/src/value/mod.rs create mode 100644 crates/wasm-wave/src/value/tests.rs create mode 100644 crates/wasm-wave/src/value/ty.rs create mode 100644 crates/wasm-wave/src/value/wit.rs create mode 100644 crates/wasm-wave/src/wasm/fmt.rs create mode 100644 crates/wasm-wave/src/wasm/func.rs create mode 100644 crates/wasm-wave/src/wasm/mod.rs create mode 100644 crates/wasm-wave/src/wasm/ty.rs create mode 100644 crates/wasm-wave/src/wasm/val.rs create mode 100644 crates/wasm-wave/src/writer.rs create mode 100644 crates/wasm-wave/tests/nan.rs create mode 100644 crates/wasm-wave/tests/types.wit create mode 100644 crates/wasm-wave/tests/ui.rs create mode 100644 crates/wasm-wave/tests/ui/README.md create mode 100644 crates/wasm-wave/tests/ui/accept-chars.out create mode 100644 crates/wasm-wave/tests/ui/accept-chars.waves create mode 100644 crates/wasm-wave/tests/ui/accept-comments.out create mode 100644 crates/wasm-wave/tests/ui/accept-comments.waves create mode 100644 crates/wasm-wave/tests/ui/accept-enums.out create mode 100644 crates/wasm-wave/tests/ui/accept-enums.waves create mode 100644 crates/wasm-wave/tests/ui/accept-flags.out create mode 100644 crates/wasm-wave/tests/ui/accept-flags.waves create mode 100644 crates/wasm-wave/tests/ui/accept-floats.out create mode 100644 crates/wasm-wave/tests/ui/accept-floats.waves create mode 100644 crates/wasm-wave/tests/ui/accept-records.out create mode 100644 crates/wasm-wave/tests/ui/accept-records.waves create mode 100644 crates/wasm-wave/tests/ui/accept-strings.out create mode 100644 crates/wasm-wave/tests/ui/accept-strings.waves create mode 100644 crates/wasm-wave/tests/ui/reject-chars.out create mode 100644 crates/wasm-wave/tests/ui/reject-chars.waves create mode 100644 crates/wasm-wave/tests/ui/reject-comments.out create mode 100644 crates/wasm-wave/tests/ui/reject-comments.waves create mode 100644 crates/wasm-wave/tests/ui/reject-enums.out create mode 100644 crates/wasm-wave/tests/ui/reject-enums.waves create mode 100644 crates/wasm-wave/tests/ui/reject-flags.out create mode 100644 crates/wasm-wave/tests/ui/reject-flags.waves create mode 100644 crates/wasm-wave/tests/ui/reject-floats.out create mode 100644 crates/wasm-wave/tests/ui/reject-floats.waves create mode 100644 crates/wasm-wave/tests/ui/reject-lists.out create mode 100644 crates/wasm-wave/tests/ui/reject-lists.waves create mode 100644 crates/wasm-wave/tests/ui/reject-options.out create mode 100644 crates/wasm-wave/tests/ui/reject-options.waves create mode 100644 crates/wasm-wave/tests/ui/reject-records.out create mode 100644 crates/wasm-wave/tests/ui/reject-records.waves create mode 100644 crates/wasm-wave/tests/ui/reject-results.out create mode 100644 crates/wasm-wave/tests/ui/reject-results.waves create mode 100644 crates/wasm-wave/tests/ui/reject-strings.out create mode 100644 crates/wasm-wave/tests/ui/reject-strings.waves create mode 100644 crates/wasm-wave/tests/ui/ui.wit create mode 100644 crates/wasm-wave/wave_ebnf.md diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..684c5c1eef --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.waves text eol=lf +*.out text eol=lf diff --git a/Cargo.lock b/Cargo.lock index dd46f31d47..98a59b1ced 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -131,6 +131,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +[[package]] +name = "beef" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" + [[package]] name = "bincode" version = "1.3.3" @@ -638,6 +644,12 @@ dependencies = [ "miniz_oxide", ] +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "fuzz-stats" version = "0.0.0" @@ -851,6 +863,12 @@ dependencies = [ "libc", ] +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + [[package]] name = "leb128" version = "0.2.5" @@ -898,6 +916,39 @@ version = "0.4.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +[[package]] +name = "logos" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161971eb88a0da7ae0c333e1063467c5b5727e7fb6b710b8db4814eade3a42e8" +dependencies = [ + "logos-derive", +] + +[[package]] +name = "logos-codegen" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e31badd9de5131fdf4921f6473d457e3dd85b11b7f091ceb50e4df7c3eeb12a" +dependencies = [ + "beef", + "fnv", + "lazy_static", + "proc-macro2", + "quote", + "regex-syntax", + "syn 2.0.60", +] + +[[package]] +name = "logos-derive" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c2a69b3eb68d5bd595107c9ee58d7e07fe2bb5e360cc85b0f084dedac80de0a" +dependencies = [ + "logos-codegen", +] + [[package]] name = "mach2" version = "0.4.2" @@ -1695,6 +1746,17 @@ dependencies = [ "wit-smith", ] +[[package]] +name = "wasm-wave" +version = "0.210.0" +dependencies = [ + "anyhow", + "indexmap 2.2.6", + "logos", + "thiserror", + "wit-parser 0.210.0", +] + [[package]] name = "wasmparser" version = "0.202.0" diff --git a/Cargo.toml b/Cargo.toml index c3d369d3e6..1690376ccc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -36,6 +36,7 @@ members = [ 'crates/c-api', 'crates/fuzz-stats', 'crates/wasm-mutate-stats', + 'crates/wasm-wave', 'fuzz', 'crates/wit-encoder', 'crates/wit-parser/fuzz', diff --git a/ci/publish.rs b/ci/publish.rs index 60418a2982..af0ff3fade 100644 --- a/ci/publish.rs +++ b/ci/publish.rs @@ -31,6 +31,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[ "wit-encoder", "wasm-compose", "wit-smith", + "wasm-wave", "wasm-tools", ]; diff --git a/crates/wasm-wave/Cargo.toml b/crates/wasm-wave/Cargo.toml new file mode 100644 index 0000000000..430d8f8a7a --- /dev/null +++ b/crates/wasm-wave/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "wasm-wave" +version.workspace = true +edition.workspace = true +rust-version.workspace = true +authors = ["lann.martin@fermyon.com"] +description = "WebAssembly Value Encoding" +documentation = "https://docs.rs/wasm-wave" +categories = ["wasm", "encoding", "parser-implementations"] +repository = "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-wave" +readme = "README.md" + +[features] +default = ["wit"] +wit = ["dep:wit-parser"] + +[dependencies] +indexmap.workspace = true +logos = "0.14.0" +thiserror = "1.0.48" +wit-parser = { workspace = true, optional = true } + +[dev-dependencies] +anyhow.workspace = true diff --git a/crates/wasm-wave/README.md b/crates/wasm-wave/README.md new file mode 100644 index 0000000000..5b3e010d53 --- /dev/null +++ b/crates/wasm-wave/README.md @@ -0,0 +1,274 @@ +# WAVE: Web Assembly Value Encoding + +WAVE is a human-oriented text encoding of WebAssembly Component Model values. It is designed to be consistent with the +[WIT IDL format](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md). + +|Type|Example Values +|---|--- +|Bools|`true`, `false` +|Integers|`123`, `-9` +|Floats|`3.14`, `6.022e+23`, `nan`, `-inf` +|Chars|`'x'`, `'☃︎'`, `'\''`, `'\u{0}'` +|Strings|`"abc\t123"` +|Tuples|`("abc", 123)` +|Lists|`[1, 2, 3]` +|Records|`{field-a: 1, field-b: "two"}` +|Variants|`days(30)`, `forever` +|Enums|`south`, `west` +|Options|`"flat some"`, `some("explicit some")`, `none` +|Results|`"flat ok"`, `ok("explicit ok")`, `err("oops")` +|Flags|`{read, write}`, `{}` + +## Usage + +```rust +use wasmtime::component::{Type, Val}; + +let val: Val = wasm_wave::from_str(&Type::String, "\"👋 Hello, world! 👋\"").unwrap(); +println!("{}", wasm_wave::to_string(&val).unwrap()); +``` + +→ `"👋 Hello, world! 👋"` + +## Encoding + +Values are encoded as Unicode text. UTF-8 should be used wherever an interoperable binary encoding is required. + +### Whitespace + +Whitespace is _insignificant between tokens_ and _significant within tokens_: keywords, labels, chars, and strings. + +### Comments + +Comments start with `//` and run to the end of the line. + +### Keywords + +Several tokens are reserved WAVE keywords: `true`, `false`, `inf`, `nan`, `some`, `none`, `ok`, `err`. Variant or enum cases that match one of these keywords must be prefixed with `%`. + +### Labels + +Kebab-case labels are used for record fields, variant cases, enum cases, and flags. Labels use ASCII alphanumeric characters and hyphens, following the [Wit identifier syntax](https://github.com/WebAssembly/component-model/blob/main/design/mvp/WIT.md#identifiers): + +- Labels consist of one or more hypen-separated words. + - `one`, `two-words` +- Words consist of one ASCII letter followed by any number of ASCII alphanumeric characters. + - `q`, `abc123` +- Each word can contain lowercase or uppercase characters but not both; each word in a label can use a different (single) case. + - `HTTP3`, `method-GET` +- Any label may be prefixed with `%`; this is not part of the label itself but allows for representing labels that would otherwise be parsed as keywords. + - `%err`, `%non-keyword` + +### Bools + +Bools are encoded as one of the keywords `false` or `true`. + +### Integers + +Integers are encoded as base-10 numbers. + +> TBD: hex/bin repr? e.g. `0xab`, `0b1011` + +### Floats + +Floats are encoded as JSON numbers or one of the keywords `nan`, (not a number) `inf` (infinity), or `-inf` (negative infinity). + +### Chars + +Chars are encoded as `''`, where `` is one of: + +- a single [Unicode Scalar Value](https://unicode.org/glossary/#unicode_scalar_value) +- one of these escapes: + - `\'` → `'` + - `\"` → `"` + - `\\` → `\` + - `\t` → U+9 (HT) + - `\n` → U+A (LF) + - `\r` → U+D (CR) + - `\u{···}` → U+··· (where `···` is a hexadecimal Unicode Scalar Value) + +Escaping newline (`\n`), `\`, and `'` is mandatory for chars. + +### Strings + +Strings are encoded as a double-quote-delimited sequence of ``s (as for [Chars](#chars)). + +Escaping newline (`\n`), `\`, and `"` is mandatory for strings. + +### Multiline Strings + +A multiline string begins with `"""` followed immediately by a line break (`\n` or `\r\n`) and ends with a line break, zero or more spaces, and `"""`. The number of spaces immediately preceding the ending `"""` determines the indent level of the entire multiline string. Every other line break in the string must be followed by at least this many spaces which are then omitted ("dedented") from the decoded string. + +Each line break in the encoded string except for the first and last is decoded as a newline character (`\n`). + +Escaping `\` is mandatory for multiline strings. Escaping carriage return (`\r`) is mandatory immediately before a literal newline character (`\n`) if it is to be retained. Escaping `"` is mandatory where necessary to break up any sequence of `"""` within a string, even if the first `"` is escaped (i.e. `\"""` is prohibited). + +```python +""" +A single line +""" +``` +→ `"A single line"` + +```python +""" + Indentation determined + by ending delimiter + """ +``` +→ +```clike +" Indentation determined\n by ending delimiter" +``` + +```python +""" + Must escape carriage return at end of line: \r + Must break up double quote triplets: ""\"" + """ +``` +→ +```clike +"Must escape carriage return at end of line: \r\nMust break up double quote triplets: \"\"\"\"" +``` + +### Tuples + +Tuples are encoded as a parenthesized sequence of comma-separated values. Trailing commas are permitted. + +`tuple` → `(123, "abc")` + +### Lists + +Lists are encoded as a square-braketed sequence of comma-separated values. Trailing commas are permitted. + +`list` → `[]`, `['a', 'b', 'c']` + +### Records + +Records are encoded as curly-braced set of comma-separated record entries. Trailing commas are permitted. Each record entry consists of a field label, a colon, and a value. Fields may be present in any order. Record entries with the `option`-typed value `none` may be omitted; if all of a record's fields are omitted in this way the "empty" record must be encoded as `{:}` (to disambiguate from an empty `flags` value). + +```clike +record example { + must-have: u8, + optional: option, +} +``` + +→ `{must-have: 123}` = `{must-have: 123, optional: none,}` + +```clike +record all-optional { + optional: option, +} +``` + +→ `{:}` = `{optional: none}` + +> Note: Field labels _may_ be prefixed with `%` but this is never required. + +### Variants + +Variants are encoded as a case label. If the case has a payload, the label is followed by the parenthesized case payload value. + +If a variant case matches a WAVE keyword it must be prefixed with `%`. + +```clike +variant response { + empty, + body(list), + err(string), +} +``` + +→ `empty`, `body([79, 75])`, `%err("oops")` + +### Enums + +Enums are encoded as a case label. + +If an enum case matches a WAVE keyword it must be prefixed with `%`. + +`enum status { ok, not-found }` → `%ok`, `not-found` + +### Options + +Options may be encoded in their variant form (e.g. `some(...)` or `none`). A `some` value may also be encoded as the "flat" payload value itself, but only if the payload is not an option or result type. + +- `option` → `123` = `some(123)` + +### Results + +Results may be encoded in their variant form (e.g. `ok(...)`, `err("oops")`). An `ok` value may also be encoded as the "flat" payload value itself, but only if it has a payload which is not an option or result type. + +- `result` → `123` = `ok(123)` +- `result<_, string>` → `ok`, `err("oops")` +- `result` → `ok`, `err` + +### Flags + +Flags are encoded as a curly-braced set of comma-separated flag labels in any order. Trailing commas are permitted. + +`flags perms { read, write, exec }` → `{write, read,}` + +> Note: Flags _may_ be prefixed with `%` but this is never required. + +> TBD: Allow record form? `{read: true, write: true, exec: false}` + +### Resources + +> TBD (`()`?) + +## Appendix: Function calls + +Some applications may benefit from a standard way to encode function calls and/or results, described here. + +Function calls can be encoded as some application-dependent function identifier (such as a kebab-case label) followed by parenthesized function arguments. + +If function results need to be encoded along with the call, they can be separated from the call by `->`. + +```clike +my-func("param") + +with-result() -> ok("result") +``` + +### Function arguments + +Arguments are encoded as a sequence of comma-separated values. + +Any number of `option` `none` values at the end of the sequence may be omitted. + +```clike +// f: func(a: option, b: option, c: option) +// all equivalent: +f(some(1)) +f(some(1), none) +f(some(1), none, none) +``` + +> TBD: Named-parameter encoding? e.g. +> `my-func(named-param: 1)` +> Could allow omitting "middle" `none` params. + +### Function results + +Results are encoded in one of several ways depending on the number of result values: + +- Any number of result values may be encoded as a parenthesized sequence of comma-separated result entries. Each result entry consists of a label (for named results) or zero-based index number, a colon, and the result value. Result entry ordering must match the function definition. +- Zero result values are encoded as `()` or omitted entirely. +- A single result value may be encoded as the "flat" result value itself. + +```clike +-> () + +-> some("single result") +// or +-> (0: some("single result")) + +-> (result-a: "abc", result-b: 123) +``` + +--- + +:ocean: diff --git a/crates/wasm-wave/contrib/vscode/.gitignore b/crates/wasm-wave/contrib/vscode/.gitignore new file mode 100644 index 0000000000..cdda1252a6 --- /dev/null +++ b/crates/wasm-wave/contrib/vscode/.gitignore @@ -0,0 +1 @@ +/*.vsix diff --git a/crates/wasm-wave/contrib/vscode/.vscode/launch.json b/crates/wasm-wave/contrib/vscode/.vscode/launch.json new file mode 100644 index 0000000000..0e191b5929 --- /dev/null +++ b/crates/wasm-wave/contrib/vscode/.vscode/launch.json @@ -0,0 +1,17 @@ +// A launch configuration that launches the extension inside a new window +// Use IntelliSense to learn about possible attributes. +// Hover to view descriptions of existing attributes. +// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Extension", + "type": "extensionHost", + "request": "launch", + "args": [ + "--extensionDevelopmentPath=${workspaceFolder}" + ] + } + ] +} \ No newline at end of file diff --git a/crates/wasm-wave/contrib/vscode/.vscodeignore b/crates/wasm-wave/contrib/vscode/.vscodeignore new file mode 100644 index 0000000000..f16fa5501b --- /dev/null +++ b/crates/wasm-wave/contrib/vscode/.vscodeignore @@ -0,0 +1 @@ +.vscode/** diff --git a/crates/wasm-wave/contrib/vscode/README.md b/crates/wasm-wave/contrib/vscode/README.md new file mode 100644 index 0000000000..7d782fc325 --- /dev/null +++ b/crates/wasm-wave/contrib/vscode/README.md @@ -0,0 +1,8 @@ +# WAVE VS Code Extension + +Basic syntax highlighting only. Contributions very welcome! + +```console +$ npx vsce package +$ code --install-extension wasm-wave-0.0.1.vsix +``` diff --git a/crates/wasm-wave/contrib/vscode/language-configuration.json b/crates/wasm-wave/contrib/vscode/language-configuration.json new file mode 100644 index 0000000000..ec5e7d9546 --- /dev/null +++ b/crates/wasm-wave/contrib/vscode/language-configuration.json @@ -0,0 +1,28 @@ +{ + "comments": { + // symbol used for single line comment. Remove this entry if your language does not support line comments + "lineComment": "//" + }, + // symbols used as brackets + "brackets": [ + ["{", "}"], + ["[", "]"], + ["(", ")"] + ], + // symbols that are auto closed when typing + "autoClosingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ], + // symbols that can be used to surround a selection + "surroundingPairs": [ + ["{", "}"], + ["[", "]"], + ["(", ")"], + ["\"", "\""], + ["'", "'"] + ] +} \ No newline at end of file diff --git a/crates/wasm-wave/contrib/vscode/package.json b/crates/wasm-wave/contrib/vscode/package.json new file mode 100644 index 0000000000..e182a7d0ef --- /dev/null +++ b/crates/wasm-wave/contrib/vscode/package.json @@ -0,0 +1,29 @@ +{ + "name": "wasm-wave", + "displayName": "Wave", + "description": "", + "version": "0.0.1", + "engines": { + "vscode": "^1.85.0" + }, + "categories": [ + "Programming Languages" + ], + "repository": { + "type": "git", + "url": "https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-wave" + }, + "contributes": { + "languages": [{ + "id": "wave", + "aliases": ["WAVE", "wave"], + "extensions": [".wave", ".waves"], + "configuration": "./language-configuration.json" + }], + "grammars": [{ + "language": "wave", + "scopeName": "source.wave", + "path": "./syntaxes/wave.tmLanguage.json" + }] + } +} diff --git a/crates/wasm-wave/contrib/vscode/syntaxes/wave.tmLanguage.json b/crates/wasm-wave/contrib/vscode/syntaxes/wave.tmLanguage.json new file mode 100644 index 0000000000..376533a2f7 --- /dev/null +++ b/crates/wasm-wave/contrib/vscode/syntaxes/wave.tmLanguage.json @@ -0,0 +1,80 @@ +{ + "$schema": "https://raw.githubusercontent.com/martinring/tmlanguage/master/tmlanguage.json", + "name": "WAVE", + "patterns": [ + { "include": "#comment" }, + { "include": "#raw-keyword"}, + { "include": "#number" }, + { "include": "#keyword-constant" }, + { "include": "#keyword-label" }, + { "include": "#label" }, + { "include": "#char" }, + { "include": "#string" } + ], + "repository": { + "comment": { + "patterns": [{ + "name": "comment.line.double-slash.wave", + "match": "(//)[^\\n]*", + "captures": { + "1": { + "name": "punctuation.definition.comment.wave" + } + } + }] + }, + "raw-keyword": { + "patterns": [{ + "name": "variable.other.label.wave", + "match": "%(true|false|some|none|ok|err|inf|nan)\\b" + }] + }, + "number": { + "patterns": [ + { + "name": "constant.numeric.wave", + "match": "-?(0|([1-9][0-9]*))(\\.[0-9]+)?([eE][-+]?[0-9]+)?" + }, + { + "name": "constant.numeric.wave", + "match": "\\b(nan|inf|-inf)\\b" + } + ] + }, + "keyword-constant": { + "patterns": [{ + "name": "constant.language.wave", + "match": "\\b(true|false)\\b" + }] + }, + "keyword-label": { + "patterns": [{ + "name": "variable.language.label.wave", + "match": "\\b(some|none|ok|err)\\b" + }] + }, + "label": { + "patterns": [{ + "name": "variable.other.label.wave", + "match": "\\b%?([a-z][a-z0-9]*|[A-Z][A-Z0-9]*)(-([a-z][a-z0-9]*|[A-Z][A-Z0-9]*))*\\b" + }] + }, + "char": { + "name": "constant.character.wave", + "begin": "'", + "end": "'", + "patterns": [{ "include": "#char-escape" }] + }, + "string": { + "name": "string.quoted.double.wave", + "begin": "\"", + "end": "\"", + "patterns": [{ "include": "#char-escape"}] + }, + "char-escape": { + "name": "constant.character.escape.wave", + "match": "\\\\(['\"tnr\\\\]|u\\{[0-9a-fA-F]+\\})" + } + }, + "scopeName": "source.wave" +} \ No newline at end of file diff --git a/crates/wasm-wave/src/ast.rs b/crates/wasm-wave/src/ast.rs new file mode 100644 index 0000000000..a82c6c491a --- /dev/null +++ b/crates/wasm-wave/src/ast.rs @@ -0,0 +1,473 @@ +//! Abstract syntax tree types + +use std::{borrow::Cow, collections::HashMap, str::FromStr}; + +use crate::{ + lex::Span, + parser::{ParserError, ParserErrorKind}, + strings::{unescape, StringPartsIter}, + wasm::{WasmType, WasmTypeKind, WasmValue, WasmValueError}, +}; + +/// A WAVE AST node. +#[derive(Clone, Debug)] +pub struct Node { + ty: NodeType, + span: Span, + children: Vec, +} + +impl Node { + pub(crate) fn new( + ty: NodeType, + span: impl Into, + children: impl IntoIterator, + ) -> Self { + Self { + ty, + span: span.into(), + children: Vec::from_iter(children), + } + } + + /// Returns this node's type. + pub fn ty(&self) -> NodeType { + self.ty + } + + /// Returns this node's span. + pub fn span(&self) -> Span { + self.span.clone() + } + + /// Returns a bool value if this node represents a bool. + pub fn as_bool(&self) -> Result { + match self.ty { + NodeType::BoolTrue => Ok(true), + NodeType::BoolFalse => Ok(false), + _ => Err(self.error(ParserErrorKind::InvalidType)), + } + } + + /// Returns a number value of the given type (integer or float) if this node + /// can represent a number of that type. + pub fn as_number(&self, src: &str) -> Result { + self.ensure_type(NodeType::Number)?; + self.slice(src) + .parse() + .map_err(|_| self.error(ParserErrorKind::InvalidValue)) + } + + /// Returns a char value if this node represents a valid char. + pub fn as_char(&self, src: &str) -> Result { + self.ensure_type(NodeType::Char)?; + let inner = &src[self.span.start + 1..self.span.end - 1]; + let (ch, len) = if inner.starts_with('\\') { + unescape(inner).ok_or_else(|| self.error(ParserErrorKind::InvalidEscape))? + } else { + let ch = inner.chars().next().unwrap(); + (ch, ch.len_utf8()) + }; + // Verify length + if len != inner.len() { + return Err(self.error(ParserErrorKind::MultipleChars)); + } + Ok(ch) + } + + /// Returns a str value if this node represents a valid string. + pub fn as_str<'src>(&self, src: &'src str) -> Result, ParserError> { + let mut parts = self.iter_str(src)?; + let Some(first) = parts.next().transpose()? else { + return Ok("".into()); + }; + match parts.next().transpose()? { + // Single part may be borrowed + None => Ok(first), + // Multiple parts must be collected into a single owned String + Some(second) => { + let s: String = [Ok(first), Ok(second)] + .into_iter() + .chain(parts) + .collect::>()?; + Ok(s.into()) + } + } + } + + /// Returns an iterator of string "parts" which together form a decoded + /// string value if this node represents a valid string. + pub fn iter_str<'src>( + &self, + src: &'src str, + ) -> Result, ParserError>>, ParserError> { + match self.ty { + NodeType::String => { + let span = self.span.start + 1..self.span.end - 1; + Ok(StringPartsIter::new(&src[span.clone()], span.start)) + } + NodeType::MultilineString => { + let span = self.span.start + 3..self.span.end - 3; + Ok(StringPartsIter::new_multiline( + &src[span.clone()], + span.start, + )?) + } + _ => Err(self.error(ParserErrorKind::InvalidType)), + } + } + + /// Returns an iterator of value nodes if this node represents a tuple. + pub fn as_tuple(&self) -> Result, ParserError> { + self.ensure_type(NodeType::Tuple)?; + Ok(self.children.iter()) + } + + /// Returns an iterator of value nodes if this node represents a list. + pub fn as_list(&self) -> Result, ParserError> { + self.ensure_type(NodeType::List)?; + Ok(self.children.iter()) + } + + /// Returns an iterator of field name and value node pairs if this node + /// represents a record value. + pub fn as_record<'this, 'src>( + &'this self, + src: &'src str, + ) -> Result, ParserError> { + self.ensure_type(NodeType::Record)?; + Ok(self + .children + .chunks(2) + .map(|chunk| (chunk[0].as_label(src).unwrap(), &chunk[1]))) + } + + /// Returns a variant label and optional payload if this node can represent + /// a variant value. + pub fn as_variant<'this, 'src>( + &'this self, + src: &'src str, + ) -> Result<(&'src str, Option<&'this Node>), ParserError> { + match self.ty { + NodeType::Label => Ok((self.as_label(src)?, None)), + NodeType::VariantWithPayload => { + let label = self.children[0].as_label(src)?; + let value = &self.children[1]; + Ok((label, Some(value))) + } + _ => Err(self.error(ParserErrorKind::InvalidType)), + } + } + + /// Returns an enum value label if this node represents a label. + pub fn as_enum<'src>(&self, src: &'src str) -> Result<&'src str, ParserError> { + self.as_label(src) + } + + /// Returns an option value if this node represents an option. + pub fn as_option(&self) -> Result, ParserError> { + match self.ty { + NodeType::OptionSome => Ok(Some(&self.children[0])), + NodeType::OptionNone => Ok(None), + _ => Err(self.error(ParserErrorKind::InvalidType)), + } + } + + /// Returns a result value with optional payload value if this node + /// represents a result. + pub fn as_result(&self) -> Result, Option<&Node>>, ParserError> { + let payload = self.children.first(); + match self.ty { + NodeType::ResultOk => Ok(Ok(payload)), + NodeType::ResultErr => Ok(Err(payload)), + _ => Err(self.error(ParserErrorKind::InvalidType)), + } + } + + /// Returns an iterator of flag labels if this node represents flags. + pub fn as_flags<'this, 'src: 'this>( + &'this self, + src: &'src str, + ) -> Result + 'this, ParserError> { + self.ensure_type(NodeType::Flags)?; + Ok(self.children.iter().map(|node| { + debug_assert_eq!(node.ty, NodeType::Label); + node.slice(src) + })) + } + + fn as_label<'src>(&self, src: &'src str) -> Result<&'src str, ParserError> { + self.ensure_type(NodeType::Label)?; + let label = self.slice(src); + let label = label.strip_prefix('%').unwrap_or(label); + Ok(label) + } + + /// Converts this node into the given typed value from the given input source. + pub fn to_wasm_value(&self, ty: &V::Type, src: &str) -> Result { + Ok(match ty.kind() { + WasmTypeKind::Bool => V::make_bool(self.as_bool()?), + WasmTypeKind::S8 => V::make_s8(self.as_number(src)?), + WasmTypeKind::S16 => V::make_s16(self.as_number(src)?), + WasmTypeKind::S32 => V::make_s32(self.as_number(src)?), + WasmTypeKind::S64 => V::make_s64(self.as_number(src)?), + WasmTypeKind::U8 => V::make_u8(self.as_number(src)?), + WasmTypeKind::U16 => V::make_u16(self.as_number(src)?), + WasmTypeKind::U32 => V::make_u32(self.as_number(src)?), + WasmTypeKind::U64 => V::make_u64(self.as_number(src)?), + WasmTypeKind::Float32 => V::make_float32(self.as_number(src)?), + WasmTypeKind::Float64 => V::make_float64(self.as_number(src)?), + WasmTypeKind::Char => V::make_char(self.as_char(src)?), + WasmTypeKind::String => V::make_string(self.as_str(src)?), + WasmTypeKind::List => self.to_wasm_list(ty, src)?, + WasmTypeKind::Record => self.to_wasm_record(ty, src)?, + WasmTypeKind::Tuple => self.to_wasm_tuple(ty, src)?, + WasmTypeKind::Variant => self.to_wasm_variant(ty, src)?, + WasmTypeKind::Enum => self.to_wasm_enum(ty, src)?, + WasmTypeKind::Option => self.to_wasm_option(ty, src)?, + WasmTypeKind::Result => self.to_wasm_result(ty, src)?, + WasmTypeKind::Flags => self.to_wasm_flags(ty, src)?, + other => { + return Err( + self.wasm_value_error(WasmValueError::UnsupportedType(other.to_string())) + ) + } + }) + } + + /// Converts this node into the given types. + /// See [`crate::untyped::UntypedFuncCall::to_wasm_params`]. + pub fn to_wasm_params<'types, V: WasmValue + 'static>( + &self, + types: impl IntoIterator, + src: &str, + ) -> Result, ParserError> { + let mut types = types.into_iter(); + let mut values = self + .as_tuple()? + .map(|node| { + let ty = types.next().ok_or_else(|| { + ParserError::with_detail( + ParserErrorKind::InvalidParams, + node.span().clone(), + "more param(s) than expected", + ) + })?; + node.to_wasm_value::(ty, src) + }) + .collect::, _>>()?; + // Handle trailing optional fields + for ty in types { + if ty.kind() == WasmTypeKind::Option { + values.push(V::make_option(ty, None).map_err(|err| self.wasm_value_error(err))?); + } else { + return Err(ParserError::with_detail( + ParserErrorKind::InvalidParams, + self.span.end - 1..self.span.end, + "missing required param(s)", + )); + } + } + Ok(values) + } + + fn to_wasm_list(&self, ty: &V::Type, src: &str) -> Result { + let element_type = ty.list_element_type().unwrap(); + let elements = self + .as_list()? + .map(|node| node.to_wasm_value(&element_type, src)) + .collect::, _>>()?; + V::make_list(ty, elements).map_err(|err| self.wasm_value_error(err)) + } + + fn to_wasm_record(&self, ty: &V::Type, src: &str) -> Result { + let values = self.as_record(src)?.collect::>(); + let record_fields = ty.record_fields().collect::>(); + let fields = record_fields + .iter() + .map(|(name, field_type)| { + let value = match (values.get(name.as_ref()), field_type.kind()) { + (Some(node), _) => node.to_wasm_value(field_type, src)?, + (None, WasmTypeKind::Option) => V::make_option(field_type, None) + .map_err(|err| self.wasm_value_error(err))?, + _ => { + return Err( + self.wasm_value_error(WasmValueError::MissingField(name.to_string())) + ); + } + }; + Ok((name.as_ref(), value)) + }) + .collect::, _>>()?; + V::make_record(ty, fields).map_err(|err| self.wasm_value_error(err)) + } + + fn to_wasm_tuple(&self, ty: &V::Type, src: &str) -> Result { + let types = ty.tuple_element_types().collect::>(); + let values = self.as_tuple()?; + if types.len() != values.len() { + return Err( + self.wasm_value_error(WasmValueError::WrongNumberOfTupleValues { + want: types.len(), + got: values.len(), + }), + ); + } + let values = ty + .tuple_element_types() + .zip(self.as_tuple()?) + .map(|(ty, node)| node.to_wasm_value(&ty, src)) + .collect::, _>>()?; + V::make_tuple(ty, values).map_err(|err| self.wasm_value_error(err)) + } + + fn to_wasm_variant(&self, ty: &V::Type, src: &str) -> Result { + let (label, payload) = self.as_variant(src)?; + let payload_type = ty + .variant_cases() + .find_map(|(case, payload)| (case == label).then_some(payload)) + .ok_or_else(|| self.wasm_value_error(WasmValueError::UnknownCase(label.into())))?; + let payload_value = self.to_wasm_maybe_payload(label, &payload_type, payload, src)?; + V::make_variant(ty, label, payload_value).map_err(|err| self.wasm_value_error(err)) + } + + fn to_wasm_enum(&self, ty: &V::Type, src: &str) -> Result { + V::make_enum(ty, self.as_enum(src)?).map_err(|err| self.wasm_value_error(err)) + } + + fn to_wasm_option(&self, ty: &V::Type, src: &str) -> Result { + let payload_type = ty.option_some_type().unwrap(); + let value = match self.ty { + NodeType::OptionSome => { + self.to_wasm_maybe_payload("some", &Some(payload_type), self.as_option()?, src)? + } + NodeType::OptionNone => { + self.to_wasm_maybe_payload("none", &None, self.as_option()?, src)? + } + _ if flattenable(payload_type.kind()) => Some(self.to_wasm_value(&payload_type, src)?), + _ => { + return Err(self.error(ParserErrorKind::InvalidType)); + } + }; + V::make_option(ty, value).map_err(|err| self.wasm_value_error(err)) + } + + fn to_wasm_result(&self, ty: &V::Type, src: &str) -> Result { + let (ok_type, err_type) = ty.result_types().unwrap(); + let value = match self.ty { + NodeType::ResultOk => { + Ok(self.to_wasm_maybe_payload("ok", &ok_type, self.as_result()?.unwrap(), src)?) + } + NodeType::ResultErr => Err(self.to_wasm_maybe_payload( + "err", + &err_type, + self.as_result()?.unwrap_err(), + src, + )?), + _ => match ok_type { + Some(ty) if flattenable(ty.kind()) => Ok(Some(self.to_wasm_value(&ty, src)?)), + _ => return Err(self.error(ParserErrorKind::InvalidType)), + }, + }; + V::make_result(ty, value).map_err(|err| self.wasm_value_error(err)) + } + + fn to_wasm_flags(&self, ty: &V::Type, src: &str) -> Result { + V::make_flags(ty, self.as_flags(src)?).map_err(|err| self.wasm_value_error(err)) + } + + fn to_wasm_maybe_payload( + &self, + case: &str, + payload_type: &Option, + payload: Option<&Node>, + src: &str, + ) -> Result, ParserError> { + match (payload_type.as_ref(), payload) { + (Some(ty), Some(node)) => Ok(Some(node.to_wasm_value(ty, src)?)), + (None, None) => Ok(None), + (Some(_), None) => { + Err(self.wasm_value_error(WasmValueError::MissingPayload(case.into()))) + } + (None, Some(_)) => { + Err(self.wasm_value_error(WasmValueError::UnexpectedPayload(case.into()))) + } + } + } + + fn wasm_value_error(&self, err: WasmValueError) -> ParserError { + ParserError::with_source(ParserErrorKind::WasmValueError, self.span(), err) + } + + pub(crate) fn slice<'src>(&self, src: &'src str) -> &'src str { + &src[self.span()] + } + + fn ensure_type(&self, ty: NodeType) -> Result<(), ParserError> { + if self.ty == ty { + Ok(()) + } else { + Err(self.error(ParserErrorKind::InvalidType)) + } + } + + fn error(&self, kind: ParserErrorKind) -> ParserError { + ParserError::new(kind, self.span()) + } +} + +fn flattenable(kind: WasmTypeKind) -> bool { + // TODO: Consider wither to allow flattening an option in a result or vice-versa. + !matches!(kind, WasmTypeKind::Option | WasmTypeKind::Result) +} + +/// The type of a WAVE AST [`Node`]. +#[derive(Clone, Copy, Debug, PartialEq)] +pub enum NodeType { + /// Boolean `true` + BoolTrue, + /// Boolean `false` + BoolFalse, + /// Number + /// May be an integer or float, including `nan`, `inf`, `-inf` + Number, + /// Char + /// Span includes delimiters. + Char, + /// String + /// Span includes delimiters. + String, + /// Multiline String + /// Span includes delimiters. + MultilineString, + /// Tuple + /// Child nodes are the tuple values. + Tuple, + /// List + /// Child nodes are the list values. + List, + /// Record + /// Child nodes are field Label, value pairs, e.g. + /// `[, , , , ...]` + Record, + /// Label + /// In value position may represent an enum value or variant case (without payload). + Label, + /// Variant case with payload + /// Child nodes are variant case Label and payload value. + VariantWithPayload, + /// Option `some` + /// Child node is the payload value. + OptionSome, + /// Option `none` + OptionNone, + /// Result `ok` + /// Has zero or one child node for the payload value. + ResultOk, + /// Result `err` + /// Has zero or one child node for the payload value. + ResultErr, + /// Flags + /// Child nodes are flag Labels. + Flags, +} diff --git a/crates/wasm-wave/src/lex.rs b/crates/wasm-wave/src/lex.rs new file mode 100644 index 0000000000..27d71d9c51 --- /dev/null +++ b/crates/wasm-wave/src/lex.rs @@ -0,0 +1,137 @@ +//! Lexing types + +use std::fmt::Display; + +pub use logos::Span; + +/// A WAVE `logos::Lexer` +pub type Lexer<'source> = logos::Lexer<'source, Token>; + +/// A WAVE token +#[derive(Clone, Copy, Debug, PartialEq, Eq, logos::Logos)] +#[logos(error = Option)] +#[logos(skip r"[ \t\n\r]+")] +#[logos(skip r"//[^\n]*")] +#[logos(subpattern label_word = r"[a-z][a-z0-9]*|[A-Z][A-Z0-9]*")] +#[logos(subpattern char_escape = r#"\\['"tnr\\]|\\u\{[0-9a-fA-F]{1,6}\}"#)] +pub enum Token { + /// The `{` symbol + #[token("{")] + BraceOpen, + /// The `}` symbol + #[token("}")] + BraceClose, + + /// The `(` symbol + #[token("(")] + ParenOpen, + /// The `)` symbol + #[token(")")] + ParenClose, + + /// The `[` symbol + #[token("[")] + BracketOpen, + /// The `]` symbol + #[token("]")] + BracketClose, + + /// The `:` symbol + #[token(":")] + Colon, + + /// The `,` symbol + #[token(",")] + Comma, + + /// A number literal + #[regex(r"-?(0|([1-9][0-9]*))(\.[0-9]+)?([eE][-+]?[0-9]+)?")] + #[token("-inf")] + Number, + + /// A label or keyword + #[regex(r"%?(?&label_word)(-(?&label_word))*")] + LabelOrKeyword, + + /// A char literal + #[regex(r#"'([^\\'\n]{1,4}|(?&char_escape))'"#, validate_char)] + Char, + + /// A string literal + #[regex(r#""([^\\"\n]|(?&char_escape))*""#)] + String, + + /// A multi-line string literal + #[token(r#"""""#, lex_multiline_string)] + MultilineString, +} + +impl Display for Token { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{self:?}") + } +} + +fn validate_char(lex: &mut Lexer) -> Result<(), Option> { + let s = &lex.slice()[1..lex.slice().len() - 1]; + if s.starts_with('\\') || s.chars().count() == 1 { + Ok(()) + } else { + Err(Some(lex.span())) + } +} + +fn lex_multiline_string(lex: &mut Lexer) -> bool { + if let Some(end) = lex.remainder().find(r#"""""#) { + lex.bump(end + 3); + true + } else { + false + } +} + +/// A WAVE keyword +#[derive(Clone, Copy, Debug, PartialEq)] +#[allow(missing_docs)] +pub enum Keyword { + True, + False, + Some, + None, + Ok, + Err, + Inf, + Nan, +} + +impl Keyword { + /// Returns any keyword exactly matching the given string. + pub fn decode(raw_label: &str) -> Option { + Some(match raw_label { + "true" => Self::True, + "false" => Self::False, + "some" => Self::Some, + "none" => Self::None, + "ok" => Self::Ok, + "err" => Self::Err, + "inf" => Self::Inf, + "nan" => Self::Nan, + _ => return None, + }) + } +} + +impl Display for Keyword { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + Keyword::True => "true", + Keyword::False => "false", + Keyword::Some => "some", + Keyword::None => "none", + Keyword::Ok => "ok", + Keyword::Err => "err", + Keyword::Inf => "inf", + Keyword::Nan => "nan", + }) + } +} diff --git a/crates/wasm-wave/src/lib.rs b/crates/wasm-wave/src/lib.rs new file mode 100644 index 0000000000..4edde4233e --- /dev/null +++ b/crates/wasm-wave/src/lib.rs @@ -0,0 +1,69 @@ +//! Web Assembly Value Encoding +//! +//! WAVE is a human-oriented text encoding of WebAssembly Component Model values. +//! +//! For more information, see the [README](https://github.com/bytecodealliance/wasm-tools/tree/main/crates/wasm-wave#readme). +#![deny(missing_docs)] + +pub mod ast; +pub mod lex; +pub mod parser; +mod strings; +pub mod untyped; +pub mod value; +pub mod wasm; +pub mod writer; + +use parser::Parser; +use wasm::WasmValue; +use writer::Writer; + +/// Parses a [`WasmValue`] from the given WAVE-encoded string. +/// ``` +/// # fn main() -> Result<(), wasm_wave::parser::ParserError> { +/// use wasm_wave::{wasm::WasmValue, value::{Type, Value}}; +/// let val: Value = wasm_wave::from_str(&Type::CHAR, r"'\u{1F44B}'")?; +/// assert_eq!(val, Value::make_char('👋')); +/// # Ok(()) +/// # } +/// ``` +pub fn from_str(ty: &V::Type, s: &str) -> Result { + let mut parser = Parser::new(s); + + let value = parser.parse_value(ty)?; + + // Ensure that we've parsed the entire string. + parser.finish()?; + + Ok(value) +} + +/// Returns the given [`WasmValue`] as a WAVE-encoded string. +/// ``` +/// # fn main() -> Result<(), wasm_wave::writer::WriterError> { +/// use wasm_wave::{wasm::WasmValue, value::Value}; +/// let wave_str = wasm_wave::to_string(&Value::make_char('\u{1F44B}'))?; +/// assert_eq!(wave_str, "'👋'"); +/// # Ok(()) +/// # } +pub fn to_string(val: &impl WasmValue) -> Result { + let mut buf = vec![]; + Writer::new(&mut buf).write_value(val)?; + Ok(String::from_utf8(buf).unwrap_or_else(|err| panic!("invalid UTF-8: {err:?}"))) +} + +fn canonicalize_nan32(val: f32) -> f32 { + if val.is_nan() { + f32::from_bits(0x7fc00000) + } else { + val + } +} + +fn canonicalize_nan64(val: f64) -> f64 { + if val.is_nan() { + f64::from_bits(0x7ff8000000000000) + } else { + val + } +} diff --git a/crates/wasm-wave/src/parser.rs b/crates/wasm-wave/src/parser.rs new file mode 100644 index 0000000000..f35e5e3428 --- /dev/null +++ b/crates/wasm-wave/src/parser.rs @@ -0,0 +1,673 @@ +//! Parsing types + +use std::{collections::HashSet, error::Error, fmt::Display}; + +use indexmap::IndexMap; + +use crate::{ + ast::{Node, NodeType}, + lex::{Keyword, Lexer, Span, Token}, + untyped::{UntypedFuncCall, UntypedValue}, + WasmValue, +}; + +/// A Web Assembly Value Encoding parser. +pub struct Parser<'source> { + lex: Lexer<'source>, + curr: Option, +} + +impl<'source> Parser<'source> { + /// Returns a new Parser of the given source. + pub fn new(source: &'source str) -> Self { + Self::with_lexer(Lexer::new(source)) + } + + /// Returns a new Parser with the given [`Lexer`]. + pub fn with_lexer(lexer: Lexer<'source>) -> Self { + Self { + lex: lexer, + curr: None, + } + } + + /// Parses a WAVE-encoded value of the given [`crate::wasm::WasmType`] into a + /// corresponding [`WasmValue`]. + pub fn parse_value(&mut self, ty: &V::Type) -> Result { + let node = self.parse_node()?; + node.to_wasm_value(ty, self.lex.source()) + } + + /// Parses a WAVE-encoded value into an [`UntypedValue`]. + pub fn parse_raw_value(&mut self) -> Result, ParserError> { + let node = self.parse_node()?; + Ok(UntypedValue::new(self.lex.source(), node)) + } + + /// Parses a function name followed by a WAVE-encoded, parenthesized, + /// comma-separated sequence of values into an [`UntypedFuncCall`]. + pub fn parse_raw_func_call(&mut self) -> Result, ParserError> { + self.advance()?; + let name = self.parse_label()?; + self.advance()?; + self.expect_token(Token::ParenOpen)?; + let params = self.parse_tuple()?; + Ok(UntypedFuncCall::new(self.lex.source(), name, params)) + } + + /// Returns an error if any significant input remains unparsed. + pub fn finish(&mut self) -> Result<(), ParserError> { + match self.lex.clone().spanned().next() { + None => Ok(()), + Some((_, span)) => Err(ParserError::new( + ParserErrorKind::TrailingCharacters, + span.clone(), + )), + } + } + + fn parse_node(&mut self) -> Result { + Ok(match self.advance()? { + Token::Number => self.leaf_node(NodeType::Number), + Token::Char => self.leaf_node(NodeType::Char), + Token::String => self.leaf_node(NodeType::String), + Token::MultilineString => self.leaf_node(NodeType::MultilineString), + Token::ParenOpen => self.parse_tuple()?, + Token::BracketOpen => self.parse_list()?, + Token::BraceOpen => self.parse_record_or_flags()?, + Token::LabelOrKeyword => match Keyword::decode(self.slice()) { + Some(Keyword::True) => self.leaf_node(NodeType::BoolTrue), + Some(Keyword::False) => self.leaf_node(NodeType::BoolFalse), + Some(Keyword::Some) => self.parse_option(NodeType::OptionSome)?, + Some(Keyword::None) => self.parse_option(NodeType::OptionNone)?, + Some(Keyword::Ok) => self.parse_result(NodeType::ResultOk)?, + Some(Keyword::Err) => self.parse_result(NodeType::ResultErr)?, + Some(Keyword::Inf | Keyword::Nan) => self.leaf_node(NodeType::Number), + None => self.parse_label_maybe_payload()?, + }, + Token::BraceClose + | Token::ParenClose + | Token::BracketClose + | Token::Colon + | Token::Comma => return Err(self.unexpected_token()), + }) + } + + fn parse_tuple(&mut self) -> Result { + let start = self.span().start; + let children = self.parse_comma_separated_nodes(Token::ParenClose)?; + let span = start..self.span().end; + if children.is_empty() { + return Err(ParserError::new(ParserErrorKind::EmptyTuple, span)); + } + Ok(Node::new(NodeType::Tuple, span, children)) + } + + fn parse_list(&mut self) -> Result { + let start = self.span().start; + let children = self.parse_comma_separated_nodes(Token::BracketClose)?; + Ok(Node::new(NodeType::List, start..self.span().end, children)) + } + + fn parse_record_or_flags(&mut self) -> Result { + let start = self.span().start; + self.advance()?; + + match self.token() { + // Handle empty record (`{:}`) + Token::Colon => { + self.advance()?; // Advance to `}` + self.expect_token(Token::BraceClose)?; + return Ok(Node::new(NodeType::Record, start..self.span().end, [])); + } + // Handle empty flags (`{}`) + Token::BraceClose => return Ok(Node::new(NodeType::Flags, start..self.span().end, [])), + _ => (), + } + + // Check for a following `:` to distinguish records from flags + if self.next_is(Token::Colon) { + self.finish_record(start) + } else { + self.finish_flags(start) + } + } + + fn finish_record(&mut self, start: usize) -> Result { + let mut seen = HashSet::with_capacity(1); + let mut children = Vec::with_capacity(2); + loop { + // Parse field label + let label = self.parse_label()?; + // Check for duplicate fields + let field = self.slice().trim_start_matches('%'); + if !seen.insert(field) { + return Err(ParserError::with_detail( + ParserErrorKind::DuplicateField, + label.span(), + format!("{field:?}"), + )); + } + // Parse colon + self.advance()?; + self.expect_token(Token::Colon)?; + // Parse value + let value = self.parse_node()?; + children.extend([label, value]); + // Parse comma and/or end of record + if self.advance()? == Token::Comma { + self.advance()?; + } + if self.token() == Token::BraceClose { + break; + } + } + Ok(Node::new( + NodeType::Record, + start..self.span().end, + children, + )) + } + + fn finish_flags(&mut self, start: usize) -> Result { + let mut flags = IndexMap::with_capacity(1); + loop { + // Parse flag label + let label = self.parse_label()?; + // Insert and check for duplicate flags + let span = label.span(); + let flag = self.slice().trim_start_matches('%'); + if flags.insert(flag, label).is_some() { + return Err(ParserError::with_detail( + ParserErrorKind::DuplicateFlag, + span, + format!("{flag:?}"), + )); + } + // Parse comma and/or end of flags + if self.advance()? == Token::Comma { + self.advance()?; + } + if self.token() == Token::BraceClose { + break; + } + } + Ok(Node::new( + NodeType::Flags, + start..self.span().end, + flags.into_values(), + )) + } + + fn parse_label_maybe_payload(&mut self) -> Result { + let start = self.span().start; + let label = self.parse_label()?; + if self.next_is(Token::ParenOpen) { + self.advance()?; + let payload = self.parse_node()?; + self.advance()?; + self.expect_token(Token::ParenClose)?; + Ok(Node::new( + NodeType::VariantWithPayload, + start..self.span().end, + [label, payload], + )) + } else { + Ok(label) + } + } + + fn parse_option(&mut self, ty: NodeType) -> Result { + let start = self.span().start; + let payload = match ty { + NodeType::OptionSome => { + self.advance()?; + self.expect_token(Token::ParenOpen)?; + let payload = self.parse_node()?; + self.advance()?; + self.expect_token(Token::ParenClose)?; + Some(payload) + } + NodeType::OptionNone => None, + _ => unreachable!(), + }; + Ok(Node::new(ty, start..self.span().end, payload)) + } + + fn parse_result(&mut self, ty: NodeType) -> Result { + let start = self.span().start; + let mut payload = None; + if self.next_is(Token::ParenOpen) { + self.advance()?; + self.expect_token(Token::ParenOpen)?; + payload = Some(self.parse_node()?); + self.advance()?; + self.expect_token(Token::ParenClose)?; + } + Ok(Node::new(ty, start..self.span().end, payload)) + } + + fn parse_label(&mut self) -> Result { + self.expect_token(Token::LabelOrKeyword)?; + Ok(self.leaf_node(NodeType::Label)) + } + + fn advance(&mut self) -> Result { + let token = match self.lex.next() { + Some(Ok(token)) => token, + Some(Err(span)) => { + let span = span.unwrap_or_else(|| self.lex.span()); + return Err(ParserError::new(ParserErrorKind::InvalidToken, span)); + } + None => { + return Err(ParserError::new( + ParserErrorKind::UnexpectedEnd, + self.lex.span(), + )); + } + }; + self.curr = Some(token); + Ok(token) + } + + fn token(&self) -> Token { + self.curr.unwrap() + } + + fn span(&self) -> Span { + self.lex.span() + } + + fn slice(&self) -> &'source str { + &self.lex.source()[self.span()] + } + + fn next_is(&mut self, token: Token) -> bool { + self.lex.clone().next().and_then(|res| res.ok()) == Some(token) + } + + fn expect_token(&self, token: Token) -> Result<(), ParserError> { + if self.token() == token { + Ok(()) + } else { + Err(self.unexpected_token()) + } + } + + fn unexpected_token(&self) -> ParserError { + ParserError::with_detail(ParserErrorKind::UnexpectedToken, self.span(), self.token()) + } + + fn parse_comma_separated_nodes(&mut self, end_token: Token) -> Result, ParserError> { + let mut nodes = vec![]; + if self.next_is(end_token) { + self.advance()?; + return Ok(nodes); + } + loop { + nodes.push(self.parse_node()?); + + match self.advance()? { + Token::Comma => { + if self.next_is(end_token) { + self.advance()?; + break; + } + } + _ => { + self.expect_token(end_token)?; + break; + } + } + } + Ok(nodes) + } + + fn leaf_node(&self, ty: NodeType) -> Node { + Node::new(ty, self.span(), []) + } +} + +/// A WAVE parsing error. +#[derive(Debug)] +pub struct ParserError { + kind: ParserErrorKind, + span: Span, + detail: Option, + source: Option>, +} + +impl ParserError { + pub(crate) fn new(kind: ParserErrorKind, span: Span) -> Self { + Self { + kind, + span, + detail: None, + source: None, + } + } + + pub(crate) fn with_detail(kind: ParserErrorKind, span: Span, detail: impl Display) -> Self { + Self { + kind, + span, + detail: Some(detail.to_string()), + source: None, + } + } + + pub(crate) fn with_source( + kind: ParserErrorKind, + span: Span, + source: impl Into>, + ) -> Self { + Self { + kind, + span, + detail: None, + source: Some(source.into()), + } + } + + /// Returns the [`ParserErrorKind`] of this error. + pub fn kind(&self) -> ParserErrorKind { + self.kind + } + + /// Returns the [`Span`] of this error. + pub fn span(&self) -> Span { + self.span.clone() + } + + /// Returns any detail string for this error. + pub fn detail(&self) -> Option<&str> { + self.detail.as_deref() + } +} + +impl Display for ParserError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if let Some(source) = &self.source { + write!(f, "{}: {} at {:?}", self.kind, source, self.span) + } else if let Some(detail) = &self.detail { + write!(f, "{}: {} at {:?}", self.kind, detail, self.span) + } else { + write!(f, "{} at {:?}", self.kind, self.span) + } + } +} + +impl Error for ParserError { + fn source(&self) -> Option<&(dyn Error + 'static)> { + Some(self.source.as_deref()? as _) + } +} + +/// The kind of a WAVE parsing error. +#[derive(Clone, Copy, Debug, PartialEq)] +#[non_exhaustive] +#[allow(missing_docs)] +pub enum ParserErrorKind { + EmptyTuple, + MultipleChars, + InvalidEscape, + InvalidMultilineString, + InvalidParams, + InvalidToken, + InvalidType, + InvalidValue, + TrailingCharacters, + UnexpectedEnd, + UnexpectedToken, + DuplicateField, + DuplicateFlag, + WasmValueError, +} + +impl Display for ParserErrorKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let msg = match self { + ParserErrorKind::EmptyTuple => "empty tuple", + ParserErrorKind::MultipleChars => "multiple characters in char value", + ParserErrorKind::InvalidEscape => "invalid character escape", + ParserErrorKind::InvalidMultilineString => "invalid multiline string", + ParserErrorKind::InvalidParams => "invalid params", + ParserErrorKind::InvalidToken => "invalid token", + ParserErrorKind::InvalidType => "invalid value type", + ParserErrorKind::InvalidValue => "invalid value", + ParserErrorKind::TrailingCharacters => "trailing characters after value", + ParserErrorKind::UnexpectedEnd => "unexpected end of input", + ParserErrorKind::UnexpectedToken => "unexpected token", + ParserErrorKind::DuplicateField => "duplicate field", + ParserErrorKind::DuplicateFlag => "duplicate flag", + ParserErrorKind::WasmValueError => "error converting Wasm value", + }; + write!(f, "{msg}") + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::value::{Type, Value}; + + #[test] + fn parse_option_or_result() { + let ty = Type::option(Type::BOOL); + assert_eq!( + parse_value("some(true)", &ty), + Value::make_option(&ty, Some(Value::make_bool(true))).unwrap() + ); + let ty = Type::result(Some(Type::BOOL), None); + assert_eq!( + parse_value("ok(false)", &ty), + Value::make_result(&ty, Ok(Some(Value::make_bool(false)))).unwrap() + ); + } + + #[test] + fn parse_flat_option_or_result() { + let ty = Type::option(Type::BOOL); + assert_eq!( + parse_value("true", &ty), + Value::make_option(&ty, Some(Value::make_bool(true))).unwrap() + ); + let ty = Type::result(Some(Type::BOOL), None); + assert_eq!( + parse_value("false", &ty), + Value::make_result(&ty, Ok(Some(Value::make_bool(false)))).unwrap() + ); + } + + #[test] + fn parse_record_reordering() { + let ty = Type::record([("red", Type::S32), ("green", Type::CHAR)]).unwrap(); + // Parse the fields in the order they appear in the type. + assert_eq!( + parse_value("{red: 0, green: 'a'}", &ty), + Value::make_record( + &ty, + [ + ("red", Value::make_s32(0)), + ("green", Value::make_char('a')) + ] + ) + .unwrap() + ); + // Parse the fields in reverse order. + assert_eq!( + parse_value("{green: 'a', red: 0}", &ty), + Value::make_record( + &ty, + [ + ("red", Value::make_s32(0)), + ("green", Value::make_char('a')) + ] + ) + .unwrap() + ); + } + + #[test] + fn parse_record_with_optional_fields() { + let field_ty = Type::option(Type::CHAR); + let ty = Type::record([("red", Type::S32), ("green", field_ty.clone())]).unwrap(); + // Explicit `some`. + assert_eq!( + parse_value("{red: 0, green: some('a')}", &ty), + Value::make_record( + &ty, + [ + ("red", Value::make_s32(0)), + ( + "green", + Value::make_option(&field_ty, Some(Value::make_char('a'))).unwrap() + ) + ] + ) + .unwrap() + ); + // Flattened `some`. + assert_eq!( + parse_value("{red: 0, green: 'a'}", &ty), + Value::make_record( + &ty, + [ + ("red", Value::make_s32(0)), + ( + "green", + Value::make_option(&field_ty, Some(Value::make_char('a'))).unwrap() + ) + ] + ) + .unwrap() + ); + // Explicit `none`. + assert_eq!( + parse_value("{red: 0, green: none}", &ty), + Value::make_record( + &ty, + [ + ("red", Value::make_s32(0)), + ("green", Value::make_option(&field_ty, None).unwrap()) + ] + ) + .unwrap() + ); + // Implied `none`. + assert_eq!( + parse_value("{red: 0}", &ty), + Value::make_record( + &ty, + [ + ("red", Value::make_s32(0)), + ("green", Value::make_option(&field_ty, None).unwrap()) + ] + ) + .unwrap() + ); + } + + #[test] + fn parse_flag_reordering() { + let ty = Type::flags(["hot", "cold"]).unwrap(); + // Parse the flags in the order they appear in the type. + assert_eq!( + parse_value("{hot, cold}", &ty), + Value::make_flags(&ty, ["hot", "cold"]).unwrap() + ); + // Parse the flags in reverse order. + assert_eq!( + parse_value("{cold, hot}", &ty), + Value::make_flags(&ty, ["hot", "cold"]).unwrap() + ); + } + + #[test] + fn parse_percent_identifiers() { + let ty = Type::record([("red", Type::S32), ("green", Type::CHAR)]).unwrap(); + // Test identifiers with '%' prefixes. + assert_eq!( + parse_value("{ %red: 0, %green: 'a' }", &ty), + Value::make_record( + &ty, + [ + ("red", Value::make_s32(0)), + ("green", Value::make_char('a')) + ] + ) + .unwrap() + ); + } + + #[test] + fn parse_prefixed_keyword_variant_cases() { + let ty = Type::list( + Type::variant([ + ("true", Some(Type::U8)), + ("false", None), + ("inf", Some(Type::U8)), + ("nan", None), + ("some", Some(Type::U8)), + ("none", None), + ("ok", Some(Type::U8)), + ("err", None), + ]) + .unwrap(), + ); + parse_value( + "[%true(1), %false, %inf(1), %nan, %some(1), %none, %ok(1), %err]", + &ty, + ); + } + + #[test] + fn reject_unprefixed_keyword_enum_cases() { + let cases = ["true", "false", "inf", "nan", "none", "ok", "err"]; + let ty = Type::enum_ty(cases).unwrap(); + for case in cases { + let err = Parser::new(case).parse_value::(&ty).unwrap_err(); + assert_eq!(err.kind(), ParserErrorKind::InvalidType); + } + } + + #[test] + fn parse_unprefixed_keyword_fields() { + let ty = Type::record([ + ("true", Type::U8), + ("false", Type::U8), + ("inf", Type::U8), + ("nan", Type::U8), + ("some", Type::U8), + ("none", Type::U8), + ("ok", Type::U8), + ("err", Type::U8), + ]) + .unwrap(); + parse_value( + "{true: 1, false: 1, inf: 1, nan: 1, some: 1, none: 1, ok: 1, err: 1}", + &ty, + ); + } + + #[test] + fn parse_unprefixed_keyword_flags() { + let ty = Type::flags(["true", "false", "inf", "nan", "some", "none", "ok", "err"]).unwrap(); + parse_value("{true, false, inf, nan, some, none, ok, err}", &ty); + } + + #[test] + fn reject_unprefixed_some_variant_case() { + let ty = Type::variant([("some", Some(Type::U8))]).unwrap(); + let err = Parser::new("some(1)") + .parse_value::(&ty) + .unwrap_err(); + assert_eq!(err.kind(), ParserErrorKind::InvalidType); + } + + fn parse_value(input: &str, ty: &Type) -> Value { + Parser::new(input) + .parse_value(ty) + .unwrap_or_else(|err| panic!("error decoding {input:?}: {err}")) + } +} diff --git a/crates/wasm-wave/src/strings.rs b/crates/wasm-wave/src/strings.rs new file mode 100644 index 0000000000..90ed03d12c --- /dev/null +++ b/crates/wasm-wave/src/strings.rs @@ -0,0 +1,202 @@ +use std::{borrow::Cow, str::Split}; + +use crate::parser::{ParserError, ParserErrorKind}; + +/// An iterator over parsed string "parts". +pub enum StringPartsIter<'a> { + Normal(StringParts<'a>), + Multiline(MultilineStringParts<'a>), +} + +impl<'a> StringPartsIter<'a> { + /// Returns an iterator over string parts for the given substring containing + /// only the _inner_ contents (between quotes) of a single-line string. + /// The given pos is the source position of this substring for error reporting. + pub fn new(src: &'a str, pos: usize) -> Self { + Self::Normal(StringParts { src, pos }) + } + + /// Returns an iterator over string parts for the given substring containing + /// only the _inner_ contents (between quotes) of a multiline string. + /// The given pos is the source position of this substring for error reporting. + pub fn new_multiline(src: &'a str, pos: usize) -> Result { + Ok(Self::Multiline(MultilineStringParts::new(src, pos)?)) + } +} + +impl<'a> Iterator for StringPartsIter<'a> { + type Item = Result, ParserError>; + + fn next(&mut self) -> Option { + match self { + StringPartsIter::Normal(parts) => parts.next(), + StringPartsIter::Multiline(parts) => parts.next(), + } + } +} + +pub struct StringParts<'a> { + src: &'a str, + pos: usize, +} + +impl<'a> StringParts<'a> { + fn next_part(&mut self) -> Result, ParserError> { + let (part, consumed) = match self.src.find('\\') { + Some(0) => { + let (esc_char, esc_len) = unescape(self.src).ok_or_else(|| { + ParserError::new(ParserErrorKind::InvalidEscape, self.pos..self.pos + 1) + })?; + (Cow::Owned(esc_char.to_string()), esc_len) + } + Some(next_esc) => { + let esc_prefix = &self.src[..next_esc]; + (Cow::Borrowed(esc_prefix), esc_prefix.len()) + } + None => (Cow::Borrowed(self.src), self.src.len()), + }; + self.src = &self.src[consumed..]; + self.pos += consumed; + Ok(part) + } +} + +impl<'a> Iterator for StringParts<'a> { + type Item = Result, ParserError>; + + fn next(&mut self) -> Option { + if self.src.is_empty() { + return None; + } + Some(self.next_part()) + } +} + +/// An iterator over parsed string "parts", each of which is either a literal +/// substring of the source or a decoded escape sequence. +pub struct MultilineStringParts<'a> { + curr: StringParts<'a>, + lines: Split<'a, char>, + next_pos: usize, + indent: &'a str, +} + +impl<'a> MultilineStringParts<'a> { + fn new(src: &'a str, pos: usize) -> Result { + let end = pos + src.len(); + + // Strip leading carriage return as part of first newline + let (src, next_pos) = match src.strip_prefix('\r') { + Some(src) => (src, pos + 2), + None => (src, pos + 1), + }; + + let mut lines = src.split('\n'); + + // Remove mandatory final line, using trailing spaces as indent + let indent = lines.next_back().unwrap(); + if indent.contains(|ch| ch != ' ') { + return Err(ParserError::with_detail( + ParserErrorKind::InvalidMultilineString, + end - 3..end, + r#"closing """ must be on a line preceded only by spaces"#, + )); + } + + // Validate mandatory initial empty line + if lines.next() != Some("") { + return Err(ParserError::with_detail( + ParserErrorKind::InvalidMultilineString, + pos..pos + 1, + r#"opening """ must be followed immediately by newline"#, + )); + } + + let mut parts = Self { + curr: StringParts { src: "", pos: 0 }, + lines, + next_pos, + indent, + }; + + // Skip first newline + if let Some(nl) = parts.next().transpose()? { + debug_assert_eq!(nl, "\n"); + } + + Ok(parts) + } +} + +impl<'a> Iterator for MultilineStringParts<'a> { + type Item = Result, ParserError>; + + fn next(&mut self) -> Option { + if self.curr.src.is_empty() { + let next = self.lines.next()?; + + // Update next line position + let pos = self.next_pos; + self.next_pos += next.len() + 1; + + // Strip indent + let Some(src) = next.strip_prefix(self.indent) else { + return Some(Err(ParserError::with_detail( + ParserErrorKind::InvalidMultilineString, + pos..pos + 1, + r#"lines must be indented at least as much as closing """"#, + ))); + }; + let pos = pos + self.indent.len(); + + // Strip trailing carriage return for `\r\n` newlines + let src = src.strip_suffix('\r').unwrap_or(src); + + self.curr = StringParts { src, pos }; + + Some(Ok("\n".into())) + } else { + Some(self.curr.next_part()) + } + } +} + +// Given a substring starting with an escape sequence, returns the decoded char +// and length of the sequence, or None if the sequence is invalid. +pub fn unescape(src: &str) -> Option<(char, usize)> { + let mut chars = src.chars(); + if chars.next() != Some('\\') { + return None; + } + Some(match chars.next()? { + '\\' => ('\\', 2), + '\'' => ('\'', 2), + '"' => ('"', 2), + 't' => ('\t', 2), + 'n' => ('\n', 2), + 'r' => ('\r', 2), + 'u' => { + if chars.next()? != '{' { + return None; + } + let mut val = 0; + let mut digits = 0; + loop { + let ch = chars.next()?; + if ch == '}' { + if digits == 0 { + return None; + } + break; + } + val = (val << 4) | ch.to_digit(16)?; + digits += 1; + if digits > 6 { + return None; + } + } + (char::from_u32(val)?, digits + 4) + } + _ => return None, + }) +} diff --git a/crates/wasm-wave/src/untyped.rs b/crates/wasm-wave/src/untyped.rs new file mode 100644 index 0000000000..6baf3c1deb --- /dev/null +++ b/crates/wasm-wave/src/untyped.rs @@ -0,0 +1,245 @@ +//! Untyped value + +use std::borrow::Cow; + +use crate::{ast::Node, lex::Keyword, parser::ParserError, Parser, WasmValue}; + +/// An UntypedValue is a parsed but not type-checked WAVE value. +#[derive(Clone, Debug)] +pub struct UntypedValue<'source> { + source: Cow<'source, str>, + node: Node, +} + +impl<'source> UntypedValue<'source> { + pub(crate) fn new(source: impl Into>, node: Node) -> Self { + Self { + source: source.into(), + node, + } + } + + /// Parses an untyped value from WAVE. + pub fn parse(source: &'source str) -> Result { + let mut parser = Parser::new(source); + let val = parser.parse_raw_value()?; + parser.finish()?; + Ok(val) + } + + /// Creates an owned value, copying the entire source string if necessary. + pub fn into_owned(self) -> UntypedValue<'static> { + UntypedValue::new(self.source.into_owned(), self.node) + } + + /// Returns the source this value was parsed from. + pub fn source(&self) -> &str { + &self.source + } + + /// Returns this value's root node. + pub fn node(&self) -> &Node { + &self.node + } + + /// Converts this untyped value into the given typed value. + pub fn to_wasm_value(&self, ty: &V::Type) -> Result { + self.node.to_wasm_value(ty, &self.source) + } +} + +impl std::fmt::Display for UntypedValue<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fmt_node(f, &self.node, &self.source) + } +} + +/// An UntypedFuncCall is a parsed but not type-checked WAVE function call. +/// +/// WAVE function calls have the form `()`. +pub struct UntypedFuncCall<'source> { + source: Cow<'source, str>, + name: Node, + params: Node, +} + +impl<'source> UntypedFuncCall<'source> { + pub(crate) fn new(source: impl Into>, name: Node, params: Node) -> Self { + Self { + source: source.into(), + name, + params, + } + } + + /// Parses an untyped function call from WAVE. + pub fn parse(source: &'source str) -> Result { + let mut parser = Parser::new(source); + let call = parser.parse_raw_func_call()?; + parser.finish()?; + Ok(call) + } + + /// Creates an owned function call, copying the entire source string if necessary. + pub fn into_owned(self) -> UntypedFuncCall<'static> { + UntypedFuncCall::new( + self.source.into_owned(), + self.name.clone(), + self.params.clone(), + ) + } + + /// Returns the source this function call was parsed from. + pub fn source(&self) -> &str { + &self.source + } + + /// Returns the function name node. + pub fn name_node(&self) -> &Node { + &self.name + } + + /// Returns the function parameters node. + pub fn params_node(&self) -> &Node { + &self.params + } + + /// Returns the function name. + pub fn name(&self) -> &str { + self.name.slice(&self.source) + } + + /// Converts the untyped parameters into the given types. + /// + /// Any number of trailing option-typed values may be omitted; those will + /// be returned as `none` values. + pub fn to_wasm_params<'types, V: WasmValue + 'static>( + &self, + types: impl IntoIterator, + ) -> Result, ParserError> { + self.params.to_wasm_params(types, self.source()) + } +} + +impl std::fmt::Display for UntypedFuncCall<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.name.slice(&self.source))?; + fmt_node(f, &self.params, &self.source) + } +} + +fn fmt_node(f: &mut impl std::fmt::Write, node: &Node, src: &str) -> std::fmt::Result { + use crate::ast::NodeType::*; + match node.ty() { + BoolTrue | BoolFalse | Number | Char | String | MultilineString | Label => { + f.write_str(node.slice(src)) + } + Tuple => fmt_sequence(f, '(', ')', node.as_tuple()?, src), + List => fmt_sequence(f, '[', ']', node.as_list()?, src), + Record => { + let fields = node.as_record(src)?; + if fields.len() == 0 { + return f.write_str("{:}"); + } + f.write_char('{')?; + for (idx, (name, value)) in node.as_record(src)?.enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + write!(f, "{name}: ")?; + fmt_node(f, value, src)?; + } + f.write_char('}') + } + VariantWithPayload => { + let (label, payload) = node.as_variant(src)?; + if Keyword::decode(label).is_some() { + f.write_char('%')?; + } + fmt_variant(f, label, payload, src) + } + OptionSome => fmt_variant(f, "some", node.as_option()?, src), + OptionNone => fmt_variant(f, "none", None, src), + ResultOk => fmt_variant(f, "ok", node.as_result()?.unwrap(), src), + ResultErr => fmt_variant(f, "err", node.as_result()?.unwrap_err(), src), + Flags => { + f.write_char('{')?; + for (idx, flag) in node.as_flags(src)?.enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + f.write_str(flag)?; + } + f.write_char('}') + } + } +} + +fn fmt_sequence<'a>( + f: &mut impl std::fmt::Write, + open: char, + close: char, + nodes: impl Iterator, + src: &str, +) -> std::fmt::Result { + f.write_char(open)?; + for (idx, node) in nodes.enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + fmt_node(f, node, src)?; + } + f.write_char(close) +} + +fn fmt_variant( + f: &mut impl std::fmt::Write, + case: &str, + payload: Option<&Node>, + src: &str, +) -> std::fmt::Result { + f.write_str(case)?; + if let Some(node) = payload { + f.write_char('(')?; + fmt_node(f, node, src)?; + f.write_char(')')?; + } + Ok(()) +} + +impl From for std::fmt::Error { + fn from(_: ParserError) -> Self { + Self + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn round_trips() { + for src in [ + "true", + "18446744073709551616", + "-9223372036854775808", + "[-3.1415, 0, inf, nan, -inf]", + "['☃', '\\n']", + r#""☃☃☃""#, + "(1, false)", + "{:}", + "{code: red}", + "left(1)", + "[some(1), none]", + "[ok(1), err(2)]", + "[ok, err]", + "%inf(inf)", + "%some", + "%none(none)", + ] { + let val = UntypedValue::parse(src).unwrap(); + let encoded = val.to_string(); + assert_eq!(encoded, src); + } + } +} diff --git a/crates/wasm-wave/src/value/convert.rs b/crates/wasm-wave/src/value/convert.rs new file mode 100644 index 0000000000..236fb7b9f7 --- /dev/null +++ b/crates/wasm-wave/src/value/convert.rs @@ -0,0 +1,260 @@ +use crate::{ + value::ValueEnum, + wasm::{WasmType, WasmTypeKind}, + WasmValue, +}; + +use super::{Type, Value}; + +pub fn from_wasm_type(ty: &impl WasmType) -> Option { + if let Some(ty) = Type::simple(ty.kind()) { + return Some(ty); + } + Some(match ty.kind() { + WasmTypeKind::List => Type::list(from_wasm_type(&ty.list_element_type()?)?), + WasmTypeKind::Record => Type::record( + ty.record_fields() + .map(|(name, ty)| Some((name, from_wasm_type(&ty)?))) + .collect::>>()?, + )?, + WasmTypeKind::Tuple => Type::tuple( + ty.tuple_element_types() + .map(|ty| from_wasm_type(&ty)) + .collect::>>()?, + )?, + WasmTypeKind::Variant => Type::variant( + ty.variant_cases() + .map(|(name, payload)| Some((name, from_optional_wasm_type(payload)?))) + .collect::>>()?, + )?, + WasmTypeKind::Enum => Type::enum_ty(ty.enum_cases())?, + WasmTypeKind::Option => Type::option(from_wasm_type(&ty.option_some_type()?)?), + WasmTypeKind::Result => { + let (ok, err) = ty.result_types()?; + Type::result(from_optional_wasm_type(ok)?, from_optional_wasm_type(err)?) + } + WasmTypeKind::Flags => Type::flags(ty.flags_names())?, + _ => return None, + }) +} + +fn from_optional_wasm_type(ty: Option) -> Option> { + Some(match ty { + Some(ty) => Some(from_wasm_type(&ty)?), + None => None, + }) +} + +trait ValueTyped { + fn value_type() -> Type; +} + +macro_rules! impl_primitives { + ($Self:ty, $(($case:ident, $ty:ty)),*) => { + $( + impl ValueTyped for $ty { + fn value_type() -> Type { + Type::must_simple(WasmTypeKind::$case) + } + } + + impl From<$ty> for $Self { + fn from(value: $ty) -> Self { + Self(ValueEnum::$case(value)) + } + } + )* + }; +} + +impl_primitives!( + Value, + (Bool, bool), + (S8, i8), + (S16, i16), + (S32, i32), + (S64, i64), + (U8, u8), + (U16, u16), + (U32, u32), + (U64, u64), + (Float32, f32), + (Float64, f64), + (Char, char) +); + +impl ValueTyped for String { + fn value_type() -> Type { + Type::STRING + } +} + +impl From for Value { + fn from(value: String) -> Self { + Self(ValueEnum::String(value.into())) + } +} + +impl<'a> ValueTyped for &'a str { + fn value_type() -> Type { + String::value_type() + } +} + +impl<'a> From<&'a str> for Value { + fn from(value: &'a str) -> Self { + value.to_string().into() + } +} + +impl ValueTyped for [T; N] { + fn value_type() -> Type { + Type::list(T::value_type()) + } +} + +impl> From<[T; N]> for Value { + fn from(values: [T; N]) -> Self { + let ty = Vec::::value_type(); + let values = values.into_iter().map(Into::into); + Value::make_list(&ty, values).unwrap() + } +} + +impl ValueTyped for Vec { + fn value_type() -> Type { + Type::list(T::value_type()) + } +} + +impl> From> for Value { + fn from(values: Vec) -> Self { + let ty = Vec::::value_type(); + let values = values.into_iter().map(Into::into); + Value::make_list(&ty, values).unwrap() + } +} + +impl ValueTyped for Option { + fn value_type() -> Type { + Type::option(T::value_type()) + } +} + +impl> From> for Value { + fn from(value: Option) -> Self { + let ty = Option::::value_type(); + Value::make_option(&ty, value.map(Into::into)).unwrap() + } +} + +impl ValueTyped for Result { + fn value_type() -> Type { + Type::result(Some(T::value_type()), Some(U::value_type())) + } +} + +impl, U: ValueTyped + Into> From> for Value { + fn from(value: Result) -> Self { + let ty = Result::::value_type(); + let value = match value { + Ok(ok) => Ok(Some(ok.into())), + Err(err) => Err(Some(err.into())), + }; + Value::make_result(&ty, value).unwrap() + } +} + +macro_rules! impl_tuple { + ($(($($var:ident),*)),*) => { + $( + impl<$($var: ValueTyped),*> ValueTyped for ($($var),*,) { + fn value_type() -> Type { + Type::tuple(vec![$($var::value_type()),*]).unwrap() + } + } + + #[allow(non_snake_case)] + impl<$($var: ValueTyped + Into),*> From<($($var),*,)> for Value { + fn from(($($var),*,): ($($var),*,)) -> Value { + let ty = <($($var),*,)>::value_type(); + $( + let $var = $var.into(); + )* + Value::make_tuple(&ty, vec![$($var),*]).unwrap() + } + } + + )* + }; +} + +impl_tuple!( + (T1), + (T1, T2), + (T1, T2, T3), + (T1, T2, T3, T4), + (T1, T2, T3, T4, T5), + (T1, T2, T3, T4, T5, T6), + (T1, T2, T3, T4, T5, T6, T7), + (T1, T2, T3, T4, T5, T6, T7, T8), + (T1, T2, T3, T4, T5, T6, T7, T8, T9), + (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10), + (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11), + (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12), + (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13), + (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14), + (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15), + (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, T11, T12, T13, T14, T15, T16) +); + +#[cfg(test)] +mod tests { + use crate::value::{Type, Value}; + + #[test] + fn type_conversion_round_trips() { + for ty in [ + Type::BOOL, + Type::U8, + Type::FLOAT32, + Type::STRING, + Type::list(Type::BOOL), + Type::record([("a", Type::BOOL)]).unwrap(), + Type::tuple([Type::BOOL]).unwrap(), + Type::variant([("a", None), ("b", Some(Type::BOOL))]).unwrap(), + Type::enum_ty(["north", "south"]).unwrap(), + Type::option(Type::BOOL), + Type::result(Some(Type::BOOL), None), + Type::flags(["read", "write"]).unwrap(), + ] { + let got = Type::from_wasm_type(&ty).unwrap(); + assert_eq!(got, ty); + } + } + + #[test] + fn value_conversions() { + for (val, expect) in [ + (1u8.into(), "1"), + ((-123i8).into(), "-123"), + (f32::NAN.into(), "nan"), + (f64::NEG_INFINITY.into(), "-inf"), + ('x'.into(), "'x'"), + ("str".into(), "\"str\""), + (vec![1, 2, 3].into(), "[1, 2, 3]"), + ([1, 2, 3].into(), "[1, 2, 3]"), + (['a'; 3].into(), "['a', 'a', 'a']"), + (Some(1).into(), "some(1)"), + (None::.into(), "none"), + (Ok::(1).into(), "ok(1)"), + (Err::("oops".into()).into(), "err(\"oops\")"), + ((1,).into(), "(1)"), + ((1, "str", [9; 2]).into(), "(1, \"str\", [9, 9])"), + ] { + let val: Value = val; + let got = crate::to_string(&val).unwrap(); + assert_eq!(got, expect); + } + } +} diff --git a/crates/wasm-wave/src/value/func.rs b/crates/wasm-wave/src/value/func.rs new file mode 100644 index 0000000000..bfaf821578 --- /dev/null +++ b/crates/wasm-wave/src/value/func.rs @@ -0,0 +1,63 @@ +use crate::wasm::WasmFunc; + +use super::{Type, WasmValueError}; + +/// A FuncType represents the parameter and result type(s) of a Wasm func. +#[derive(Clone, PartialEq)] +pub struct FuncType { + params: Vec<(String, Type)>, + results: Vec<(String, Type)>, +} + +impl FuncType { + /// Returns a new FuncType with the given param and result type(s). + /// Returns an error if the resulting func type would be invalid in the + /// component model, e.g. has any unnamed results with more than one + /// result type. + pub fn new( + params: impl Into>, + results: impl Into>, + ) -> Result { + let params = params.into(); + if params.iter().any(|(name, _)| name.is_empty()) { + return Err(WasmValueError::Other("func params must be named".into())); + } + let results = results.into(); + if results.len() > 1 && results.iter().any(|(name, _)| name.is_empty()) { + return Err(WasmValueError::Other( + "funcs with more than one result must have all results named".into(), + )); + } + Ok(Self { params, results }) + } +} + +impl WasmFunc for FuncType { + type Type = Type; + + fn params(&self) -> Box + '_> { + Box::new(self.params.iter().map(|(_, ty)| ty.clone())) + } + + fn param_names(&self) -> Box> + '_> { + Box::new(self.params.iter().map(|(name, _)| name.into())) + } + + fn results(&self) -> Box + '_> { + Box::new(self.results.iter().map(|(_, ty)| ty.clone())) + } + + fn result_names(&self) -> Box> + '_> { + Box::new( + self.results + .iter() + .flat_map(|(name, _)| (!name.is_empty()).then_some(name.into())), + ) + } +} + +impl std::fmt::Display for FuncType { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + crate::wasm::DisplayFunc(self.clone()).fmt(f) + } +} diff --git a/crates/wasm-wave/src/value/mod.rs b/crates/wasm-wave/src/value/mod.rs new file mode 100644 index 0000000000..1fb2cc42b9 --- /dev/null +++ b/crates/wasm-wave/src/value/mod.rs @@ -0,0 +1,553 @@ +//! Value enum for WAVE values. + +mod convert; +#[cfg(test)] +mod tests; +mod ty; + +mod func; +#[cfg(feature = "wit")] +mod wit; + +#[cfg(feature = "wit")] +pub use wit::{resolve_wit_func_type, resolve_wit_type}; + +use std::{borrow::Cow, collections::HashMap, sync::Arc}; + +use crate::{ + canonicalize_nan32, canonicalize_nan64, + wasm::{ + ensure_type_kind, maybe_unwrap_type, unwrap_val, WasmType, WasmTypeKind, WasmValue, + WasmValueError, + }, +}; + +use self::ty::{ + EnumType, FlagsType, ListType, OptionType, RecordType, ResultType, TupleType, TypeEnum, + VariantType, +}; + +pub use self::func::FuncType; +pub use self::ty::Type; + +/// A Value is a WAVE value, and implements [`WasmValue`]. +#[derive(Debug, Clone, PartialEq)] +pub struct Value(ValueEnum); + +#[derive(Debug, Clone, PartialEq)] +pub(super) enum ValueEnum { + Bool(bool), + S8(i8), + U8(u8), + S16(i16), + U16(u16), + S32(i32), + U32(u32), + S64(i64), + U64(u64), + Float32(f32), + Float64(f64), + Char(char), + String(Box), + List(List), + Record(Record), + Tuple(Tuple), + Variant(Variant), + Enum(Enum), + Option(OptionValue), + Result(ResultValue), + Flags(Flags), +} + +#[derive(Debug, Clone, PartialEq)] +#[doc(hidden)] +pub struct List { + ty: Arc, + elements: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +#[doc(hidden)] +pub struct Record { + ty: Arc, + fields: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +#[doc(hidden)] +pub struct Tuple { + ty: Arc, + elements: Vec, +} + +#[derive(Debug, Clone, PartialEq)] +#[doc(hidden)] +pub struct Variant { + ty: Arc, + case: usize, + payload: Option>, +} + +#[derive(Debug, Clone, PartialEq)] +#[doc(hidden)] +pub struct Enum { + ty: Arc, + case: usize, +} + +#[derive(Debug, Clone, PartialEq)] +#[doc(hidden)] +pub struct OptionValue { + ty: Arc, + value: Option>, +} + +#[derive(Debug, Clone, PartialEq)] +#[doc(hidden)] +pub struct ResultValue { + ty: Arc, + value: Result>, Option>>, +} + +#[derive(Debug, Clone, PartialEq)] +#[doc(hidden)] +pub struct Flags { + ty: Arc, + flags: Vec, +} + +macro_rules! impl_primitives { + ($Self:ident, $(($case:ident, $ty:ty, $make:ident, $unwrap:ident)),*) => { + $( + fn $make(val: $ty) -> $Self { + $Self(ValueEnum::$case(val)) + } + + fn $unwrap(&self) -> $ty { + *unwrap_val!(&self.0, ValueEnum::$case, stringify!($case)) + } + )* + }; +} + +impl WasmValue for Value { + type Type = Type; + + fn kind(&self) -> WasmTypeKind { + match &self.0 { + ValueEnum::Bool(_) => WasmTypeKind::Bool, + ValueEnum::S8(_) => WasmTypeKind::S8, + ValueEnum::S16(_) => WasmTypeKind::S16, + ValueEnum::S32(_) => WasmTypeKind::S32, + ValueEnum::S64(_) => WasmTypeKind::S64, + ValueEnum::U8(_) => WasmTypeKind::U8, + ValueEnum::U16(_) => WasmTypeKind::U16, + ValueEnum::U32(_) => WasmTypeKind::U32, + ValueEnum::U64(_) => WasmTypeKind::U64, + ValueEnum::Float32(_) => WasmTypeKind::Float32, + ValueEnum::Float64(_) => WasmTypeKind::Float64, + ValueEnum::Char(_) => WasmTypeKind::Char, + ValueEnum::String(_) => WasmTypeKind::String, + ValueEnum::List(_) => WasmTypeKind::List, + ValueEnum::Record(_) => WasmTypeKind::Record, + ValueEnum::Tuple(_) => WasmTypeKind::Tuple, + ValueEnum::Variant(_) => WasmTypeKind::Variant, + ValueEnum::Enum(_) => WasmTypeKind::Enum, + ValueEnum::Option(_) => WasmTypeKind::Option, + ValueEnum::Result(_) => WasmTypeKind::Result, + ValueEnum::Flags(_) => WasmTypeKind::Flags, + } + } + + impl_primitives!( + Self, + (Bool, bool, make_bool, unwrap_bool), + (S8, i8, make_s8, unwrap_s8), + (S16, i16, make_s16, unwrap_s16), + (S32, i32, make_s32, unwrap_s32), + (S64, i64, make_s64, unwrap_s64), + (U8, u8, make_u8, unwrap_u8), + (U16, u16, make_u16, unwrap_u16), + (U32, u32, make_u32, unwrap_u32), + (U64, u64, make_u64, unwrap_u64), + (Char, char, make_char, unwrap_char) + ); + + fn make_float32(val: f32) -> Self { + let val = canonicalize_nan32(val); + Self(ValueEnum::Float32(val)) + } + + fn make_float64(val: f64) -> Self { + let val = canonicalize_nan64(val); + Self(ValueEnum::Float64(val)) + } + + fn make_string(val: std::borrow::Cow) -> Self { + Self(ValueEnum::String(val.into())) + } + + fn make_list( + ty: &Self::Type, + vals: impl IntoIterator, + ) -> Result { + ensure_type_kind(ty, WasmTypeKind::List)?; + let element_type = ty.list_element_type().unwrap(); + let elements = vals + .into_iter() + .map(|v| check_type(&element_type, v)) + .collect::>()?; + let ty = maybe_unwrap_type!(&ty.0, TypeEnum::List).unwrap().clone(); + Ok(Self(ValueEnum::List(List { ty, elements }))) + } + + fn make_record<'a>( + ty: &Self::Type, + fields: impl IntoIterator, + ) -> Result { + ensure_type_kind(ty, WasmTypeKind::Record)?; + let mut field_vals: HashMap<_, _> = fields.into_iter().collect(); + let mut fields = Vec::with_capacity(field_vals.len()); + for (name, ty) in ty.record_fields() { + let val = field_vals + .remove(&*name) + .ok_or_else(|| WasmValueError::MissingField(name.into()))?; + fields.push(check_type(&ty, val)?); + } + if let Some(unknown) = field_vals.into_keys().next() { + return Err(WasmValueError::UnknownField(unknown.into())); + } + let ty = maybe_unwrap_type!(&ty.0, TypeEnum::Record).unwrap().clone(); + Ok(Self(ValueEnum::Record(Record { ty, fields }))) + } + + fn make_tuple( + ty: &Self::Type, + vals: impl IntoIterator, + ) -> Result { + ensure_type_kind(ty, WasmTypeKind::Tuple)?; + let types = ty.tuple_element_types().collect::>(); + let elements = Vec::from_iter(vals); + if types.len() != elements.len() { + return Err(WasmValueError::WrongNumberOfTupleValues { + want: types.len(), + got: elements.len(), + }); + } + for (ty, val) in types.iter().zip(&elements) { + check_type2(ty, val)?; + } + let ty = maybe_unwrap_type!(&ty.0, TypeEnum::Tuple).unwrap().clone(); + Ok(Self(ValueEnum::Tuple(Tuple { ty, elements }))) + } + + fn make_variant( + ty: &Self::Type, + case_name: &str, + val: Option, + ) -> Result { + ensure_type_kind(ty, WasmTypeKind::Variant)?; + let (case, payload_type) = ty + .variant_cases() + .enumerate() + .find_map(|(idx, (name, ty))| (name == case_name).then_some((idx, ty))) + .ok_or_else(|| WasmValueError::UnknownCase(case_name.into()))?; + let payload = check_payload_type(case_name, &payload_type, val)?; + let ty = maybe_unwrap_type!(&ty.0, TypeEnum::Variant) + .unwrap() + .clone(); + Ok(Self(ValueEnum::Variant(Variant { ty, case, payload }))) + } + + fn make_enum(ty: &Self::Type, case: &str) -> Result { + ensure_type_kind(ty, WasmTypeKind::Enum)?; + let case = ty + .enum_cases() + .position(|name| name == case) + .ok_or_else(|| WasmValueError::UnknownCase(case.into()))?; + let ty = maybe_unwrap_type!(&ty.0, TypeEnum::Enum).unwrap().clone(); + Ok(Self(ValueEnum::Enum(Enum { ty, case }))) + } + + fn make_option(ty: &Self::Type, val: Option) -> Result { + ensure_type_kind(ty, WasmTypeKind::Option)?; + let value = match val { + Some(val) => Some(Box::new(check_type(&ty.option_some_type().unwrap(), val)?)), + None => None, + }; + let ty = maybe_unwrap_type!(&ty.0, TypeEnum::Option).unwrap().clone(); + Ok(Self(ValueEnum::Option(OptionValue { ty, value }))) + } + + fn make_result( + ty: &Self::Type, + val: Result, Option>, + ) -> Result { + ensure_type_kind(ty, WasmTypeKind::Result)?; + let (ok_type, err_type) = ty.result_types().unwrap(); + let value = match val { + Ok(ok) => Ok(check_payload_type("ok", &ok_type, ok)?), + Err(err) => Err(check_payload_type("err", &err_type, err)?), + }; + let ty = maybe_unwrap_type!(&ty.0, TypeEnum::Result).unwrap().clone(); + Ok(Self(ValueEnum::Result(ResultValue { ty, value }))) + } + + fn make_flags<'a>( + ty: &Self::Type, + names: impl IntoIterator, + ) -> Result { + ensure_type_kind(ty, WasmTypeKind::Flags)?; + let flag_names = ty.flags_names().collect::>(); + let mut flags = names + .into_iter() + .map(|name| { + flag_names + .iter() + .position(|flag| flag == name) + .ok_or_else(|| WasmValueError::UnknownCase(name.into())) + }) + .collect::, WasmValueError>>()?; + // Flags values don't logically contain an ordering of the flags. Sort + // the flags values so that equivalent flags values compare equal. + flags.sort(); + let ty = maybe_unwrap_type!(&ty.0, TypeEnum::Flags).unwrap().clone(); + Ok(Self(ValueEnum::Flags(Flags { ty, flags }))) + } + + fn unwrap_float32(&self) -> f32 { + let val = *unwrap_val!(&self.0, ValueEnum::Float32, "float32"); + canonicalize_nan32(val) + } + + fn unwrap_float64(&self) -> f64 { + let val = *unwrap_val!(&self.0, ValueEnum::Float64, "float64"); + canonicalize_nan64(val) + } + + fn unwrap_string(&self) -> std::borrow::Cow { + unwrap_val!(&self.0, ValueEnum::String, "string") + .as_ref() + .into() + } + fn unwrap_list(&self) -> Box> + '_> { + let list = unwrap_val!(&self.0, ValueEnum::List, "list"); + Box::new(list.elements.iter().map(cow)) + } + fn unwrap_record(&self) -> Box, Cow)> + '_> { + let record = unwrap_val!(&self.0, ValueEnum::Record, "record"); + Box::new( + record + .ty + .fields + .iter() + .map(|(name, _)| cow(name.as_ref())) + .zip(record.fields.iter().map(cow)), + ) + } + fn unwrap_tuple(&self) -> Box> + '_> { + let tuple = unwrap_val!(&self.0, ValueEnum::Tuple, "tuple"); + Box::new(tuple.elements.iter().map(cow)) + } + fn unwrap_variant(&self) -> (Cow, Option>) { + let variant = unwrap_val!(&self.0, ValueEnum::Variant, "variant"); + let (ref name, _) = variant.ty.cases[variant.case]; + (cow(name.as_ref()), variant.payload.as_deref().map(cow)) + } + fn unwrap_enum(&self) -> Cow { + let enum_ = unwrap_val!(&self.0, ValueEnum::Enum, "enum"); + cow(enum_.ty.cases[enum_.case].as_ref()) + } + fn unwrap_option(&self) -> Option> { + unwrap_val!(&self.0, ValueEnum::Option, "option") + .value + .as_ref() + .map(|v| cow(v.as_ref())) + } + fn unwrap_result(&self) -> Result>, Option>> { + match &unwrap_val!(&self.0, ValueEnum::Result, "result").value { + Ok(val) => Ok(val.as_deref().map(cow)), + Err(val) => Err(val.as_deref().map(cow)), + } + } + fn unwrap_flags(&self) -> Box> + '_> { + let flags = unwrap_val!(&self.0, ValueEnum::Flags, "flags"); + Box::new( + flags + .flags + .iter() + .map(|idx| cow(flags.ty.flags[*idx].as_ref())), + ) + } +} + +fn cow(t: &T) -> Cow { + Cow::Borrowed(t) +} + +fn check_type(expected: &Type, val: Value) -> Result { + check_type2(expected, &val)?; + Ok(val) +} + +fn check_type2(expected: &Type, val: &Value) -> Result<(), WasmValueError> { + let wrong_value_type = + || -> Result<(), WasmValueError> { Err(WasmValueError::wrong_value_type(expected, val)) }; + + match (&val.0, expected) { + (ValueEnum::Bool(_), &Type::BOOL) => {} + (ValueEnum::S8(_), &Type::S8) => {} + (ValueEnum::S16(_), &Type::S16) => {} + (ValueEnum::S32(_), &Type::S32) => {} + (ValueEnum::S64(_), &Type::S64) => {} + (ValueEnum::U8(_), &Type::U8) => {} + (ValueEnum::U16(_), &Type::U16) => {} + (ValueEnum::U32(_), &Type::U32) => {} + (ValueEnum::U64(_), &Type::U64) => {} + (ValueEnum::Float32(_), &Type::FLOAT32) => {} + (ValueEnum::Float64(_), &Type::FLOAT64) => {} + (ValueEnum::Char(_), &Type::CHAR) => {} + (ValueEnum::String(_), &Type::STRING) => {} + (ValueEnum::List(list), _) => { + if let TypeEnum::List(list_type) = &expected.0 { + let ty = &list_type.element; + if ty != &list.ty.element { + return wrong_value_type(); + } + for v in &list.elements { + check_type2(ty, v)?; + } + } else { + return wrong_value_type(); + } + } + (ValueEnum::Record(record), _) => { + if let TypeEnum::Record(record_type) = &expected.0 { + if record.ty.as_ref() != record_type.as_ref() { + return wrong_value_type(); + } + let expected_element_types = &record_type.fields; + if expected_element_types != &record.ty.fields { + return wrong_value_type(); + } + if expected_element_types.len() != record.fields.len() { + return wrong_value_type(); + } + + for (field_ty, val) in expected_element_types.as_ref().iter().zip(&record.fields) { + check_type2(&field_ty.1, val)?; + } + } else { + return wrong_value_type(); + } + } + (ValueEnum::Tuple(tuple), _) => { + if let TypeEnum::Tuple(tuple_type) = &expected.0 { + let expected_element_types = &tuple_type.elements; + if expected_element_types != &tuple.ty.elements { + return wrong_value_type(); + } + if expected_element_types.len() != tuple.elements.len() { + return wrong_value_type(); + } + + for (ty, val) in expected_element_types.as_ref().iter().zip(&tuple.elements) { + check_type2(ty, val)?; + } + } else { + return wrong_value_type(); + } + } + (ValueEnum::Variant(variant), _) => { + if let TypeEnum::Variant(variant_type) = &expected.0 { + if variant.ty.cases != variant_type.cases { + return wrong_value_type(); + } + if variant.case >= variant.ty.cases.len() { + return wrong_value_type(); + } + match (&variant.ty.cases[variant.case].1, &variant.payload) { + (None, None) => {} + (Some(t), Some(v)) => check_type2(t, v)?, + _ => return wrong_value_type(), + } + } else { + return wrong_value_type(); + } + } + (ValueEnum::Enum(enm), _) => { + if let TypeEnum::Enum(enum_type) = &expected.0 { + if enm.case >= enm.ty.cases.len() { + return wrong_value_type(); + } + if enm.ty.cases != enum_type.cases { + return wrong_value_type(); + } + } else { + return wrong_value_type(); + } + } + (ValueEnum::Option(option), _) => { + if let TypeEnum::Option(option_type) = &expected.0 { + if option.ty.as_ref().some != option_type.some { + return wrong_value_type(); + } + if let Some(v) = option.value.as_ref() { + check_type2(&option_type.some, v)?; + } + } else { + return wrong_value_type(); + } + } + (ValueEnum::Result(result), _) => { + if let TypeEnum::Result(result_type) = &expected.0 { + if result.ty.as_ref() != result_type.as_ref() { + return wrong_value_type(); + } + match &result.value { + Ok(o) => match (&o, &result_type.ok) { + (None, None) => {} + (Some(v), Some(t)) => check_type2(t, v)?, + _ => return wrong_value_type(), + }, + Err(e) => match (&e, &result_type.err) { + (None, None) => {} + (Some(v), Some(t)) => check_type2(t, v)?, + _ => return wrong_value_type(), + }, + } + } else { + return wrong_value_type(); + } + } + (ValueEnum::Flags(flags), _) => { + if let TypeEnum::Flags(flags_type) = &expected.0 { + if flags.ty.as_ref() != flags_type.as_ref() { + return wrong_value_type(); + } + for flag in &flags.flags { + if *flag >= flags.ty.as_ref().flags.len() { + return wrong_value_type(); + } + } + } else { + return wrong_value_type(); + } + } + (_, _) => return wrong_value_type(), + }; + Ok(()) +} + +fn check_payload_type( + name: &str, + expected: &Option, + val: Option, +) -> Result>, WasmValueError> { + match (expected, val) { + (Some(payload_type), Some(val)) => Ok(Some(Box::new(check_type(payload_type, val)?))), + (None, None) => Ok(None), + (Some(_), None) => Err(WasmValueError::MissingPayload(name.into())), + (None, Some(_)) => Err(WasmValueError::UnexpectedPayload(name.into())), + } +} diff --git a/crates/wasm-wave/src/value/tests.rs b/crates/wasm-wave/src/value/tests.rs new file mode 100644 index 0000000000..0a4dc9e8e3 --- /dev/null +++ b/crates/wasm-wave/src/value/tests.rs @@ -0,0 +1,154 @@ +use crate::WasmValue; + +use super::{Type, Value}; + +#[test] +fn simple_value_round_trips() { + for val in [ + Value::make_bool(true), + Value::make_u8(u8::MAX), + Value::make_u16(u16::MAX), + Value::make_u32(u32::MAX), + Value::make_u64(u64::MAX), + Value::make_s8(i8::MIN), + Value::make_s16(i16::MIN), + Value::make_s32(i32::MIN), + Value::make_s64(i64::MIN), + Value::make_char('☃'), + Value::make_string("str".into()), + ] { + test_value_round_trip(val) + } +} + +#[test] +fn float_round_trips() { + for (float32, float64) in [ + (0.0, 0.0), + (f32::MIN, f64::MIN), + (f32::MIN_POSITIVE, f64::MIN_POSITIVE), + (f32::MAX, f64::MAX), + (f32::EPSILON, f64::EPSILON), + (f32::INFINITY, f64::INFINITY), + (f32::NEG_INFINITY, f64::NEG_INFINITY), + ] { + test_value_round_trip(Value::make_float32(float32)); + test_value_round_trip(Value::make_float64(float64)); + } +} + +#[test] +fn list_round_trips() { + let ty = Type::list(Type::U8); + test_value_round_trip(Value::make_list(&ty, []).unwrap()); + test_value_round_trip(Value::make_list(&ty, [Value::make_u8(1)]).unwrap()); + test_value_round_trip(Value::make_list(&ty, [Value::make_u8(1), Value::make_u8(2)]).unwrap()); +} + +#[test] +fn record_round_trip() { + let option_ty = Type::option(Type::U8); + let record_ty = + Type::record([("field-a", Type::BOOL), ("field-b", option_ty.clone())]).unwrap(); + let record_val = Value::make_record( + &record_ty, + [ + ("field-a", Value::make_bool(true)), + ("field-b", Value::make_option(&option_ty, None).unwrap()), + ], + ) + .unwrap(); + test_value_round_trip(record_val) +} + +#[test] +fn tuple_round_trip() { + let ty = Type::tuple([Type::BOOL, Type::U8]).unwrap(); + let val = Value::make_tuple(&ty, [Value::make_bool(true), Value::make_u8(1)]).unwrap(); + test_value_round_trip(val); +} + +#[test] +fn variant_round_trips() { + let ty = Type::variant([("off", None), ("on", Some(Type::U8))]).unwrap(); + test_value_round_trip(Value::make_variant(&ty, "off", None).unwrap()); + test_value_round_trip(Value::make_variant(&ty, "on", Some(Value::make_u8(1))).unwrap()); +} + +#[test] +fn enum_round_trips() { + let ty = Type::enum_ty(["north", "east", "south", "west"]).unwrap(); + test_value_round_trip(Value::make_enum(&ty, "north").unwrap()); + test_value_round_trip(Value::make_enum(&ty, "south").unwrap()); +} + +#[test] +fn option_round_trips() { + let ty = Type::option(Type::U8); + test_value_round_trip(Value::make_option(&ty, Some(Value::make_u8(1))).unwrap()); + test_value_round_trip(Value::make_option(&ty, None).unwrap()); +} + +#[test] +fn result_round_trips() { + let no_payloads = Type::result(None, None); + let both_payloads = Type::result(Some(Type::U8), Some(Type::STRING)); + let ok_only = Type::result(Some(Type::U8), None); + let err_only = Type::result(None, Some(Type::STRING)); + for (ty, payload) in [ + (&no_payloads, Ok(None)), + (&no_payloads, Err(None)), + (&both_payloads, Ok(Some(Value::make_u8(1)))), + (&both_payloads, Err(Some(Value::make_string("oops".into())))), + (&ok_only, Ok(Some(Value::make_u8(1)))), + (&ok_only, Err(None)), + (&err_only, Ok(None)), + (&err_only, Err(Some(Value::make_string("oops".into())))), + ] { + let val = Value::make_result(ty, payload).unwrap(); + test_value_round_trip(val); + } +} + +#[test] +fn flags_round_trips() { + let ty = Type::flags(["read", "write", "execute"]).unwrap(); + test_value_round_trip(Value::make_flags(&ty, []).unwrap()); + test_value_round_trip(Value::make_flags(&ty, ["write"]).unwrap()); + test_value_round_trip(Value::make_flags(&ty, ["read", "execute"]).unwrap()); +} + +fn local_ty(val: &Value) -> Type { + use crate::value::{TypeEnum, ValueEnum}; + match &val.0 { + ValueEnum::Bool(_) => Type::BOOL, + ValueEnum::S8(_) => Type::S8, + ValueEnum::S16(_) => Type::S16, + ValueEnum::S32(_) => Type::S32, + ValueEnum::S64(_) => Type::S64, + ValueEnum::U8(_) => Type::U8, + ValueEnum::U16(_) => Type::U16, + ValueEnum::U32(_) => Type::U32, + ValueEnum::U64(_) => Type::U64, + ValueEnum::Float32(_) => Type::FLOAT32, + ValueEnum::Float64(_) => Type::FLOAT64, + ValueEnum::Char(_) => Type::CHAR, + ValueEnum::String(_) => Type::STRING, + ValueEnum::List(inner) => Type(TypeEnum::List(inner.ty.clone())), + ValueEnum::Record(inner) => Type(TypeEnum::Record(inner.ty.clone())), + ValueEnum::Tuple(inner) => Type(TypeEnum::Tuple(inner.ty.clone())), + ValueEnum::Variant(inner) => Type(TypeEnum::Variant(inner.ty.clone())), + ValueEnum::Enum(inner) => Type(TypeEnum::Enum(inner.ty.clone())), + ValueEnum::Option(inner) => Type(TypeEnum::Option(inner.ty.clone())), + ValueEnum::Result(inner) => Type(TypeEnum::Result(inner.ty.clone())), + ValueEnum::Flags(inner) => Type(TypeEnum::Flags(inner.ty.clone())), + } +} + +fn test_value_round_trip(val: Value) { + let ty = local_ty(&val); + let serialized = crate::to_string(&val).unwrap(); + let deserialized: Value = crate::from_str(&ty, &serialized) + .unwrap_or_else(|err| panic!("failed to deserialize {serialized:?}: {err}")); + assert_eq!(deserialized, val, "for {val:?}"); +} diff --git a/crates/wasm-wave/src/value/ty.rs b/crates/wasm-wave/src/value/ty.rs new file mode 100644 index 0000000000..5c01fa9121 --- /dev/null +++ b/crates/wasm-wave/src/value/ty.rs @@ -0,0 +1,267 @@ +use std::{borrow::Cow, sync::Arc}; + +use crate::wasm::{maybe_unwrap_type, WasmType, WasmTypeKind}; + +/// The [`WasmType`] of a [`Value`](super::Value). +#[derive(Clone, Debug, PartialEq, Eq)] +pub struct Type(pub(super) TypeEnum); + +#[derive(Clone, Debug, PartialEq, Eq)] +pub(super) enum TypeEnum { + Simple(SimpleType), + List(Arc), + Record(Arc), + Tuple(Arc), + Variant(Arc), + Enum(Arc), + Option(Arc), + Result(Arc), + Flags(Arc), +} + +#[allow(missing_docs)] +impl Type { + pub const BOOL: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::Bool))); + pub const S8: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::S8))); + pub const S16: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::S16))); + pub const S32: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::S32))); + pub const S64: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::S64))); + pub const U8: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::U8))); + pub const U16: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::U16))); + pub const U32: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::U32))); + pub const U64: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::U64))); + pub const FLOAT32: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::Float32))); + pub const FLOAT64: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::Float64))); + pub const CHAR: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::Char))); + pub const STRING: Self = Self(TypeEnum::Simple(SimpleType(WasmTypeKind::String))); + + /// Returns the simple type of the given `kind`. Returns None if the kind + /// represents a parameterized type. + pub fn simple(kind: WasmTypeKind) -> Option { + is_simple(kind).then_some(Self(TypeEnum::Simple(SimpleType(kind)))) + } + + #[doc(hidden)] + pub const fn must_simple(kind: WasmTypeKind) -> Self { + if !is_simple(kind) { + panic!("kind is not simple"); + } + Self(TypeEnum::Simple(SimpleType(kind))) + } + + /// Returns a list type with the given element type. + pub fn list(element_type: impl Into) -> Self { + let element = element_type.into(); + Self(TypeEnum::List(Arc::new(ListType { element }))) + } + + /// Returns a record type with the given field types. Returns None if + /// `fields` is empty. + pub fn record>>( + field_types: impl IntoIterator, + ) -> Option { + let fields = field_types + .into_iter() + .map(|(name, ty)| (name.into(), ty)) + .collect::>(); + if fields.is_empty() { + return None; + } + Some(Self(TypeEnum::Record(Arc::new(RecordType { fields })))) + } + + /// Returns a tuple type with the given element types. Returns None if + /// `element_types` is empty. + pub fn tuple(element_types: impl Into>) -> Option { + let elements = element_types.into(); + if elements.is_empty() { + return None; + } + Some(Self(TypeEnum::Tuple(Arc::new(TupleType { elements })))) + } + + /// Returns a variant type with the given case names and optional payloads. + /// Returns None if `cases` is empty. + pub fn variant>>( + cases: impl IntoIterator)>, + ) -> Option { + let cases = cases + .into_iter() + .map(|(name, ty)| (name.into(), ty)) + .collect::>(); + if cases.is_empty() { + return None; + } + Some(Self(TypeEnum::Variant(Arc::new(VariantType { cases })))) + } + + /// Returns an enum type with the given case names. Returns None if `cases` + /// is empty. + pub fn enum_ty>>(cases: impl IntoIterator) -> Option { + let cases = cases.into_iter().map(Into::into).collect::>(); + if cases.is_empty() { + return None; + } + Some(Self(TypeEnum::Enum(Arc::new(EnumType { cases })))) + } + + /// Returns an option type with the given "some" type. + pub fn option(some: Self) -> Self { + Self(TypeEnum::Option(Arc::new(OptionType { some }))) + } + + /// Returns a result type with the given optional "ok" and "err" payloads. + pub fn result(ok: Option, err: Option) -> Self { + Self(TypeEnum::Result(Arc::new(ResultType { ok, err }))) + } + + /// Returns a flags type with the given flag names. Returns None if `flags` + /// is empty. + pub fn flags>>(flags: impl IntoIterator) -> Option { + let flags = flags.into_iter().map(Into::into).collect::>(); + if flags.is_empty() { + return None; + } + Some(Self(TypeEnum::Flags(Arc::new(FlagsType { flags })))) + } + + /// Returns a [`Type`] matching the given [`WasmType`]. Returns None if the + /// given type is unsupported or otherwise invalid. + pub fn from_wasm_type(ty: &impl WasmType) -> Option { + super::convert::from_wasm_type(ty) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct SimpleType(WasmTypeKind); + +const fn is_simple(kind: WasmTypeKind) -> bool { + use WasmTypeKind::*; + matches!( + kind, + Bool | S8 | S16 | S32 | S64 | U8 | U16 | U32 | U64 | Float32 | Float64 | Char | String + ) +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct ListType { + pub(super) element: Type, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct RecordType { + pub(super) fields: Box<[(Box, Type)]>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct TupleType { + pub(super) elements: Box<[Type]>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct VariantType { + pub(super) cases: Box<[(Box, Option)]>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct EnumType { + pub(super) cases: Box<[Box]>, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct OptionType { + pub(super) some: Type, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct ResultType { + pub(super) ok: Option, + pub(super) err: Option, +} + +#[derive(Debug, PartialEq, Eq)] +pub struct FlagsType { + pub(super) flags: Box<[Box]>, +} + +impl WasmType for Type { + fn kind(&self) -> WasmTypeKind { + match self.0 { + TypeEnum::Simple(simple) => simple.0, + TypeEnum::List(_) => WasmTypeKind::List, + TypeEnum::Record(_) => WasmTypeKind::Record, + TypeEnum::Tuple(_) => WasmTypeKind::Tuple, + TypeEnum::Variant(_) => WasmTypeKind::Variant, + TypeEnum::Enum(_) => WasmTypeKind::Enum, + TypeEnum::Option(_) => WasmTypeKind::Option, + TypeEnum::Result(_) => WasmTypeKind::Result, + TypeEnum::Flags(_) => WasmTypeKind::Flags, + } + } + + fn list_element_type(&self) -> Option { + let list = maybe_unwrap_type!(&self.0, TypeEnum::List)?; + Some(list.element.clone()) + } + + fn record_fields(&self) -> Box, Self)> + '_> { + let TypeEnum::Record(record) = &self.0 else { + return Box::new(std::iter::empty()); + }; + Box::new( + record + .fields + .iter() + .map(|(name, ty)| (name.as_ref().into(), ty.clone())), + ) + } + + fn tuple_element_types(&self) -> Box + '_> { + let TypeEnum::Tuple(tuple) = &self.0 else { + return Box::new(std::iter::empty()); + }; + Box::new(tuple.elements.iter().cloned()) + } + + fn variant_cases(&self) -> Box, Option)> + '_> { + let TypeEnum::Variant(variant) = &self.0 else { + return Box::new(std::iter::empty()); + }; + Box::new( + variant + .cases + .iter() + .map(|(name, ty)| (name.as_ref().into(), ty.clone())), + ) + } + + fn enum_cases(&self) -> Box> + '_> { + let TypeEnum::Enum(enum_) = &self.0 else { + return Box::new(std::iter::empty()); + }; + Box::new(enum_.cases.iter().map(|name| name.as_ref().into())) + } + + fn option_some_type(&self) -> Option { + let option = maybe_unwrap_type!(&self.0, TypeEnum::Option)?; + Some(option.some.clone()) + } + + fn result_types(&self) -> Option<(Option, Option)> { + let result = maybe_unwrap_type!(&self.0, TypeEnum::Result)?; + Some((result.ok.clone(), result.err.clone())) + } + + fn flags_names(&self) -> Box> + '_> { + let TypeEnum::Flags(flags) = &self.0 else { + return Box::new(std::iter::empty()); + }; + Box::new(flags.flags.iter().map(|name| name.as_ref().into())) + } +} + +impl std::fmt::Display for Type { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + crate::wasm::DisplayType(self).fmt(f) + } +} diff --git a/crates/wasm-wave/src/value/wit.rs b/crates/wasm-wave/src/value/wit.rs new file mode 100644 index 0000000000..73ab19d4fb --- /dev/null +++ b/crates/wasm-wave/src/value/wit.rs @@ -0,0 +1,218 @@ +use wit_parser::{ + Enum, Flags, Function, Record, Resolve, Result_, Tuple, Type, TypeDefKind, TypeId, Variant, +}; + +use crate::{value, wasm::WasmValueError}; + +/// Resolves a [`value::Type`] from the given [`wit_parser::Resolve`] and [`TypeId`]. +/// # Panics +/// Panics if `type_id` is not valid in `resolve`. +pub fn resolve_wit_type(resolve: &Resolve, type_id: TypeId) -> Result { + TypeResolver { resolve }.resolve_type_id(type_id) +} + +/// Resolves a [`value::FuncType`] from the given [`wit_parser::Resolve`] and [`Function`]. +/// # Panics +/// Panics if `function`'s types are not valid in `resolve`. +pub fn resolve_wit_func_type( + resolve: &Resolve, + function: &Function, +) -> Result { + let resolver = TypeResolver { resolve }; + let params = resolver.resolve_params(&function.params)?; + let results = match &function.results { + wit_parser::Results::Named(results) => resolver.resolve_params(results)?, + wit_parser::Results::Anon(ty) => vec![("".into(), resolver.resolve_type(*ty)?)], + }; + value::FuncType::new(params, results) +} + +struct TypeResolver<'a> { + resolve: &'a Resolve, +} + +type ValueResult = Result; + +impl<'a> TypeResolver<'a> { + fn resolve_type_id(&self, type_id: TypeId) -> ValueResult { + self.resolve(&self.resolve.types.get(type_id).unwrap().kind) + } + + fn resolve_type(&self, ty: Type) -> ValueResult { + self.resolve(&TypeDefKind::Type(ty)) + } + + fn resolve_params( + &self, + params: &[(String, Type)], + ) -> Result, WasmValueError> { + params + .iter() + .map(|(name, ty)| { + let ty = self.resolve_type(*ty)?; + Ok((name.clone(), ty)) + }) + .collect() + } + + fn resolve(&self, mut kind: &'a TypeDefKind) -> ValueResult { + // Recursively resolve any type defs. + while let &TypeDefKind::Type(Type::Id(id)) = kind { + kind = &self.resolve.types.get(id).unwrap().kind; + } + + match kind { + TypeDefKind::Record(record) => self.resolve_record(record), + TypeDefKind::Flags(flags) => self.resolve_flags(flags), + TypeDefKind::Tuple(tuple) => self.resolve_tuple(tuple), + TypeDefKind::Variant(variant) => self.resolve_variant(variant), + TypeDefKind::Enum(enum_) => self.resolve_enum(enum_), + TypeDefKind::Option(some_type) => self.resolve_option(some_type), + TypeDefKind::Result(result) => self.resolve_result(result), + TypeDefKind::List(element_type) => self.resolve_list(element_type), + TypeDefKind::Type(Type::Bool) => Ok(value::Type::BOOL), + TypeDefKind::Type(Type::U8) => Ok(value::Type::U8), + TypeDefKind::Type(Type::U16) => Ok(value::Type::U16), + TypeDefKind::Type(Type::U32) => Ok(value::Type::U32), + TypeDefKind::Type(Type::U64) => Ok(value::Type::U64), + TypeDefKind::Type(Type::S8) => Ok(value::Type::S8), + TypeDefKind::Type(Type::S16) => Ok(value::Type::S16), + TypeDefKind::Type(Type::S32) => Ok(value::Type::S32), + TypeDefKind::Type(Type::S64) => Ok(value::Type::S64), + TypeDefKind::Type(Type::F32) => Ok(value::Type::FLOAT32), + TypeDefKind::Type(Type::F64) => Ok(value::Type::FLOAT64), + TypeDefKind::Type(Type::Char) => Ok(value::Type::CHAR), + TypeDefKind::Type(Type::String) => Ok(value::Type::STRING), + TypeDefKind::Type(Type::Id(_)) => unreachable!(), + other => Err(WasmValueError::UnsupportedType(other.as_str().into())), + } + } + + fn resolve_record(&self, record: &Record) -> ValueResult { + let fields = record + .fields + .iter() + .map(|f| Ok((f.name.as_str(), self.resolve_type(f.ty)?))) + .collect::, _>>()?; + Ok(value::Type::record(fields).unwrap()) + } + + fn resolve_flags(&self, flags: &Flags) -> ValueResult { + let names = flags.flags.iter().map(|f| f.name.as_str()); + Ok(value::Type::flags(names).unwrap()) + } + + fn resolve_tuple(&self, tuple: &Tuple) -> ValueResult { + let types = tuple + .types + .iter() + .map(|ty| self.resolve_type(*ty)) + .collect::, _>>()?; + Ok(value::Type::tuple(types).unwrap()) + } + + fn resolve_variant(&self, variant: &Variant) -> ValueResult { + let cases = variant + .cases + .iter() + .map(|case| { + Ok(( + case.name.as_str(), + case.ty.map(|ty| self.resolve_type(ty)).transpose()?, + )) + }) + .collect::, _>>()?; + Ok(value::Type::variant(cases).unwrap()) + } + + fn resolve_enum(&self, enum_: &Enum) -> ValueResult { + let cases = enum_.cases.iter().map(|c| c.name.as_str()); + Ok(value::Type::enum_ty(cases).unwrap()) + } + + fn resolve_option(&self, some_type: &Type) -> ValueResult { + let some = self.resolve_type(*some_type)?; + Ok(value::Type::option(some)) + } + + fn resolve_result(&self, result: &Result_) -> ValueResult { + let ok = result.ok.map(|ty| self.resolve_type(ty)).transpose()?; + let err = result.err.map(|ty| self.resolve_type(ty)).transpose()?; + Ok(value::Type::result(ok, err)) + } + + fn resolve_list(&self, element_type: &Type) -> ValueResult { + let element_type = self.resolve_type(*element_type)?; + Ok(value::Type::list(element_type)) + } +} + +#[cfg(test)] +mod tests { + use wit_parser::UnresolvedPackageGroup; + + use super::*; + + #[test] + fn resolve_wit_type_smoke_test() { + let UnresolvedPackageGroup { + mut packages, + source_map, + } = UnresolvedPackageGroup::parse( + "test.wit", + r#" + package test:types; + interface types { + type uint8 = u8; + } + "#, + ) + .unwrap(); + let mut resolve = Resolve::new(); + resolve.push(packages.remove(0), &source_map).unwrap(); + + let (type_id, _) = resolve.types.iter().next().unwrap(); + let ty = resolve_wit_type(&resolve, type_id).unwrap(); + assert_eq!(ty, value::Type::U8); + } + + #[test] + fn resolve_wit_func_type_smoke_test() { + let UnresolvedPackageGroup { + mut packages, + source_map, + } = UnresolvedPackageGroup::parse( + "test.wit", + r#" + package test:types; + interface types { + type uint8 = u8; + no-results: func(a: uint8, b: string); + one-result: func(c: uint8, d: string) -> uint8; + named-results: func(e: uint8, f: string) -> (x: u8, y: string); + } + "#, + ) + .unwrap(); + let mut resolve = Resolve::new(); + resolve.push(packages.remove(0), &source_map).unwrap(); + + for (func_name, expected_display) in [ + ("no-results", "func(a: u8, b: string)"), + ("one-result", "func(c: u8, d: string) -> u8"), + ( + "named-results", + "func(e: u8, f: string) -> (x: u8, y: string)", + ), + ] { + let function = resolve + .interfaces + .iter() + .flat_map(|(_, i)| &i.functions) + .find_map(|(name, function)| (name == func_name).then_some(function)) + .unwrap(); + let ty = resolve_wit_func_type(&resolve, function).unwrap(); + assert_eq!(ty.to_string(), expected_display, "for {function:?}"); + } + } +} diff --git a/crates/wasm-wave/src/wasm/fmt.rs b/crates/wasm-wave/src/wasm/fmt.rs new file mode 100644 index 0000000000..1cfc8c32bc --- /dev/null +++ b/crates/wasm-wave/src/wasm/fmt.rs @@ -0,0 +1,225 @@ +use std::fmt::Display; + +use crate::{ + wasm::{WasmFunc, WasmType, WasmTypeKind}, + writer::Writer, + WasmValue, +}; + +/// Implements a WAVE-formatted [`Display`] for a [`WasmType`]. +pub struct DisplayType<'a, T: WasmType>(pub &'a T); + +impl<'a, T: WasmType> Display for DisplayType<'a, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let ty = self.0; + match ty.kind() { + WasmTypeKind::List => { + write!(f, "list<{}>", DisplayType(&ty.list_element_type().unwrap())) + } + WasmTypeKind::Record => { + f.write_str("record { ")?; + for (idx, (name, field_type)) in ty.record_fields().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + write!(f, "{name}: {}", DisplayType(&field_type))?; + } + f.write_str(" }") + } + WasmTypeKind::Tuple => { + f.write_str("tuple<")?; + for (idx, ty) in ty.tuple_element_types().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + write!(f, "{}", DisplayType(&ty))?; + } + f.write_str(">") + } + WasmTypeKind::Variant => { + f.write_str("variant { ")?; + for (idx, (name, payload)) in ty.variant_cases().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + f.write_str(name.as_ref())?; + if let Some(ty) = payload { + write!(f, "({})", DisplayType(&ty))?; + } + } + f.write_str(" }") + } + WasmTypeKind::Enum => { + f.write_str("enum { ")?; + for (idx, name) in ty.enum_cases().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + f.write_str(name.as_ref())?; + } + f.write_str(" }") + } + WasmTypeKind::Option => { + write!( + f, + "option<{}>", + DisplayType(&ty.option_some_type().unwrap()) + ) + } + WasmTypeKind::Result => { + f.write_str("result")?; + match ty.result_types().unwrap() { + (None, None) => Ok(()), + (None, Some(err)) => write!(f, "<_, {}>", DisplayType(&err)), + (Some(ok), None) => write!(f, "<{}>", DisplayType(&ok)), + (Some(ok), Some(err)) => { + write!(f, "<{}, {}>", DisplayType(&ok), DisplayType(&err)) + } + } + } + WasmTypeKind::Flags => { + f.write_str("flags { ")?; + for (idx, name) in ty.flags_names().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + f.write_str(name.as_ref())?; + } + f.write_str(" }") + } + simple => Display::fmt(&simple, f), + } + } +} + +/// Implements a WAVE-formatted [`Display`] for a [`WasmValue`]. +pub struct DisplayValue<'a, T: WasmValue>(pub &'a T); + +impl<'a, T: WasmValue> Display for DisplayValue<'a, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let mut buf = vec![]; + Writer::new(&mut buf) + .write_value(self.0) + .map_err(|_| std::fmt::Error)?; + f.write_str(String::from_utf8_lossy(&buf).as_ref()) + } +} + +/// Implements a WAVE-formatted [`Display`] for a [`WasmFunc`]. +pub struct DisplayFunc(pub T); + +impl Display for DisplayFunc { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("func(")?; + let mut param_names = self.0.param_names(); + for (idx, ty) in self.0.params().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + if let Some(name) = param_names.next() { + write!(f, "{name}: ")?; + } + DisplayType(&ty).fmt(f)? + } + f.write_str(")")?; + + let results = self.0.results().collect::>(); + if results.is_empty() { + return Ok(()); + } + + let mut result_names = self.0.result_names(); + if results.len() == 1 { + let ty = DisplayType(&results.into_iter().next().unwrap()).to_string(); + if let Some(name) = result_names.next() { + write!(f, " -> ({name}: {ty})") + } else { + write!(f, " -> {ty}") + } + } else { + f.write_str(" -> (")?; + for (idx, ty) in results.into_iter().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + if let Some(name) = result_names.next() { + write!(f, "{name}: ")?; + } + DisplayType(&ty).fmt(f)?; + } + f.write_str(")") + } + } +} + +/// Implements a WAVE-formatted [`Display`] for [`WasmValue`] func arguments. +pub struct DisplayFuncArgs<'a, T: WasmValue>(pub &'a [T]); + +impl<'a, T: WasmValue> Display for DisplayFuncArgs<'a, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str("(")?; + for (idx, v) in self.0.iter().enumerate() { + if idx != 0 { + f.write_str(", ")?; + } + DisplayValue(v).fmt(f)?; + } + f.write_str(")") + } +} + +/// Implements a WAVE-formatted [`Display`] for [`WasmValue`] func results. +pub struct DisplayFuncResults<'a, T: WasmValue>(pub &'a [T]); + +impl<'a, T: WasmValue> Display for DisplayFuncResults<'a, T> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + if self.0.len() == 1 { + DisplayValue(&self.0[0]).fmt(f) + } else { + DisplayFuncArgs(self.0).fmt(f) + } + } +} + +#[cfg(test)] +mod tests { + use crate::value::Type; + + #[test] + fn test_type_display() { + for (ty, expected) in [ + (Type::U8, "u8"), + (Type::list(Type::U8), "list"), + ( + Type::record([("a", Type::U8), ("b", Type::STRING)]).unwrap(), + "record { a: u8, b: string }", + ), + ( + Type::tuple([Type::U8, Type::BOOL]).unwrap(), + "tuple", + ), + ( + Type::variant([("off", None), ("on", Some(Type::U8))]).unwrap(), + "variant { off, on(u8) }", + ), + ( + Type::enum_ty(["east", "west"]).unwrap(), + "enum { east, west }", + ), + (Type::option(Type::U8), "option"), + (Type::result(None, None), "result"), + (Type::result(Some(Type::U8), None), "result"), + (Type::result(None, Some(Type::STRING)), "result<_, string>"), + ( + Type::result(Some(Type::U8), Some(Type::STRING)), + "result", + ), + ( + Type::flags(["read", "write"]).unwrap(), + "flags { read, write }", + ), + ] { + assert_eq!(ty.to_string(), expected); + } + } +} diff --git a/crates/wasm-wave/src/wasm/func.rs b/crates/wasm-wave/src/wasm/func.rs new file mode 100644 index 0000000000..171ec12db1 --- /dev/null +++ b/crates/wasm-wave/src/wasm/func.rs @@ -0,0 +1,33 @@ +use std::borrow::Cow; + +use crate::wasm::WasmType; + +// TODO: Given recent versions of rustc we may want to take a look at changing the returns of boxed +// iterators to -> impl Iterator + +/// The WasmFunc trait may be implemented to represent Wasm func type +/// signatures to be (de)serialized with WAVE. +pub trait WasmFunc { + /// A type representing types of these params and results. + type Type: WasmType; + + /// Returns an iterator of the func's parameter types. + fn params(&self) -> Box + '_>; + + /// Returns an iterator of the func's parameter names. Must be the same + /// length as the iterator returned by `params` or empty if this WasmFunc + /// impl does not support param names. + fn param_names(&self) -> Box> + '_> { + Box::new(std::iter::empty()) + } + + /// Returns an iterator of the func's result types. + fn results(&self) -> Box + '_>; + + /// Returns an iterator of the func's result names. Must be the same + /// length as the iterator returned by `results` or empty if there are no + /// named results or if this WasmFunc impl does not support result names. + fn result_names(&self) -> Box> + '_> { + Box::new(std::iter::empty()) + } +} diff --git a/crates/wasm-wave/src/wasm/mod.rs b/crates/wasm-wave/src/wasm/mod.rs new file mode 100644 index 0000000000..237ccba7d1 --- /dev/null +++ b/crates/wasm-wave/src/wasm/mod.rs @@ -0,0 +1,78 @@ +//! Wasm type and value types + +mod fmt; +mod func; +mod ty; +mod val; + +pub use fmt::{DisplayFunc, DisplayFuncArgs, DisplayFuncResults, DisplayType, DisplayValue}; +pub use func::WasmFunc; +pub use ty::{WasmType, WasmTypeKind}; +pub use val::WasmValue; + +pub(crate) use ty::maybe_unwrap_type; +pub(crate) use val::unwrap_val; + +/// Returns an error if the given [`WasmType`] is not of the given [`WasmTypeKind`]. +pub fn ensure_type_kind(ty: &impl WasmType, kind: WasmTypeKind) -> Result<(), WasmValueError> { + if ty.kind() == kind { + Ok(()) + } else { + Err(WasmValueError::WrongTypeKind { + ty: DisplayType(ty).to_string(), + kind, + }) + } +} + +/// An error from creating a [`WasmValue`]. +#[derive(Debug)] +#[allow(missing_docs)] +pub enum WasmValueError { + MissingField(String), + MissingPayload(String), + UnexpectedPayload(String), + UnknownCase(String), + UnknownField(String), + UnsupportedType(String), + WrongNumberOfTupleValues { want: usize, got: usize }, + WrongTypeKind { kind: WasmTypeKind, ty: String }, + WrongValueType { ty: String, val: String }, + Other(String), +} + +impl WasmValueError { + pub(crate) fn wrong_value_type(ty: &impl WasmType, val: &impl WasmValue) -> Self { + Self::WrongValueType { + ty: DisplayType(ty).to_string(), + val: DisplayValue(val).to_string(), + } + } +} + +impl std::fmt::Display for WasmValueError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::MissingField(name) => { + write!(f, "missing field {name:?}") + } + Self::MissingPayload(name) => write!(f, "missing payload for {name:?} case"), + Self::UnexpectedPayload(name) => write!(f, "unexpected payload for {name:?} case"), + Self::UnknownCase(name) => write!(f, "unknown case {name:?}"), + Self::UnknownField(name) => write!(f, "unknown field {name:?}"), + Self::UnsupportedType(ty) => write!(f, "unsupported type {ty}"), + Self::WrongNumberOfTupleValues { want, got } => { + write!(f, "expected {want} tuple elements; got {got}") + } + Self::WrongTypeKind { kind, ty } => { + write!(f, "expected a {kind}; got {ty}") + } + Self::WrongValueType { ty, val } => { + write!(f, "expected a {ty}; got {val}") + } + Self::Other(msg) => write!(f, "{msg}"), + } + } +} + +impl std::error::Error for WasmValueError {} diff --git a/crates/wasm-wave/src/wasm/ty.rs b/crates/wasm-wave/src/wasm/ty.rs new file mode 100644 index 0000000000..b9baea45ee --- /dev/null +++ b/crates/wasm-wave/src/wasm/ty.rs @@ -0,0 +1,137 @@ +use std::{borrow::Cow, fmt::Debug}; + +/// The kind of a [`WasmType`]. These correspond to the value types defined by the +/// [Component Model design](https://github.com/WebAssembly/component-model/blob/673d5c43c3cc0f4aeb8996a5c0931af623f16808/design/mvp/WIT.md). +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +#[allow(missing_docs)] +#[non_exhaustive] +pub enum WasmTypeKind { + Bool, + S8, + S16, + S32, + S64, + U8, + U16, + U32, + U64, + Float32, + Float64, + Char, + String, + List, + Record, + Tuple, + Variant, + Enum, + Option, + Result, + Flags, + #[doc(hidden)] + Unsupported, +} + +impl std::fmt::Display for WasmTypeKind { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(match self { + WasmTypeKind::Bool => "bool", + WasmTypeKind::S8 => "s8", + WasmTypeKind::S16 => "s16", + WasmTypeKind::S32 => "s32", + WasmTypeKind::S64 => "s64", + WasmTypeKind::U8 => "u8", + WasmTypeKind::U16 => "u16", + WasmTypeKind::U32 => "u32", + WasmTypeKind::U64 => "u64", + WasmTypeKind::Float32 => "float32", + WasmTypeKind::Float64 => "float64", + WasmTypeKind::Char => "char", + WasmTypeKind::String => "string", + WasmTypeKind::List => "list", + WasmTypeKind::Record => "record", + WasmTypeKind::Tuple => "tuple", + WasmTypeKind::Variant => "variant", + WasmTypeKind::Enum => "enum", + WasmTypeKind::Option => "option", + WasmTypeKind::Result => "result", + WasmTypeKind::Flags => "flags", + WasmTypeKind::Unsupported => "<>", + }) + } +} + +/// The WasmType trait may be implemented to represent types to be +/// (de)serialized with WAVE, notably [`value::Type`](crate::value::Type). +/// The [`wasmtime`] crate provides an impl for [`wasmtime::component::Type`]. +/// +/// The `Self`-returning methods should be called only for corresponding +/// [`WasmTypeKind`]s. +pub trait WasmType: Clone + Sized { + /// Returns the [`WasmTypeKind`] of this type. + fn kind(&self) -> WasmTypeKind; + + /// Returns the list element type or `None` if `self` is not a list type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn list_element_type(&self) -> Option { + unimplemented!() + } + /// Returns an iterator of the record's field names and Types. The + /// iterator will be empty iff `self` is not a record type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn record_fields(&self) -> Box, Self)> + '_> { + unimplemented!() + } + /// Returns an iterator of the tuple's field Types. The iterator will be + /// empty iff `self` is not a tuple type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn tuple_element_types(&self) -> Box + '_> { + unimplemented!() + } + /// Returns an iterator of the variant's case names and optional payload + /// Types. The iterator will be empty iff `self` is not a tuple type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn variant_cases(&self) -> Box, Option)> + '_> { + unimplemented!() + } + /// Returns an iterator of the enum's case names. The iterator will be + /// empty iff `self` is not an enum type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn enum_cases(&self) -> Box> + '_> { + unimplemented!() + } + /// Returns the option's "some" type or `None` if `self` is not an option type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn option_some_type(&self) -> Option { + unimplemented!() + } + /// Returns the result's optional "ok" and "err" Types or `None` if `self` + /// is not a result type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn result_types(&self) -> Option<(Option, Option)> { + unimplemented!() + } + /// Returns an iterator of the flags' names. The iterator will be empty iff + /// `self` is not a flags type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn flags_names(&self) -> Box> + '_> { + unimplemented!() + } +} + +macro_rules! maybe_unwrap_type { + ($ty:expr, $case:path) => { + match $ty { + $case(v) => Some(v), + _ => None, + } + }; +} +pub(crate) use maybe_unwrap_type; diff --git a/crates/wasm-wave/src/wasm/val.rs b/crates/wasm-wave/src/wasm/val.rs new file mode 100644 index 0000000000..69413c54ca --- /dev/null +++ b/crates/wasm-wave/src/wasm/val.rs @@ -0,0 +1,329 @@ +use std::borrow::Cow; + +use crate::wasm::{WasmType, WasmTypeKind, WasmValueError}; + +/// The WasmValue trait may be implemented to represent values to be +/// (de)serialized with WAVE, notably [`value::Value`](crate::value::Value). +/// The `wasmtime` crate provides an impl for [`wasmtime::component::Val`]. +/// +/// The `make_*` and `unwrap_*` methods should be called only for corresponding +/// [`WasmTypeKind`](crate::wasm::WasmTypeKind)s. +#[allow(unused_variables)] +pub trait WasmValue: Clone + Sized { + /// A type representing types of these values. + type Type: WasmType; + + /// The kind of type of this value. + fn kind(&self) -> WasmTypeKind; + + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_bool(val: bool) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_s8(val: i8) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_s16(val: i16) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_s32(val: i32) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_s64(val: i64) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_u8(val: u8) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_u16(val: u16) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_u32(val: u32) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_u64(val: u64) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// + /// The Rust `f32` type has many distinct NaN bitpatterns, however the + /// component-model `float32` type only has a single NaN value, so this + /// function does not preserve NaN bitpatterns. + /// + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_float32(val: f32) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// + /// The Rust `f64` type has many distinct NaN bitpatterns, however the + /// component-model `float64` type only has a single NaN value, so this + /// function does not preserve NaN bitpatterns. + /// + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_float64(val: f64) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_char(val: char) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_string(val: Cow) -> Self { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_list( + ty: &Self::Type, + vals: impl IntoIterator, + ) -> Result { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// + /// The fields provided by `fields` are not necessarily sorted; the callee + /// should perform sorting itself if needed. + /// + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_record<'a>( + ty: &Self::Type, + fields: impl IntoIterator, + ) -> Result { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_tuple( + ty: &Self::Type, + vals: impl IntoIterator, + ) -> Result { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_variant( + ty: &Self::Type, + case: &str, + val: Option, + ) -> Result { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_enum(ty: &Self::Type, case: &str) -> Result { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_option(ty: &Self::Type, val: Option) -> Result { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_result( + ty: &Self::Type, + val: Result, Option>, + ) -> Result { + unimplemented!() + } + /// Returns a new WasmValue of the given type. + /// + /// The strings provided by `names` are not necessarily sorted; the callee + /// should perform sorting itself if needed. + /// + /// # Panics + /// Panics if the type is not implemented (the trait default). + fn make_flags<'a>( + ty: &Self::Type, + names: impl IntoIterator, + ) -> Result { + unimplemented!() + } + + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_bool(&self) -> bool { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_s8(&self) -> i8 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_s16(&self) -> i16 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_s32(&self) -> i32 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_s64(&self) -> i64 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_u8(&self) -> u8 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_u16(&self) -> u16 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_u32(&self) -> u32 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_u64(&self) -> u64 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// + /// The Rust `f32` type has many distinct NaN bitpatterns, however the + /// component-model `float64` type only has a single NaN value, so this + /// function does not preserve NaN bitpatterns. + /// + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_float32(&self) -> f32 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// + /// The Rust `f64` type has many distinct NaN bitpatterns, however the + /// component-model `float64` type only has a single NaN value, so this + /// function does not preserve NaN bitpatterns. + /// + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_float64(&self) -> f64 { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_char(&self) -> char { + unimplemented!() + } + /// Returns the underlying value of the WasmValue, panicing if it's the wrong type. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_string(&self) -> Cow { + unimplemented!() + } + /// Returns an iterator of the element Vals of the list. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_list(&self) -> Box> + '_> { + unimplemented!() + } + /// Returns an iterator of the field names and Vals of the record. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_record(&self) -> Box, Cow)> + '_> { + unimplemented!() + } + /// Returns an iterator of the field Vals of the tuple. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_tuple(&self) -> Box> + '_> { + unimplemented!() + } + /// Returns the variant case name and optional payload WasmValue of the variant. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_variant(&self) -> (Cow, Option>) { + unimplemented!() + } + /// Returns the case name of the enum. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_enum(&self) -> Cow { + unimplemented!() + } + /// Returns the optional WasmValue. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_option(&self) -> Option> { + unimplemented!() + } + /// Returns Ok(_) or Err(_) with the optional payload WasmValue. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_result(&self) -> Result>, Option>> { + unimplemented!() + } + /// Returns an iterator of the names of the flags WasmValue. + /// # Panics + /// Panics if `self` is not of the right type. + fn unwrap_flags(&self) -> Box> + '_> { + unimplemented!() + } +} + +macro_rules! unwrap_val { + ($val:expr, $case:path, $name:expr) => { + match $val { + $case(v) => v, + _ => panic!("called unwrap_{name} on non-{name} value", name = $name), + } + }; +} +pub(crate) use unwrap_val; diff --git a/crates/wasm-wave/src/writer.rs b/crates/wasm-wave/src/writer.rs new file mode 100644 index 0000000000..f857624cd2 --- /dev/null +++ b/crates/wasm-wave/src/writer.rs @@ -0,0 +1,200 @@ +//! WAVE writer + +use std::{fmt::Debug, io::Write}; + +use thiserror::Error; + +use crate::{ + lex::Keyword, + wasm::{WasmTypeKind, WasmValue}, +}; + +/// A Web Assembly Value Encoding writer. +/// +/// Writes to the wrapped `W` writer. +pub struct Writer { + inner: W, +} + +impl Writer { + /// Returns a new Writer for the given [`std::io::Write`]. + pub fn new(w: W) -> Self { + Self { inner: w } + } + + /// WAVE-encodes and writes the given [`WasmValue`] to the underlying writer. + pub fn write_value(&mut self, val: &V) -> Result<(), WriterError> + where + V: WasmValue, + { + match val.kind() { + WasmTypeKind::Bool => self.write_str(if val.unwrap_bool() { "true" } else { "false" }), + WasmTypeKind::S8 => self.write_display(val.unwrap_s8()), + WasmTypeKind::S16 => self.write_display(val.unwrap_s16()), + WasmTypeKind::S32 => self.write_display(val.unwrap_s32()), + WasmTypeKind::S64 => self.write_display(val.unwrap_s64()), + WasmTypeKind::U8 => self.write_display(val.unwrap_u8()), + WasmTypeKind::U16 => self.write_display(val.unwrap_u16()), + WasmTypeKind::U32 => self.write_display(val.unwrap_u32()), + WasmTypeKind::U64 => self.write_display(val.unwrap_u64()), + WasmTypeKind::Float32 => { + let f = val.unwrap_float32(); + if f.is_nan() { + self.write_str("nan") // Display is "NaN" + } else { + self.write_display(f) + } + } + WasmTypeKind::Float64 => { + let f = val.unwrap_float64(); + if f.is_nan() { + self.write_str("nan") // Display is "NaN" + } else { + self.write_display(f) + } + } + WasmTypeKind::Char => { + self.write_str("'")?; + self.write_char(val.unwrap_char())?; + self.write_str("'") + } + WasmTypeKind::String => { + self.write_str("\"")?; + for ch in val.unwrap_string().chars() { + self.write_char(ch)?; + } + self.write_str("\"") + } + WasmTypeKind::List => { + self.write_str("[")?; + for (idx, val) in val.unwrap_list().enumerate() { + if idx != 0 { + self.write_str(", ")?; + } + self.write_value(&*val)?; + } + self.write_str("]") + } + WasmTypeKind::Record => { + self.write_str("{")?; + let mut first = true; + for (name, val) in val.unwrap_record() { + if !matches!(val.kind(), WasmTypeKind::Option) || val.unwrap_option().is_some() + { + if first { + first = false; + } else { + self.write_str(", ")?; + } + self.write_str(name)?; + self.write_str(": ")?; + self.write_value(&*val)?; + } + } + if first { + self.write_str(":")?; + } + self.write_str("}") + } + WasmTypeKind::Tuple => { + self.write_str("(")?; + for (idx, val) in val.unwrap_tuple().enumerate() { + if idx != 0 { + self.write_str(", ")?; + } + self.write_value(&*val)?; + } + self.write_str(")") + } + WasmTypeKind::Variant => { + let (name, val) = val.unwrap_variant(); + if Keyword::decode(&name).is_some() { + self.write_char('%')?; + } + self.write_str(name)?; + if let Some(val) = val { + self.write_str("(")?; + self.write_value(&*val)?; + self.write_str(")")?; + } + Ok(()) + } + WasmTypeKind::Enum => { + let case = val.unwrap_enum(); + if Keyword::decode(&case).is_some() { + self.write_char('%')?; + } + self.write_str(case) + } + WasmTypeKind::Option => match val.unwrap_option() { + Some(val) => { + self.write_str("some(")?; + self.write_value(&*val)?; + self.write_str(")") + } + None => self.write_str("none"), + }, + WasmTypeKind::Result => { + let (name, val) = match val.unwrap_result() { + Ok(val) => ("ok", val), + Err(val) => ("err", val), + }; + self.write_str(name)?; + if let Some(val) = val { + self.write_str("(")?; + self.write_value(&*val)?; + self.write_str(")")?; + } + Ok(()) + } + WasmTypeKind::Flags => { + self.write_str("{")?; + for (idx, name) in val.unwrap_flags().enumerate() { + if idx != 0 { + self.write_str(", ")?; + } + self.write_str(name)?; + } + self.write_str("}")?; + Ok(()) + } + WasmTypeKind::Unsupported => panic!("unsupported value type"), + } + } + + fn write_str(&mut self, s: impl AsRef) -> Result<(), WriterError> { + self.inner.write_all(s.as_ref().as_bytes())?; + Ok(()) + } + + fn write_display(&mut self, d: impl std::fmt::Display) -> Result<(), WriterError> { + write!(self.inner, "{d}")?; + Ok(()) + } + + fn write_char(&mut self, ch: char) -> Result<(), WriterError> { + if "\\\"\'\t\r\n".contains(ch) { + write!(self.inner, "{}", ch.escape_default())?; + } else if ch.is_control() { + write!(self.inner, "{}", ch.escape_unicode())?; + } else { + write!(self.inner, "{}", ch.escape_debug())?; + } + Ok(()) + } +} + +impl AsMut for Writer { + fn as_mut(&mut self) -> &mut W { + &mut self.inner + } +} + +/// A Writer error. +#[derive(Debug, Error)] +#[non_exhaustive] +pub enum WriterError { + /// An error from the underlying writer + #[error("write failed: {0}")] + Io(#[from] std::io::Error), +} diff --git a/crates/wasm-wave/tests/nan.rs b/crates/wasm-wave/tests/nan.rs new file mode 100644 index 0000000000..18609ccaa2 --- /dev/null +++ b/crates/wasm-wave/tests/nan.rs @@ -0,0 +1,61 @@ +//! Test that NaN bitpatterns are not propagated through Wave values. +//! +//! The component-model floating-point types only have a single NaN value, to +//! make it easier to exchange values with source languages and protocols where +//! there is only one NaN value. To help users avoid depending on NaN bits being +//! propagated, we canonicalize NaNs. + +use std::{f32, f64}; + +use wasm_wave::wasm::WasmValue; + +#[test] +fn nan() { + for bits in [ + 0, + i32::MIN as u32, + 1.0_f32.to_bits(), + (-f32::consts::TAU).to_bits(), + 0xffffffff, + 0x7fff0f0f, + 0x8f800000, + f32::NAN.to_bits(), + ] { + let val = f32::from_bits(bits); + let expected = if val.is_nan() { 0x7fc00000 } else { bits }; + + { + use wasm_wave::value::Value; + assert_eq!( + Value::make_float32(val).unwrap_float32().to_bits(), + expected + ); + } + } + + for bits in [ + 0, + i64::MIN as u64, + 1.0_f64.to_bits(), + (-f64::consts::TAU).to_bits(), + 0xffffffffffffffff, + 0x7fff0f0f0f0f0f0f, + 0x8ff0000000000000, + f64::NAN.to_bits(), + ] { + let val = f64::from_bits(bits); + let expected = if val.is_nan() { + 0x7ff8000000000000 + } else { + bits + }; + + { + use wasm_wave::value::Value; + assert_eq!( + Value::make_float64(val).unwrap_float64().to_bits(), + expected + ); + } + } +} diff --git a/crates/wasm-wave/tests/types.wit b/crates/wasm-wave/tests/types.wit new file mode 100644 index 0000000000..24d930d830 --- /dev/null +++ b/crates/wasm-wave/tests/types.wit @@ -0,0 +1,44 @@ +// Regenerate types.wasm: +// wasm-tools component embed --dummy types.wit | wasm-tools component new -o types.wasm + +package tests:tests + +world tests { + export bools: func() -> tuple + export sints: func() -> tuple + export uints: func() -> tuple + export floats: func() -> tuple + export options: func() -> tuple, option>> + export list-chars: func() -> list + export list-strings: func() -> list + export result-ok-only: func() -> result + export result-err-only: func() -> result<_, s8> + export result-no-payloads: func() -> result + export result-both-payloads: func() -> result + + record record-type { + required: u8, + optional: option, + } + export %record: func() -> record-type + + variant variant-type { + without-payload, + with-payload(u8), + } + export %variant: func() -> variant-type + + enum enum-type { + first, + second, + } + export %enum: func() -> enum-type + + flags flags-type { + read, + write, + } + export %flags: func() -> flags-type + + export func-type: func(a: bool, b: enum-type) -> result +} \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui.rs b/crates/wasm-wave/tests/ui.rs new file mode 100644 index 0000000000..4157c34900 --- /dev/null +++ b/crates/wasm-wave/tests/ui.rs @@ -0,0 +1,119 @@ +use anyhow::{Context, Result}; +use std::{collections::HashMap, fmt::Write, fs, path::Path, sync::OnceLock}; + +use wasm_wave::{ + parser::ParserError, + untyped::UntypedFuncCall, + value::{resolve_wit_func_type, FuncType, Value}, + wasm::{DisplayValue, WasmFunc}, +}; +use wit_parser::Resolve; + +fn test(path: &Path) -> Result { + let filename = path.file_name().unwrap().to_string_lossy(); + let inputs = std::fs::read_to_string(path)?; + let mut output = String::new(); + let out = &mut output; + let inputs = inputs.trim_end().trim_end_matches(';'); + for mut input in inputs.split(";\n") { + // Copy leading comments into the output + while input.starts_with("//") { + let Some((comment, remainder)) = input.split_once('\n') else { + break; + }; + input = remainder; + writeln!(out, "{comment}")?; + continue; + } + + fn parse_func_call( + input: &str, + ) -> Result<(String, &'static FuncType, Vec), ParserError> { + let untyped_call = UntypedFuncCall::parse(input)?; + let func_name = untyped_call.name().to_string(); + let func_type = get_func_type(&func_name).unwrap_or_else(|| { + panic!("unknown test func {func_name:?}"); + }); + let param_types = func_type.params().collect::>(); + let values = untyped_call.to_wasm_params::(¶m_types)?; + Ok((func_name, func_type, values)) + } + + match parse_func_call(input) { + Ok((func_name, func_type, values)) => { + assert!( + !filename.starts_with("reject-"), + "accepted input {input:?} in {filename}" + ); + write!(out, "{func_name}(")?; + let mut first = true; + for (name, value) in func_type.param_names().zip(values) { + if first { + first = false; + } else { + write!(out, ", ")?; + } + write!(out, "{name}: {value}", value = DisplayValue(&value))?; + } + writeln!(out, ")")?; + } + Err(err) => { + assert!( + !filename.starts_with("accept-"), + "rejected input {input:?} in {filename}: {err:#}" + ); + writeln!(out, "{err}")?; + } + } + } + Ok(output) +} + +fn get_func_type(func_name: &str) -> Option<&'static FuncType> { + static FUNC_TYPES: OnceLock> = OnceLock::new(); + FUNC_TYPES + .get_or_init(|| { + let mut resolve = Resolve::new(); + resolve.push_path("tests/ui/ui.wit").unwrap(); + resolve + .interfaces + .iter() + .flat_map(|(_, i)| &i.functions) + .map(|(name, func)| (name.clone(), resolve_wit_func_type(&resolve, func).unwrap())) + .collect::>() + }) + .get(func_name) +} + +// TODO: this ought to be be migrated to use `libtest-mimic`, which other crates in this workspace +// use as well +#[test] +fn ui() -> Result<()> { + for entry in fs::read_dir("tests/ui")? { + let path = entry?.path(); + if path.extension().is_none() { + continue; + } + if path.extension().unwrap() != "waves" { + continue; + } + + println!("testing {path:?}"); + let actual = test(&path)?; + let expected = path.with_extension("out"); + if std::env::var_os("BLESS").is_some() { + fs::write(&expected, actual) + .with_context(|| format!("failed to write {expected:?}"))?; + } else { + let expected = fs::read_to_string(&expected) + .with_context(|| format!("failed to read {expected:?}"))?; + assert_eq!( + expected, actual, + "expectation `{}` did not match actual `{}`", + expected, actual, + ); + } + } + + Ok(()) +} diff --git a/crates/wasm-wave/tests/ui/README.md b/crates/wasm-wave/tests/ui/README.md new file mode 100644 index 0000000000..5dbf8a9f37 --- /dev/null +++ b/crates/wasm-wave/tests/ui/README.md @@ -0,0 +1,13 @@ +# UI ("golden files") Tests + +Each `.waves` (WAVE Script) file in this directory contains a set of test inputs; corresponding outputs are in the matching `.out` file (e.g. `test.wave` → `test.out`). + +Each test input looks like `(, ...);\n` where `` is a function defined in the `ui.wit` file. For each input the WAVE value arguments are parsed and type-checked against the function parameter types. The result of this parsing - either a re-encoded copy of the input or an error - is written to the output. + +Note that each test input must end with `;\n`. This is not part of WAVE itself; inputs are split on this exact substring so it may not appear anywhere else in inputs. + +Comments at the start of a test case are copied into the output. + +## Updating Outputs + +By default, running the `ui` tests will check outputs against the contents of the existing `.out` files. When updating tests, the `.out` files can be overwritten by setting the environment variable `BLESS=1` when running the tests. Updated outputs should be committed and reviewed. \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/accept-chars.out b/crates/wasm-wave/tests/ui/accept-chars.out new file mode 100644 index 0000000000..5cf7bbb95c --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-chars.out @@ -0,0 +1,6 @@ +list-chars(vals: ['W', 'A', 'V', 'E', '🌊']) +list-chars(vals: ['\\', '\'', '\"', '\t', '\n', '\r']) +list-chars(vals: ['\u{0}', '\u{0}']) +list-chars(vals: ['x', 'x', 'x']) +list-chars(vals: ['☃', '☃', '☃']) +list-chars(vals: ['\"', '\"', '\"']) diff --git a/crates/wasm-wave/tests/ui/accept-chars.waves b/crates/wasm-wave/tests/ui/accept-chars.waves new file mode 100644 index 0000000000..ff83fb54e8 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-chars.waves @@ -0,0 +1,6 @@ +list-chars(['W', 'A', 'V', 'E', '🌊']); +list-chars(['\\', '\'', '\"', '\t', '\n', '\r']); +list-chars(['\u{0}', '\u{000000}']); +list-chars(['x', '\u{78}', '\u{000078}']); +list-chars(['☃', '\u{2603}', '\u{002603}']); +list-chars(['"', '\"', '\u{22}']); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/accept-comments.out b/crates/wasm-wave/tests/ui/accept-comments.out new file mode 100644 index 0000000000..d9deb3e100 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-comments.out @@ -0,0 +1,2 @@ +basic-record(rec: {required: 1, optional: some(1)}) +basic-record(rec: {required: 1, optional: some(1)}) diff --git a/crates/wasm-wave/tests/ui/accept-comments.waves b/crates/wasm-wave/tests/ui/accept-comments.waves new file mode 100644 index 0000000000..7f8c189339 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-comments.waves @@ -0,0 +1,19 @@ +basic-record( // comment + { // comment + required: 1, // comment + optional: some( // comment + 1 // comment + ), // comment + } // comment +) // comment; +basic-record( + // comment + { + // comment + required: 1, + // comment + optional: some(1) + // comment + } + // comment +); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/accept-enums.out b/crates/wasm-wave/tests/ui/accept-enums.out new file mode 100644 index 0000000000..1c203d5f12 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-enums.out @@ -0,0 +1,10 @@ +hand-enum(enm: left) +hand-enum(enm: left) +keyword-cases-enum(enm: %true) +keyword-cases-enum(enm: %false) +keyword-cases-enum(enm: %some) +keyword-cases-enum(enm: %none) +keyword-cases-enum(enm: %ok) +keyword-cases-enum(enm: %err) +keyword-cases-enum(enm: %inf) +keyword-cases-enum(enm: %nan) diff --git a/crates/wasm-wave/tests/ui/accept-enums.waves b/crates/wasm-wave/tests/ui/accept-enums.waves new file mode 100644 index 0000000000..45d6dd11a2 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-enums.waves @@ -0,0 +1,10 @@ +hand-enum(left); +hand-enum(%left); +keyword-cases-enum(%true); +keyword-cases-enum(%false); +keyword-cases-enum(%some); +keyword-cases-enum(%none); +keyword-cases-enum(%ok); +keyword-cases-enum(%err); +keyword-cases-enum(%inf); +keyword-cases-enum(%nan); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/accept-flags.out b/crates/wasm-wave/tests/ui/accept-flags.out new file mode 100644 index 0000000000..7a42c54e21 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-flags.out @@ -0,0 +1,7 @@ +permission-flags(flgs: {}) +permission-flags(flgs: {read}) +permission-flags(flgs: {read}) +permission-flags(flgs: {write}) +permission-flags(flgs: {read, write}) +permission-flags(flgs: {read, write}) +permission-flags(flgs: {read, write}) diff --git a/crates/wasm-wave/tests/ui/accept-flags.waves b/crates/wasm-wave/tests/ui/accept-flags.waves new file mode 100644 index 0000000000..1f0bbbc438 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-flags.waves @@ -0,0 +1,7 @@ +permission-flags({}); +permission-flags({ read }); +permission-flags({ read, }); +permission-flags({ write }); +permission-flags({ read, write }); +permission-flags({ write, read }); +permission-flags({ read, write, }); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/accept-floats.out b/crates/wasm-wave/tests/ui/accept-floats.out new file mode 100644 index 0000000000..5f682c47f7 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-floats.out @@ -0,0 +1,13 @@ +list-float32(vals: [0, 0, 0, 0, 0, 0, 0]) +list-float32(vals: [-3.1415, -100000, -0]) +list-float32(vals: [nan, inf, -inf]) +// Largest normal f32 +float32(val: 340282350000000000000000000000000000000) +// Truncated precision +float32(val: 340282350000000000000000000000000000000) +// Too large; infinity +float32(val: inf) +// Smallest positive non-zero f32 +float32(val: 0.000000000000000000000000000000000000000000001) +// Too small; round to zero +float32(val: 0) diff --git a/crates/wasm-wave/tests/ui/accept-floats.waves b/crates/wasm-wave/tests/ui/accept-floats.waves new file mode 100644 index 0000000000..01d4d48662 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-floats.waves @@ -0,0 +1,13 @@ +list-float32([0, 0.0, 0e0, 0.0e0, 0e-1, 0e+1, 0.000e100]); +list-float32([-3.1415, -100000, -0.0e-0]); +list-float32([nan, inf, -inf]); +// Largest normal f32 +float32(3.4028234664e38); +// Truncated precision +float32(3.4028234664123e38); +// Too large; infinity +float32(3.4028234664e39); +// Smallest positive non-zero f32 +float32(1.4012984643e-45); +// Too small; round to zero +float32(1.4012984643e-46); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/accept-records.out b/crates/wasm-wave/tests/ui/accept-records.out new file mode 100644 index 0000000000..cc67ee2521 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-records.out @@ -0,0 +1,14 @@ +basic-record(rec: {required: 1}) +basic-record(rec: {required: 1}) +basic-record(rec: {required: 1}) +basic-record(rec: {required: 1}) +basic-record(rec: {required: 1, optional: some(2)}) +basic-record(rec: {required: 1, optional: some(2)}) +optional-fields-record(rec: {:}) +optional-fields-record(rec: {:}) +optional-fields-record(rec: {:}) +optional-fields-record(rec: {:}) +optional-fields-record(rec: {a: some(1)}) +optional-fields-record(rec: {b: some(2)}) +optional-fields-record(rec: {a: some(1), b: some(2)}) +keyword-fields-record(rec: {true: true, false: false, some: some(1), ok: ok, err: err, inf: inf, nan: nan}) diff --git a/crates/wasm-wave/tests/ui/accept-records.waves b/crates/wasm-wave/tests/ui/accept-records.waves new file mode 100644 index 0000000000..a70b181153 --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-records.waves @@ -0,0 +1,14 @@ +basic-record({ required: 1 }); +basic-record({ required: 1, optional: none }); +basic-record({ required: 1, optional: none, }); +basic-record({ optional: none, required: 1 }); +basic-record({ required: 1, optional: some(2) }); +basic-record({ optional: some(2), required: 1, }); +optional-fields-record({:}); +optional-fields-record({ : }); +optional-fields-record({a:none,b:none}); +optional-fields-record({b:none,}); +optional-fields-record({a:some(1)}); +optional-fields-record({b:some(2)}); +optional-fields-record({b:some(2),a:some(1)}); +keyword-fields-record({true: true, false: false, some: some(1), none: none, ok: ok, err: err, inf: inf, nan: nan}); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/accept-strings.out b/crates/wasm-wave/tests/ui/accept-strings.out new file mode 100644 index 0000000000..1b4b2824bf --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-strings.out @@ -0,0 +1,13 @@ +string(val: "") +string(val: "WAVE🌊") +string(val: "\\\'\"\t\n\r") +string(val: "\'\'\'") +string(val: "☃☃☃") +// Multiline: empty +string(val: "") +// Multiline: escapes +string(val: "Break up double quote sequences: \"\"\"\"\nOther escapes work: \\\'\"\t\n\r☃") +// Multiline: indent behavior +string(val: " Trailing indent\ndetermines dedent\n behavior.") +// Multiline: empty lines +string(val: "\nEmpty leading and trailing lines are retained\n") diff --git a/crates/wasm-wave/tests/ui/accept-strings.waves b/crates/wasm-wave/tests/ui/accept-strings.waves new file mode 100644 index 0000000000..a45df90dbe --- /dev/null +++ b/crates/wasm-wave/tests/ui/accept-strings.waves @@ -0,0 +1,25 @@ +string(""); +string("WAVE🌊"); +string("\\\'\"\t\n\r"); +string("'\'\u{27}"); +string("☃\u{2603}\u{002603}"); +// Multiline: empty +string(""" +"""); +// Multiline: escapes +string(""" +Break up double quote sequences: ""\"" +Other escapes work: \\\'\"\t\n\r\u{2603} +"""); +// Multiline: indent behavior +string(""" + Trailing indent + determines dedent + behavior. + """); +// Multiline: empty lines +string(""" + +Empty leading and trailing lines are retained + +""") \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-chars.out b/crates/wasm-wave/tests/ui/reject-chars.out new file mode 100644 index 0000000000..6ae4b7197d --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-chars.out @@ -0,0 +1,27 @@ +// Wrong length +invalid token at 5..6 +invalid token at 5..9 +invalid token at 5..9 +invalid token at 5..6 +// Invalid escapes +invalid token at 5..6 +invalid token at 5..6 +invalid token at 5..6 +invalid token at 5..10 +invalid token at 5..6 +invalid token at 5..6 +invalid token at 5..6 +invalid token at 5..10 +// Out of range +invalid character escape at 5..17 +// Unpaired surrogates +invalid character escape at 5..15 +invalid character escape at 5..15 +// Invalid delimiters +invalid token at 5..6 +invalid token at 6..7 +invalid token at 5..7 +// Missing mandatory escapes +invalid token at 5..6 +invalid token at 5..6 +invalid token at 5..6 diff --git a/crates/wasm-wave/tests/ui/reject-chars.waves b/crates/wasm-wave/tests/ui/reject-chars.waves new file mode 100644 index 0000000000..cf83d90b7f --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-chars.waves @@ -0,0 +1,28 @@ +// Wrong length +char(''); +char('ab'); +char('☃☃'); +char('\n\n'); +// Invalid escapes +char('\z'); +char('\x1f'); +char('\u1f'); +char('\u{1f'); +char('\u1f}'); +char('\u{}'); +char('\u{x}'); +char('\u{12345678}'); +// Out of range +char('\u{110000}'); +// Unpaired surrogates +char('\u{D800}'); +char('\u{DFFF}'); +// Invalid delimiters +char('); +char(x'); +char('x); +// Missing mandatory escapes +char('\'); +char('''); +char(' +'); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-comments.out b/crates/wasm-wave/tests/ui/reject-comments.out new file mode 100644 index 0000000000..16939c26d7 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-comments.out @@ -0,0 +1,5 @@ +unexpected end of input at 32..32 +unexpected end of input at 33..33 +unexpected token: Colon at 28..29 +unexpected token: ParenClose at 39..40 +error converting Wasm value: missing field "required" at 13..52 diff --git a/crates/wasm-wave/tests/ui/reject-comments.waves b/crates/wasm-wave/tests/ui/reject-comments.waves new file mode 100644 index 0000000000..fef5abad69 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-comments.waves @@ -0,0 +1,14 @@ +basic-record(// { required: 1 }); +basic-record({ required: 1 } // ); +basic-record(// { + required: 1, +}); +basic-record( + { + required: 1, +//} +); +basic-record({ + // required: 1, + optional: none, +}); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-enums.out b/crates/wasm-wave/tests/ui/reject-enums.out new file mode 100644 index 0000000000..efb20daac5 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-enums.out @@ -0,0 +1,8 @@ +invalid value type at 19..23 +invalid value type at 19..24 +unexpected token: ParenClose at 23..24 +invalid value type at 19..23 +invalid value type at 19..21 +invalid value type at 19..22 +invalid value type at 19..22 +invalid value type at 19..22 diff --git a/crates/wasm-wave/tests/ui/reject-enums.waves b/crates/wasm-wave/tests/ui/reject-enums.waves new file mode 100644 index 0000000000..fa9724bc04 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-enums.waves @@ -0,0 +1,8 @@ +keyword-cases-enum(true); +keyword-cases-enum(false); +keyword-cases-enum(some); +keyword-cases-enum(none); +keyword-cases-enum(ok); +keyword-cases-enum(err); +keyword-cases-enum(inf); +keyword-cases-enum(nan); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-flags.out b/crates/wasm-wave/tests/ui/reject-flags.out new file mode 100644 index 0000000000..cb4b320df5 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-flags.out @@ -0,0 +1,6 @@ +// Duplicate flags. +duplicate flag: "read" at 25..29 +duplicate flag: "write" at 26..31 +duplicate flag: "read" at 32..36 +// Unrecognized flag. +error converting Wasm value: unknown case "execute" at 17..41 diff --git a/crates/wasm-wave/tests/ui/reject-flags.waves b/crates/wasm-wave/tests/ui/reject-flags.waves new file mode 100644 index 0000000000..f360389cdf --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-flags.waves @@ -0,0 +1,6 @@ +// Duplicate flags. +permission-flags({ read, read }); +permission-flags({ write, write }); +permission-flags({ read, write, read }); +// Unrecognized flag. +permission-flags({ read, write, execute }); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-floats.out b/crates/wasm-wave/tests/ui/reject-floats.out new file mode 100644 index 0000000000..6489eb9250 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-floats.out @@ -0,0 +1,19 @@ +// Reject "-nan". +invalid token at 8..9 +invalid token at 8..9 +unexpected token: LabelOrKeyword at 9..10 +unexpected token: LabelOrKeyword at 9..10 +// Reject "infinity", "-infinity", and uppercase variations. +invalid value type at 8..16 +unexpected token: LabelOrKeyword at 12..17 +invalid value type at 8..16 +invalid token at 8..9 +unexpected token: LabelOrKeyword at 9..16 +invalid token at 8..9 +unexpected token: LabelOrKeyword at 9..16 +invalid token at 8..9 +// Reject mixed case variations of "inf" and "nan". +unexpected token: LabelOrKeyword at 9..11 +invalid token at 8..9 +invalid value type at 8..11 +invalid token at 8..9 diff --git a/crates/wasm-wave/tests/ui/reject-floats.waves b/crates/wasm-wave/tests/ui/reject-floats.waves new file mode 100644 index 0000000000..672a01c5db --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-floats.waves @@ -0,0 +1,19 @@ +// Reject "-nan". +float32(-nan); +float64(-nan); +float32(NaN); +float64(NaN); +// Reject "infinity", "-infinity", and uppercase variations. +float32(infinity); +float64(-infinity); +float32(INFINITY); +float64(-INFINITY); +float32(Infinity); +float64(-Infinity); +float32(Infinity); +float64(-Infinity); +// Reject mixed case variations of "inf" and "nan". +float32(Inf); +float64(-Inf); +float32(INF); +float64(-INF); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-lists.out b/crates/wasm-wave/tests/ui/reject-lists.out new file mode 100644 index 0000000000..d16d8af9f4 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-lists.out @@ -0,0 +1,7 @@ +unexpected token: Comma at 22..23 +unexpected token: Comma at 14..15 +unexpected token: Comma at 14..15 +unexpected token: ParenClose at 14..15 +unexpected token: BracketClose at 14..15 +unexpected token: BraceClose at 14..15 +unexpected token: BracketClose at 14..15 diff --git a/crates/wasm-wave/tests/ui/reject-lists.waves b/crates/wasm-wave/tests/ui/reject-lists.waves new file mode 100644 index 0000000000..4fd5af3eb8 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-lists.waves @@ -0,0 +1,7 @@ +list-strings(["\u{0}",,]); +list-strings([,"\u{0}"]); +list-strings([,]); +list-strings([)); +list-strings((]); +list-strings([}); +list-strings({]); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-options.out b/crates/wasm-wave/tests/ui/reject-options.out new file mode 100644 index 0000000000..3db40e2cc5 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-options.out @@ -0,0 +1,11 @@ +unexpected token: ParenClose at 14..15 +unexpected token: ParenClose at 15..16 +unexpected token: ParenClose at 19..20 +unexpected token: ParenClose at 20..21 +invalid value type at 15..22 +invalid value type at 15..28 +unexpected token: ParenOpen at 14..15 +unexpected token: ParenOpen at 19..20 +unexpected token: ParenOpen at 14..15 +unexpected token: ParenOpen at 19..20 +unexpected token: ParenOpen at 14..15 diff --git a/crates/wasm-wave/tests/ui/reject-options.waves b/crates/wasm-wave/tests/ui/reject-options.waves new file mode 100644 index 0000000000..01b0c366e5 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-options.waves @@ -0,0 +1,11 @@ +option-u8(some); +option-u8(some()); +option-u8(some(some)); +option-u8(some(some())); +option-u8(some(some(0))); +option-u8(some(some(some(0)))); +option-u8(none()); +option-u8(some(none())); +option-u8(none(0)); +option-u8(some(none(0))); +option-u8(none(some(0))); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-records.out b/crates/wasm-wave/tests/ui/reject-records.out new file mode 100644 index 0000000000..4c0f5862d1 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-records.out @@ -0,0 +1,18 @@ +// Missing `required`. +error converting Wasm value: missing field "required" at 13..31 +error converting Wasm value: missing field "required" at 13..34 +// Duplicate `required`. +duplicate field: "required" at 28..36 +duplicate field: "required" at 28..36 +duplicate field: "required" at 28..36 +// Duplicate `optional`. +duplicate field: "optional" at 44..52 +duplicate field: "optional" at 44..52 +duplicate field: "optional" at 47..55 +duplicate field: "optional" at 47..55 +// Bad commas. +unexpected token: Comma at 27..28 +unexpected token: Comma at 15..16 +unexpected token: Comma at 15..16 +// Invalid empty record syntax. +invalid value type at 23..25 diff --git a/crates/wasm-wave/tests/ui/reject-records.waves b/crates/wasm-wave/tests/ui/reject-records.waves new file mode 100644 index 0000000000..3c3bea15cb --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-records.waves @@ -0,0 +1,18 @@ +// Missing `required`. +basic-record({ optional: none }); +basic-record({ optional: some(0) }); +// Duplicate `required`. +basic-record({ required: 0, required: 0 }); +basic-record({ required: 0, required: 0, optional: none }); +basic-record({ required: 0, required: 0, optional: some(0) }); +// Duplicate `optional`. +basic-record({ required: 0, optional: none, optional: none }); +basic-record({ required: 0, optional: none, optional: some(0) }); +basic-record({ required: 0, optional: some(0), optional: none }); +basic-record({ required: 0, optional: some(0), optional: some(0) }); +// Bad commas. +basic-record({ required: 0,, }); +basic-record({ , required: 0, }); +basic-record({ ,, required: 0 }); +// Invalid empty record syntax. +optional-fields-record({}); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-results.out b/crates/wasm-wave/tests/ui/reject-results.out new file mode 100644 index 0000000000..42436aafb1 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-results.out @@ -0,0 +1,20 @@ +error converting Wasm value: missing payload for "ok" case at 13..15 +unexpected token: ParenClose at 16..17 +invalid value type at 13..17 +error converting Wasm value: unexpected payload for "err" case at 13..19 +error converting Wasm value: missing payload for "err" case at 14..17 +unexpected token: ParenClose at 18..19 +invalid value type at 14..18 +error converting Wasm value: unexpected payload for "ok" case at 14..19 +unexpected token: ParenClose at 22..23 +invalid value type at 19..23 +error converting Wasm value: unexpected payload for "ok" case at 19..24 +unexpected token: ParenClose at 23..24 +invalid value type at 19..23 +error converting Wasm value: unexpected payload for "err" case at 19..25 +error converting Wasm value: missing payload for "ok" case at 15..17 +unexpected token: ParenClose at 18..19 +invalid value type at 15..19 +error converting Wasm value: missing payload for "err" case at 15..18 +unexpected token: ParenClose at 19..20 +invalid value type at 15..19 diff --git a/crates/wasm-wave/tests/ui/reject-results.waves b/crates/wasm-wave/tests/ui/reject-results.waves new file mode 100644 index 0000000000..283cad2599 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-results.waves @@ -0,0 +1,20 @@ +result-ok-u8(ok); +result-ok-u8(ok()); +result-ok-u8(o(0)); +result-ok-u8(err(0)); +result-err-u8(err); +result-err-u8(err()); +result-err-u8(e(0)); +result-err-u8(ok(0)); +result-no-payloads(ok()); +result-no-payloads(o(0)); +result-no-payloads(ok(0)); +result-no-payloads(err()); +result-no-payloads(e(0)); +result-no-payloads(err(0)); +result-both-u8(ok); +result-both-u8(ok()); +result-both-u8(o(0)); +result-both-u8(err); +result-both-u8(err()); +result-both-u8(e(0)); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/reject-strings.out b/crates/wasm-wave/tests/ui/reject-strings.out new file mode 100644 index 0000000000..78982ec730 --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-strings.out @@ -0,0 +1,32 @@ +// Reject surrogates. +invalid character escape at 8..9 +invalid character escape at 8..9 +invalid character escape at 8..9 +invalid character escape at 8..9 +invalid character escape at 8..9 +// Reject invalid values. +invalid character escape at 8..9 +invalid character escape at 8..9 +invalid character escape at 8..9 +// Reject invalid syntax. +invalid token at 7..8 +invalid token at 7..8 +// Missing mandatory escapes +invalid token at 7..10 +invalid token at 7..11 +invalid token at 7..8 +// Multiline missing mandatory line breaks +invalid multiline string: opening """ must be followed immediately by newline at 10..11 +invalid multiline string: opening """ must be followed immediately by newline at 10..11 +// Multiline invalid delimiters +invalid multiline string: opening """ must be followed immediately by newline at 10..11 +invalid token at 7..10 +invalid token at 14..16 +invalid multiline string: opening """ must be followed immediately by newline at 10..11 +invalid multiline string: closing """ must be on a line preceded only by spaces at 13..16 +// Multiline double quote triplet in content +invalid token at 19..22 +unexpected token: LabelOrKeyword at 25..30 +// Multiline invalid escapes +invalid character escape at 30..31 +invalid character escape at 27..28 diff --git a/crates/wasm-wave/tests/ui/reject-strings.waves b/crates/wasm-wave/tests/ui/reject-strings.waves new file mode 100644 index 0000000000..4d0bc4bb7a --- /dev/null +++ b/crates/wasm-wave/tests/ui/reject-strings.waves @@ -0,0 +1,46 @@ +// Reject surrogates. +string("\u{d800}"); +string("\u{dbff}"); +string("\u{dc00}"); +string("\u{dcff}"); +string("\u{d800}\\u{dc00}"); +// Reject invalid values. +string("\u{110000}"); +string("\u{ffffffff}"); +string("\u{80000000}"); +// Reject invalid syntax. +string("\u{-1}"); +string("\u{+1}"); +// Missing mandatory escapes +string("""); +string("\"); +string(" +"); +// Multiline missing mandatory line breaks +string(""""""); +string(""" """); +// Multiline invalid delimiters +string("""" +"""); +string(""" +""); +string(""" +""""); +string("""extra +"""); +string(""" +extra"""); +// Multiline double quote triplet in content +string(""" + """ + """); +string(""" + Between """ words + """); +// Multiline invalid escapes +string(""" +Invalid surrogate: \u{d800} +"""); +string(""" +Invalid escape: \j +"""); \ No newline at end of file diff --git a/crates/wasm-wave/tests/ui/ui.wit b/crates/wasm-wave/tests/ui/ui.wit new file mode 100644 index 0000000000..435a765b1a --- /dev/null +++ b/crates/wasm-wave/tests/ui/ui.wit @@ -0,0 +1,65 @@ +package ui:tests; + +interface ui { + %float32: func(val: f32); + %list-float32: func(vals: list); + %float64: func(val: f64); + %list-float64: func(vals: list); + %char: func(val: char); + list-chars: func(vals: list); + %string: func(val: string); + list-strings: func(vals: list); + option-u8: func(opt: option); + result-ok-u8: func(res: result); + result-err-u8: func(res: result<_, u8>); + result-both-u8: func(res: result); + result-no-payloads: func(res: result); + + record basic { + required: u8, + optional: option, + } + basic-record: func(rec: basic); + + record optional-fields { + a: option, + b: option, + } + optional-fields-record: func(rec: optional-fields); + + record keyword-fields { + true: bool, + false: bool, + some: option, + none: option, + ok: result, + err: result, + inf: f32, + nan: f32, + } + keyword-fields-record: func(rec: keyword-fields); + + enum hand { + left, + right, + } + hand-enum: func(enm: hand); + + enum keyword-cases { + true, + false, + some, + none, + ok, + err, + inf, + nan, + } + keyword-cases-enum: func(enm: keyword-cases); + + flags permissions { + read, + write, + } + permission-flags: func(flgs: permissions); +} \ No newline at end of file diff --git a/crates/wasm-wave/wave_ebnf.md b/crates/wasm-wave/wave_ebnf.md new file mode 100644 index 0000000000..7eb3790eda --- /dev/null +++ b/crates/wasm-wave/wave_ebnf.md @@ -0,0 +1,84 @@ +# WAVE EBNF + +A WAVE value is defined by the `value` rule below. Many applications may allow +whitespace around the value, equivalent to the `value-ws` rule. + +> Note that Bool, Variant, Enum, Option and Result values are combined under +> the `variant-case` rule because these cannot be distinguished without type +> information. + +```ebnf +value ::= number + | char + | string + | variant-case + | tuple + | list + | flags + | record + +value-ws ::= ws value ws +ws ::= ([ \t\n\r]* comment?)* +comment ::= '//' [^\n]* + +number ::= number_finite + | 'nan' + | 'inf' + | '-inf' +number_finite ::= integer number-fraction? number-exponent? +integer ::= unsigned-integer + | '-' unsigned-integer +unsigned-integer ::= '0' + | [1-9] [0-9]* +number-fraction ::= '.' [0-9]+ +number-exponent ::= [eE] [+-]? unsigned-integer + +char ::= ['] char-char ['] +char-char ::= common-char | '"' + +string ::= '"' string-char* '"' +string-char ::= common-char | ['] + +multiline-string ::= '"""' line-break multiline-string-line* [ ]* '"""' +multiline-string-line ::= [ ]* multiline-string-char* line-break +multiline-string-char ::= common-char | ['"] + +line-break ::= '\r\n' | '\n' + +common-char ::= + | '\' escape +escape ::= ['"tnr\\] | escape-unicode +escape-unicode ::= 'u{' [0-9a-fA-F]+ '}' + +variant-case ::= label ws variant-case-payload? +variant-case-payload ::= '(' value-ws ')' + +tuple ::= '(' values-seq ','? ws ')' + +list ::= '[' ws ']' + | '[' values-seq ','? ws ']' + +values-seq ::= value-ws + | values ',' values-ws + +flags ::= '{' ws '}' + | '{' flags-seq ','? ws '}' +flags-seq ::= ws label ws + | flags-seq ',' label + +record ::= '{' ws ':' ws '}' + | '{' record-fields ','? ws '}' +record-fields ::= ws record-field ws + | record-fields ',' record-field +record-field ::= label ws ':' ws value + +label ::= '%'? inner-label +inner-label ::= word + | inner-label '-' word +word ::= [a-z][a-z0-9]* + | [A-Z][A-Z0-9]* +``` + +* "`Unicode scalar value`" is defined by Unicode +* `escape-unicode` must identify a valid Unicode scalar value. +* `multiline-string-line` must not contain `"""` \ No newline at end of file From 1d402142ad36c3f0b23c65db5956bf503544e088 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 18 Jun 2024 13:56:44 -0700 Subject: [PATCH 18/58] fuzz(mutate): enable the custom-page-sizes proposal as necessary (#1619) * fuzz(mutate): enable the custom-page-sizes proposal as necessary If `wasm-smith` enabled it, then we need to enable it for validation of the mutated binaries. * Enable all validator features * fix unused var --- fuzz/src/mutate.rs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/fuzz/src/mutate.rs b/fuzz/src/mutate.rs index 9ca2061927..b67e356fe8 100644 --- a/fuzz/src/mutate.rs +++ b/fuzz/src/mutate.rs @@ -11,7 +11,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { let mut seed = 0; let mut preserve_semantics = false; - let (wasm, config) = crate::generate_valid_module(u, |config, u| { + let (wasm, _config) = crate::generate_valid_module(u, |config, u| { config.exceptions_enabled = false; config.gc_enabled = false; seed = u.arbitrary()?; @@ -55,19 +55,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { } }; - // Note that on-by-default features in wasmparser are not disabled here if - // the feature was disabled in `config` when the module was generated. For - // example if the input module doesn't have simd then wasm-mutate may - // produce a module that uses simd, which is ok and expected. - // - // Otherwise only forward some off-by-default features which are affected by - // wasm-smith's generation of modules and wasm-mutate otherwise won't add - // itself if it doesn't already exist. - let mut features = WasmFeatures::default(); - features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled); - features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1); - features.set(WasmFeatures::MEMORY64, config.memory64_enabled); - features.set(WasmFeatures::THREADS, config.threads_enabled); + let features = WasmFeatures::all(); for (i, mutated_wasm) in iterator.take(10).enumerate() { let mutated_wasm = match mutated_wasm { From bf58ec8be4c26c0d5636730caf8ec90ecec15ba3 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Tue, 18 Jun 2024 13:57:55 -0700 Subject: [PATCH 19/58] fuzz(no_traps): enable the custom-page-sizes proposal as necessary (#1620) * fuzz(no_traps): enable the custom-page-sizes proposal as necessary If the `wasm-smith` config enabled it, then we should enable it for validation. * Enable all validator features * fix unused var --- fuzz/src/no_traps.rs | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/fuzz/src/no_traps.rs b/fuzz/src/no_traps.rs index 782ff0cafe..2b180b353d 100644 --- a/fuzz/src/no_traps.rs +++ b/fuzz/src/no_traps.rs @@ -24,7 +24,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { config.memory64_enabled = false; Ok(()) })?; - validate_module(config.clone(), &wasm_bytes); + validate_module(&wasm_bytes); // Tail calls aren't implemented in wasmtime, so don't try to run them // there. @@ -111,22 +111,9 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { Ok(()) } -fn validate_module(config: wasm_smith::Config, wasm_bytes: &Vec) { +fn validate_module(wasm_bytes: &Vec) { // Validate the module or component and assert that it passes validation. - let mut validator = wasmparser::Validator::new_with_features({ - let mut features = WasmFeatures::default(); - features.remove(WasmFeatures::COMPONENT_MODEL); - features.set(WasmFeatures::MULTI_VALUE, config.multi_value_enabled); - features.set(WasmFeatures::MULTI_MEMORY, config.max_memories > 1); - features.insert(WasmFeatures::BULK_MEMORY); - features.insert(WasmFeatures::REFERENCE_TYPES); - features.set(WasmFeatures::SIMD, config.simd_enabled); - features.set(WasmFeatures::RELAXED_SIMD, config.relaxed_simd_enabled); - features.set(WasmFeatures::MEMORY64, config.memory64_enabled); - features.set(WasmFeatures::THREADS, config.threads_enabled); - features.set(WasmFeatures::EXCEPTIONS, config.exceptions_enabled); - features - }); + let mut validator = wasmparser::Validator::new_with_features(WasmFeatures::all()); if let Err(e) = validator.validate_all(wasm_bytes) { panic!("Invalid module: {}", e); } From c14ff15bef5347c2a29a725b02686ee444c9ef42 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 22:41:14 +0000 Subject: [PATCH 20/58] Release wasm-tools 1.211.0 (#1621) [automatically-tag-and-release-this-commit] Co-authored-by: Auto Release Process --- Cargo.lock | 114 ++++++++++++++++++++--------------------- Cargo.toml | 32 ++++++------ crates/wast/Cargo.toml | 2 +- crates/wat/Cargo.toml | 2 +- 4 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 98a59b1ced..1bf566e150 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,7 +295,7 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" name = "component" version = "0.0.0" dependencies = [ - "wasmprinter 0.210.0", + "wasmprinter 0.211.0", "wat", "wit-bindgen-rt", ] @@ -1540,7 +1540,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-compose" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "glob", @@ -1554,9 +1554,9 @@ dependencies = [ "serde_derive", "serde_yaml", "smallvec", - "wasm-encoder 0.210.0", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasm-encoder 0.211.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wat", "wit-component", ] @@ -1572,17 +1572,17 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "leb128", "tempfile", - "wasmparser 0.210.0", + "wasmparser 0.211.0", ] [[package]] name = "wasm-metadata" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "clap", @@ -1591,14 +1591,14 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.210.0", - "wasmparser 0.210.0", + "wasm-encoder 0.211.0", + "wasmparser 0.211.0", "wat", ] [[package]] name = "wasm-mutate" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "clap", @@ -1607,9 +1607,9 @@ dependencies = [ "log", "rand", "thiserror", - "wasm-encoder 0.210.0", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasm-encoder 0.211.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wat", ] @@ -1626,14 +1626,14 @@ dependencies = [ "num_cpus", "rand", "wasm-mutate", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wasmtime", ] [[package]] name = "wasm-shrink" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "blake3", @@ -1642,14 +1642,14 @@ dependencies = [ "log", "rand", "wasm-mutate", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wat", ] [[package]] name = "wasm-smith" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "arbitrary", @@ -1662,15 +1662,15 @@ dependencies = [ "rand", "serde", "serde_derive", - "wasm-encoder 0.210.0", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasm-encoder 0.211.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wat", ] [[package]] name = "wasm-tools" -version = "1.210.0" +version = "1.211.0" dependencies = [ "addr2line", "anyhow", @@ -1694,18 +1694,18 @@ dependencies = [ "tempfile", "termcolor", "wasm-compose", - "wasm-encoder 0.210.0", + "wasm-encoder 0.211.0", "wasm-metadata", "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wast", "wat", "wit-component", "wit-encoder", - "wit-parser 0.210.0", + "wit-parser 0.211.0", "wit-smith", ] @@ -1717,8 +1717,8 @@ dependencies = [ "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wast", "wat", ] @@ -1733,28 +1733,28 @@ dependencies = [ "libfuzzer-sys", "log", "tempfile", - "wasm-encoder 0.210.0", + "wasm-encoder 0.211.0", "wasm-mutate", "wasm-smith", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wasmtime", "wast", "wat", "wit-component", - "wit-parser 0.210.0", + "wit-parser 0.211.0", "wit-smith", ] [[package]] name = "wasm-wave" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "indexmap 2.2.6", "logos", "thiserror", - "wit-parser 0.210.0", + "wit-parser 0.211.0", ] [[package]] @@ -1770,7 +1770,7 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.210.0" +version = "0.211.0" dependencies = [ "ahash", "anyhow", @@ -1784,7 +1784,7 @@ dependencies = [ "rayon", "semver", "serde", - "wasm-encoder 0.210.0", + "wasm-encoder 0.211.0", "wast", "wat", ] @@ -1801,14 +1801,14 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "diff", "rayon", "tempfile", "termcolor", - "wasmparser 0.210.0", + "wasmparser 0.211.0", "wast", "wat", ] @@ -2026,7 +2026,7 @@ dependencies = [ [[package]] name = "wast" -version = "210.0.0" +version = "211.0.0" dependencies = [ "anyhow", "bumpalo", @@ -2034,14 +2034,14 @@ dependencies = [ "libtest-mimic", "memchr", "unicode-width", - "wasm-encoder 0.210.0", - "wasmparser 0.210.0", + "wasm-encoder 0.211.0", + "wasmparser 0.211.0", "wat", ] [[package]] name = "wat" -version = "1.210.0" +version = "1.211.0" dependencies = [ "wast", ] @@ -2178,7 +2178,7 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "bitflags", @@ -2191,19 +2191,19 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.210.0", + "wasm-encoder 0.211.0", "wasm-metadata", - "wasmparser 0.210.0", - "wasmprinter 0.210.0", + "wasmparser 0.211.0", + "wasmprinter 0.211.0", "wasmtime", "wast", "wat", - "wit-parser 0.210.0", + "wit-parser 0.211.0", ] [[package]] name = "wit-encoder" -version = "0.210.0" +version = "0.211.0" dependencies = [ "pretty_assertions", "semver", @@ -2229,7 +2229,7 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.210.0" +version = "0.211.0" dependencies = [ "anyhow", "env_logger", @@ -2243,9 +2243,9 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.210.0", + "wasmparser 0.211.0", "wat", - "wit-parser 0.210.0", + "wit-parser 0.211.0", ] [[package]] @@ -2256,13 +2256,13 @@ dependencies = [ "env_logger", "libfuzzer-sys", "log", - "wasmprinter 0.210.0", - "wit-parser 0.210.0", + "wasmprinter 0.211.0", + "wit-parser 0.211.0", ] [[package]] name = "wit-smith" -version = "0.210.0" +version = "0.211.0" dependencies = [ "arbitrary", "clap", @@ -2270,7 +2270,7 @@ dependencies = [ "log", "semver", "wit-component", - "wit-parser 0.210.0", + "wit-parser 0.211.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 1690376ccc..f61560cfe6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-tools" -version = "1.210.0" +version = "1.211.0" authors = ["The Wasmtime Project Developers"] edition.workspace = true description = "CLI tools for interoperating with WebAssembly files" @@ -52,7 +52,7 @@ all = "allow" [workspace.package] edition = '2021' -version = "0.210.0" +version = "0.211.0" # Current policy for wasm-tools is the same as Wasmtime which is that this # number can be no larger than the current stable release of Rust minus 2. rust-version = "1.76.0" @@ -85,20 +85,20 @@ hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] ahash = { version = "0.8.11", default-features = false } termcolor = "1.2.0" -wasm-compose = { version = "0.210.0", path = "crates/wasm-compose" } -wasm-encoder = { version = "0.210.0", path = "crates/wasm-encoder" } -wasm-metadata = { version = "0.210.0", path = "crates/wasm-metadata" } -wasm-mutate = { version = "0.210.0", path = "crates/wasm-mutate" } -wasm-shrink = { version = "0.210.0", path = "crates/wasm-shrink" } -wasm-smith = { version = "0.210.0", path = "crates/wasm-smith" } -wasmparser = { version = "0.210.0", path = "crates/wasmparser", default-features = false, features = ['std'] } -wasmprinter = { version = "0.210.0", path = "crates/wasmprinter" } -wast = { version = "210.0.0", path = "crates/wast" } -wat = { version = "1.210.0", path = "crates/wat" } -wit-component = { version = "0.210.0", path = "crates/wit-component" } -wit-encoder = { version = "0.210.0", path = "crates/wit-encoder" } -wit-parser = { version = "0.210.0", path = "crates/wit-parser" } -wit-smith = { version = "0.210.0", path = "crates/wit-smith" } +wasm-compose = { version = "0.211.0", path = "crates/wasm-compose" } +wasm-encoder = { version = "0.211.0", path = "crates/wasm-encoder" } +wasm-metadata = { version = "0.211.0", path = "crates/wasm-metadata" } +wasm-mutate = { version = "0.211.0", path = "crates/wasm-mutate" } +wasm-shrink = { version = "0.211.0", path = "crates/wasm-shrink" } +wasm-smith = { version = "0.211.0", path = "crates/wasm-smith" } +wasmparser = { version = "0.211.0", path = "crates/wasmparser", default-features = false, features = ['std'] } +wasmprinter = { version = "0.211.0", path = "crates/wasmprinter" } +wast = { version = "211.0.0", path = "crates/wast" } +wat = { version = "1.211.0", path = "crates/wat" } +wit-component = { version = "0.211.0", path = "crates/wit-component" } +wit-encoder = { version = "0.211.0", path = "crates/wit-encoder" } +wit-parser = { version = "0.211.0", path = "crates/wit-parser" } +wit-smith = { version = "0.211.0", path = "crates/wit-smith" } [dependencies] anyhow = { workspace = true } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index 296e353f6d..aec5148eb8 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wast" -version = "210.0.0" +version = "211.0.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml index 078a8ec3fe..df3c2c4320 100644 --- a/crates/wat/Cargo.toml +++ b/crates/wat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wat" -version = "1.210.0" +version = "1.211.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" From 8b5e324f1f38509cec20ee694edfffe61ed1d421 Mon Sep 17 00:00:00 2001 From: Pat Hickey Date: Tue, 18 Jun 2024 16:14:19 -0700 Subject: [PATCH 21/58] wasm-wave: add license to cargo.toml (#1622) --- crates/wasm-wave/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/wasm-wave/Cargo.toml b/crates/wasm-wave/Cargo.toml index 430d8f8a7a..fbf0efc2fd 100644 --- a/crates/wasm-wave/Cargo.toml +++ b/crates/wasm-wave/Cargo.toml @@ -2,6 +2,7 @@ name = "wasm-wave" version.workspace = true edition.workspace = true +license = "Apache-2.0 WITH LLVM-exception" rust-version.workspace = true authors = ["lann.martin@fermyon.com"] description = "WebAssembly Value Encoding" From d4b0ccde68f35f43ea280828e9d436c903212d58 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 00:11:50 +0000 Subject: [PATCH 22/58] Release wasm-tools 1.211.1 (#1623) [automatically-tag-and-release-this-commit] Co-authored-by: Auto Release Process --- Cargo.lock | 114 ++++++++++++++++++++--------------------- Cargo.toml | 32 ++++++------ crates/wast/Cargo.toml | 2 +- crates/wat/Cargo.toml | 2 +- 4 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 1bf566e150..5ed751ad42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -295,7 +295,7 @@ checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" name = "component" version = "0.0.0" dependencies = [ - "wasmprinter 0.211.0", + "wasmprinter 0.211.1", "wat", "wit-bindgen-rt", ] @@ -1540,7 +1540,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-compose" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "glob", @@ -1554,9 +1554,9 @@ dependencies = [ "serde_derive", "serde_yaml", "smallvec", - "wasm-encoder 0.211.0", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasm-encoder 0.211.1", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wat", "wit-component", ] @@ -1572,17 +1572,17 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "leb128", "tempfile", - "wasmparser 0.211.0", + "wasmparser 0.211.1", ] [[package]] name = "wasm-metadata" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "clap", @@ -1591,14 +1591,14 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.211.0", - "wasmparser 0.211.0", + "wasm-encoder 0.211.1", + "wasmparser 0.211.1", "wat", ] [[package]] name = "wasm-mutate" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "clap", @@ -1607,9 +1607,9 @@ dependencies = [ "log", "rand", "thiserror", - "wasm-encoder 0.211.0", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasm-encoder 0.211.1", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wat", ] @@ -1626,14 +1626,14 @@ dependencies = [ "num_cpus", "rand", "wasm-mutate", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wasmtime", ] [[package]] name = "wasm-shrink" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "blake3", @@ -1642,14 +1642,14 @@ dependencies = [ "log", "rand", "wasm-mutate", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wat", ] [[package]] name = "wasm-smith" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "arbitrary", @@ -1662,15 +1662,15 @@ dependencies = [ "rand", "serde", "serde_derive", - "wasm-encoder 0.211.0", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasm-encoder 0.211.1", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wat", ] [[package]] name = "wasm-tools" -version = "1.211.0" +version = "1.211.1" dependencies = [ "addr2line", "anyhow", @@ -1694,18 +1694,18 @@ dependencies = [ "tempfile", "termcolor", "wasm-compose", - "wasm-encoder 0.211.0", + "wasm-encoder 0.211.1", "wasm-metadata", "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wast", "wat", "wit-component", "wit-encoder", - "wit-parser 0.211.0", + "wit-parser 0.211.1", "wit-smith", ] @@ -1717,8 +1717,8 @@ dependencies = [ "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wast", "wat", ] @@ -1733,28 +1733,28 @@ dependencies = [ "libfuzzer-sys", "log", "tempfile", - "wasm-encoder 0.211.0", + "wasm-encoder 0.211.1", "wasm-mutate", "wasm-smith", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wasmtime", "wast", "wat", "wit-component", - "wit-parser 0.211.0", + "wit-parser 0.211.1", "wit-smith", ] [[package]] name = "wasm-wave" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "indexmap 2.2.6", "logos", "thiserror", - "wit-parser 0.211.0", + "wit-parser 0.211.1", ] [[package]] @@ -1770,7 +1770,7 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.211.0" +version = "0.211.1" dependencies = [ "ahash", "anyhow", @@ -1784,7 +1784,7 @@ dependencies = [ "rayon", "semver", "serde", - "wasm-encoder 0.211.0", + "wasm-encoder 0.211.1", "wast", "wat", ] @@ -1801,14 +1801,14 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "diff", "rayon", "tempfile", "termcolor", - "wasmparser 0.211.0", + "wasmparser 0.211.1", "wast", "wat", ] @@ -2026,7 +2026,7 @@ dependencies = [ [[package]] name = "wast" -version = "211.0.0" +version = "211.0.1" dependencies = [ "anyhow", "bumpalo", @@ -2034,14 +2034,14 @@ dependencies = [ "libtest-mimic", "memchr", "unicode-width", - "wasm-encoder 0.211.0", - "wasmparser 0.211.0", + "wasm-encoder 0.211.1", + "wasmparser 0.211.1", "wat", ] [[package]] name = "wat" -version = "1.211.0" +version = "1.211.1" dependencies = [ "wast", ] @@ -2178,7 +2178,7 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "bitflags", @@ -2191,19 +2191,19 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.211.0", + "wasm-encoder 0.211.1", "wasm-metadata", - "wasmparser 0.211.0", - "wasmprinter 0.211.0", + "wasmparser 0.211.1", + "wasmprinter 0.211.1", "wasmtime", "wast", "wat", - "wit-parser 0.211.0", + "wit-parser 0.211.1", ] [[package]] name = "wit-encoder" -version = "0.211.0" +version = "0.211.1" dependencies = [ "pretty_assertions", "semver", @@ -2229,7 +2229,7 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.211.0" +version = "0.211.1" dependencies = [ "anyhow", "env_logger", @@ -2243,9 +2243,9 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.211.0", + "wasmparser 0.211.1", "wat", - "wit-parser 0.211.0", + "wit-parser 0.211.1", ] [[package]] @@ -2256,13 +2256,13 @@ dependencies = [ "env_logger", "libfuzzer-sys", "log", - "wasmprinter 0.211.0", - "wit-parser 0.211.0", + "wasmprinter 0.211.1", + "wit-parser 0.211.1", ] [[package]] name = "wit-smith" -version = "0.211.0" +version = "0.211.1" dependencies = [ "arbitrary", "clap", @@ -2270,7 +2270,7 @@ dependencies = [ "log", "semver", "wit-component", - "wit-parser 0.211.0", + "wit-parser 0.211.1", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index f61560cfe6..a8b01554ff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-tools" -version = "1.211.0" +version = "1.211.1" authors = ["The Wasmtime Project Developers"] edition.workspace = true description = "CLI tools for interoperating with WebAssembly files" @@ -52,7 +52,7 @@ all = "allow" [workspace.package] edition = '2021' -version = "0.211.0" +version = "0.211.1" # Current policy for wasm-tools is the same as Wasmtime which is that this # number can be no larger than the current stable release of Rust minus 2. rust-version = "1.76.0" @@ -85,20 +85,20 @@ hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] ahash = { version = "0.8.11", default-features = false } termcolor = "1.2.0" -wasm-compose = { version = "0.211.0", path = "crates/wasm-compose" } -wasm-encoder = { version = "0.211.0", path = "crates/wasm-encoder" } -wasm-metadata = { version = "0.211.0", path = "crates/wasm-metadata" } -wasm-mutate = { version = "0.211.0", path = "crates/wasm-mutate" } -wasm-shrink = { version = "0.211.0", path = "crates/wasm-shrink" } -wasm-smith = { version = "0.211.0", path = "crates/wasm-smith" } -wasmparser = { version = "0.211.0", path = "crates/wasmparser", default-features = false, features = ['std'] } -wasmprinter = { version = "0.211.0", path = "crates/wasmprinter" } -wast = { version = "211.0.0", path = "crates/wast" } -wat = { version = "1.211.0", path = "crates/wat" } -wit-component = { version = "0.211.0", path = "crates/wit-component" } -wit-encoder = { version = "0.211.0", path = "crates/wit-encoder" } -wit-parser = { version = "0.211.0", path = "crates/wit-parser" } -wit-smith = { version = "0.211.0", path = "crates/wit-smith" } +wasm-compose = { version = "0.211.1", path = "crates/wasm-compose" } +wasm-encoder = { version = "0.211.1", path = "crates/wasm-encoder" } +wasm-metadata = { version = "0.211.1", path = "crates/wasm-metadata" } +wasm-mutate = { version = "0.211.1", path = "crates/wasm-mutate" } +wasm-shrink = { version = "0.211.1", path = "crates/wasm-shrink" } +wasm-smith = { version = "0.211.1", path = "crates/wasm-smith" } +wasmparser = { version = "0.211.1", path = "crates/wasmparser", default-features = false, features = ['std'] } +wasmprinter = { version = "0.211.1", path = "crates/wasmprinter" } +wast = { version = "211.0.1", path = "crates/wast" } +wat = { version = "1.211.1", path = "crates/wat" } +wit-component = { version = "0.211.1", path = "crates/wit-component" } +wit-encoder = { version = "0.211.1", path = "crates/wit-encoder" } +wit-parser = { version = "0.211.1", path = "crates/wit-parser" } +wit-smith = { version = "0.211.1", path = "crates/wit-smith" } [dependencies] anyhow = { workspace = true } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index aec5148eb8..3c7fe6b3ed 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wast" -version = "211.0.0" +version = "211.0.1" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml index df3c2c4320..b062f04a0e 100644 --- a/crates/wat/Cargo.toml +++ b/crates/wat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wat" -version = "1.211.0" +version = "1.211.1" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" From 8afdf0558ae4256d9faf57dde6441b72a087be34 Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Thu, 20 Jun 2024 20:04:16 +0300 Subject: [PATCH 23/58] Add parsing methods (#1627) Co-authored-by: Alex Crichton --- crates/wasm-encoder/Cargo.toml | 3 + crates/wasm-encoder/src/core/code.rs | 181 +++++++++++++++++- crates/wasm-encoder/src/core/custom.rs | 10 + crates/wasm-encoder/src/core/data.rs | 32 ++++ crates/wasm-encoder/src/core/elements.rs | 48 +++++ crates/wasm-encoder/src/core/exports.rs | 20 ++ crates/wasm-encoder/src/core/functions.rs | 13 ++ crates/wasm-encoder/src/core/globals.rs | 24 +++ crates/wasm-encoder/src/core/imports.rs | 25 +++ crates/wasm-encoder/src/core/memories.rs | 14 ++ crates/wasm-encoder/src/core/tables.rs | 31 +++ crates/wasm-encoder/src/core/tags.rs | 14 ++ crates/wasm-encoder/src/core/types.rs | 25 +++ crates/wasm-encoder/src/lib.rs | 1 + crates/wasm-mutate/src/mutators/translate.rs | 8 - crates/wasm-smith/src/core.rs | 82 ++------ crates/wasm-smith/src/core/code_builder.rs | 8 +- crates/wasmparser/Cargo.toml | 3 + .../wasmparser/src/readers/core/operators.rs | 24 +++ crates/wit-component/src/encoding.rs | 4 +- crates/wit-component/src/gc.rs | 14 +- crates/wit-component/src/linking.rs | 4 +- 22 files changed, 478 insertions(+), 110 deletions(-) diff --git a/crates/wasm-encoder/Cargo.toml b/crates/wasm-encoder/Cargo.toml index 6a7478dfac..7472bd7b13 100644 --- a/crates/wasm-encoder/Cargo.toml +++ b/crates/wasm-encoder/Cargo.toml @@ -13,6 +13,9 @@ A low-level WebAssembly encoder. """ rust-version.workspace = true +[package.metadata.docs.rs] +all-features = true + [lints] workspace = true diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs index e8887fb24a..25de73be66 100644 --- a/crates/wasm-encoder/src/core/code.rs +++ b/crates/wasm-encoder/src/core/code.rs @@ -103,6 +103,30 @@ impl CodeSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the code to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::CodeSectionReader<'_>, + ) -> wasmparser::Result<&mut Self> { + for code in section { + self.parse(code?)?; + } + Ok(self) + } + + /// Parses a single [`wasmparser::Code`] and adds it to this section. + #[cfg(feature = "wasmparser")] + pub fn parse(&mut self, func: wasmparser::FunctionBody<'_>) -> wasmparser::Result<&mut Self> { + let mut f = Function::new_parsed_locals(&func)?; + let mut reader = func.get_operators_reader()?; + while !reader.eof() { + f.parse(&mut reader)?; + } + Ok(self.function(&f)) + } } impl Encode for CodeSection { @@ -214,6 +238,18 @@ impl Function { Function::new(locals_collected) } + /// Create a new [`Function`] by parsing the locals declarations from the + /// provided [`wasmparser::FunctionBody`]. + #[cfg(feature = "wasmparser")] + pub fn new_parsed_locals(func: &wasmparser::FunctionBody<'_>) -> wasmparser::Result { + let mut locals = Vec::new(); + for pair in func.get_locals_reader()? { + let (cnt, ty) = pair?; + locals.push((cnt, ty.try_into().unwrap())); + } + Ok(Function::new(locals)) + } + /// Write an instruction into this function body. pub fn instruction(&mut self, instruction: &Instruction) -> &mut Self { instruction.encode(&mut self.bytes); @@ -237,6 +273,15 @@ impl Function { pub fn byte_len(&self) -> usize { self.bytes.len() } + + /// Parses a single instruction from `reader` and adds it to `self`. + #[cfg(feature = "wasmparser")] + pub fn parse( + &mut self, + reader: &mut wasmparser::OperatorsReader<'_>, + ) -> wasmparser::Result<&mut Self> { + Ok(self.instruction(&reader.read()?.try_into().unwrap())) + } } impl Encode for Function { @@ -276,6 +321,17 @@ impl Encode for MemArg { } } +#[cfg(feature = "wasmparser")] +impl From for MemArg { + fn from(arg: wasmparser::MemArg) -> MemArg { + MemArg { + offset: arg.offset, + align: arg.align.into(), + memory_index: arg.memory, + } + } +} + /// The memory ordering for atomic instructions. /// /// For an in-depth explanation of memory orderings, see the C++ documentation @@ -304,6 +360,16 @@ impl Encode for Ordering { } } +#[cfg(feature = "wasmparser")] +impl From for Ordering { + fn from(arg: wasmparser::Ordering) -> Ordering { + match arg { + wasmparser::Ordering::SeqCst => Ordering::SeqCst, + wasmparser::Ordering::AcqRel => Ordering::AcqRel, + } + } +} + /// Describe an unchecked SIMD lane index. pub type Lane = u8; @@ -328,6 +394,18 @@ impl Encode for BlockType { } } +#[cfg(feature = "wasmparser")] +impl TryFrom for BlockType { + type Error = (); + fn try_from(arg: wasmparser::BlockType) -> Result { + match arg { + wasmparser::BlockType::Empty => Ok(BlockType::Empty), + wasmparser::BlockType::FuncType(n) => Ok(BlockType::FunctionType(n)), + wasmparser::BlockType::Type(t) => Ok(BlockType::Result(t.try_into()?)), + } + } +} + /// WebAssembly instructions. #[derive(Clone, Debug)] #[non_exhaustive] @@ -350,14 +428,14 @@ pub enum Instruction<'a> { Call(u32), CallRef(u32), CallIndirect { - ty: u32, - table: u32, + type_index: u32, + table_index: u32, }, ReturnCallRef(u32), ReturnCall(u32), ReturnCallIndirect { - ty: u32, - table: u32, + type_index: u32, + table_index: u32, }, TryTable(BlockType, Cow<'a, [Catch]>), Throw(u32), @@ -1119,10 +1197,13 @@ impl Encode for Instruction<'_> { sink.push(0x14); ty.encode(sink); } - Instruction::CallIndirect { ty, table } => { + Instruction::CallIndirect { + type_index, + table_index, + } => { sink.push(0x11); - ty.encode(sink); - table.encode(sink); + type_index.encode(sink); + table_index.encode(sink); } Instruction::ReturnCallRef(ty) => { sink.push(0x15); @@ -1133,10 +1214,13 @@ impl Encode for Instruction<'_> { sink.push(0x12); f.encode(sink); } - Instruction::ReturnCallIndirect { ty, table } => { + Instruction::ReturnCallIndirect { + type_index, + table_index, + } => { sink.push(0x13); - ty.encode(sink); - table.encode(sink); + type_index.encode(sink); + table_index.encode(sink); } Instruction::Delegate(l) => { sink.push(0x18); @@ -3278,6 +3362,64 @@ impl Encode for Instruction<'_> { } } +#[cfg(feature = "wasmparser")] +impl TryFrom> for Instruction<'_> { + type Error = (); + + fn try_from(arg: wasmparser::Operator<'_>) -> Result { + use Instruction::*; + + macro_rules! define_match { + ($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { + match arg { + $( + wasmparser::Operator::$op $({ $($arg),* })? => { + $( + $(let $arg = define_match!(map $arg $arg);)* + )? + Ok(define_match!(mk $op $($($arg)*)?)) + } + )* + } + }; + + // No-payload instructions are named the same in wasmparser as they are in + // wasm-encoder + (mk $op:ident) => ($op); + + // Instructions which need "special care" to map from wasmparser to + // wasm-encoder + (mk BrTable $arg:ident) => ({BrTable($arg.0, $arg.1)}); + (mk TryTable $arg:ident) => ({TryTable($arg.0, $arg.1)}); + + // Catch-all for the translation of one payload argument which is typically + // represented as a tuple-enum in wasm-encoder. + (mk $op:ident $arg:ident) => ($op($arg)); + + // Catch-all of everything else where the wasmparser fields are simply + // translated to wasm-encoder fields. + (mk $op:ident $($arg:ident)*) => ($op { $($arg),* }); + + // Special-case BrTable/TryTable conversion of arguments. + (map $arg:ident targets) => (( + $arg.targets().map(|i| i.unwrap()).collect::>().into(), + $arg.default(), + )); + (map $arg:ident try_table) => (( + $arg.ty.try_into().unwrap(), + $arg.catches.into_iter().map(|i| i.into()).collect::>().into(), + )); + + // Everything else is converted with `TryFrom`/`From`. Note that the + // fallibility here has to do with how indexes are represented in + // `wasmparser` which we know when reading directly we'll never hit the + // erroneous cases here, hence the unwrap. + (map $arg:ident $other:ident) => {$other.try_into().unwrap()}; + } + wasmparser::for_each_operator!(define_match) + } +} + #[derive(Clone, Debug)] #[allow(missing_docs)] pub enum Catch { @@ -3312,6 +3454,18 @@ impl Encode for Catch { } } +#[cfg(feature = "wasmparser")] +impl From for Catch { + fn from(arg: wasmparser::Catch) -> Catch { + match arg { + wasmparser::Catch::One { tag, label } => Catch::One { tag, label }, + wasmparser::Catch::OneRef { tag, label } => Catch::OneRef { tag, label }, + wasmparser::Catch::All { label } => Catch::All { label }, + wasmparser::Catch::AllRef { label } => Catch::AllRef { label }, + } + } +} + /// A constant expression. /// /// Usable in contexts such as offsets or initializers. @@ -3494,6 +3648,13 @@ pub enum ConstExprConversionError { CanonicalizedTypeReference, } +#[cfg(feature = "wasmparser")] +impl From for ConstExprConversionError { + fn from(err: wasmparser::BinaryReaderError) -> ConstExprConversionError { + ConstExprConversionError::ParseError(err) + } +} + #[cfg(feature = "wasmparser")] impl std::fmt::Display for ConstExprConversionError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { diff --git a/crates/wasm-encoder/src/core/custom.rs b/crates/wasm-encoder/src/core/custom.rs index 870ee62617..75cae490c5 100644 --- a/crates/wasm-encoder/src/core/custom.rs +++ b/crates/wasm-encoder/src/core/custom.rs @@ -44,6 +44,16 @@ impl Section for RawCustomSection<'_> { } } +#[cfg(feature = "wasmparser")] +impl<'a> From> for CustomSection<'a> { + fn from(section: wasmparser::CustomSectionReader<'a>) -> Self { + CustomSection { + data: section.data().into(), + name: section.name().into(), + } + } +} + #[cfg(test)] mod tests { use super::*; diff --git a/crates/wasm-encoder/src/core/data.rs b/crates/wasm-encoder/src/core/data.rs index 6f408befe5..7f8dd795d1 100644 --- a/crates/wasm-encoder/src/core/data.rs +++ b/crates/wasm-encoder/src/core/data.rs @@ -151,6 +151,38 @@ impl DataSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the data to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::DataSectionReader<'_>, + ) -> Result<&mut Self, crate::ConstExprConversionError> { + for data in section { + self.parse(data?)?; + } + Ok(self) + } + + /// Parses a single [`wasmparser::Data`] and adds it to this section. + #[cfg(feature = "wasmparser")] + pub fn parse( + &mut self, + data: wasmparser::Data<'_>, + ) -> Result<&mut Self, crate::ConstExprConversionError> { + match data.kind { + wasmparser::DataKind::Active { + memory_index, + offset_expr, + } => Ok(self.active( + memory_index, + &ConstExpr::try_from(offset_expr)?, + data.data.iter().copied(), + )), + wasmparser::DataKind::Passive => Ok(self.passive(data.data.iter().copied())), + } + } } impl Encode for DataSection { diff --git a/crates/wasm-encoder/src/core/elements.rs b/crates/wasm-encoder/src/core/elements.rs index 28a1031ed8..8d24c4e363 100644 --- a/crates/wasm-encoder/src/core/elements.rs +++ b/crates/wasm-encoder/src/core/elements.rs @@ -206,6 +206,54 @@ impl ElementSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the elements to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::ElementSectionReader<'_>, + ) -> Result<&mut Self, crate::ConstExprConversionError> { + for element in section { + self.parse(element?)?; + } + Ok(self) + } + + /// Parses the single [`wasmparser::Element`] provided and adds it to this + /// section. + #[cfg(feature = "wasmparser")] + pub fn parse( + &mut self, + element: wasmparser::Element<'_>, + ) -> Result<&mut Self, crate::ConstExprConversionError> { + let mut funcs; + let mut exprs; + let elements = match element.items { + wasmparser::ElementItems::Functions(f) => { + funcs = Vec::new(); + for func in f { + funcs.push(func?); + } + Elements::Functions(&funcs) + } + wasmparser::ElementItems::Expressions(ty, e) => { + exprs = Vec::new(); + for expr in e { + exprs.push(ConstExpr::try_from(expr?)?); + } + Elements::Expressions(ty.try_into().unwrap(), &exprs) + } + }; + match element.kind { + wasmparser::ElementKind::Active { + table_index, + offset_expr, + } => Ok(self.active(table_index, &ConstExpr::try_from(offset_expr)?, elements)), + wasmparser::ElementKind::Passive => Ok(self.passive(elements)), + wasmparser::ElementKind::Declared => Ok(self.declared(elements)), + } + } } impl Encode for ElementSection { diff --git a/crates/wasm-encoder/src/core/exports.rs b/crates/wasm-encoder/src/core/exports.rs index fc2dc9c392..7b06fabc06 100644 --- a/crates/wasm-encoder/src/core/exports.rs +++ b/crates/wasm-encoder/src/core/exports.rs @@ -83,6 +83,26 @@ impl ExportSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the exports to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::ExportSectionReader<'_>, + ) -> wasmparser::Result<&mut Self> { + for export in section { + self.parse(export?); + } + Ok(self) + } + + /// Parses the single [`wasmparser::Export`] provided and adds it to this + /// section. + #[cfg(feature = "wasmparser")] + pub fn parse(&mut self, export: wasmparser::Export<'_>) -> &mut Self { + self.export(export.name, export.kind.into(), export.index) + } } impl Encode for ExportSection { diff --git a/crates/wasm-encoder/src/core/functions.rs b/crates/wasm-encoder/src/core/functions.rs index e21d8c10a7..3cc053ba5b 100644 --- a/crates/wasm-encoder/src/core/functions.rs +++ b/crates/wasm-encoder/src/core/functions.rs @@ -48,6 +48,19 @@ impl FunctionSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the functions to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::FunctionSectionReader<'_>, + ) -> wasmparser::Result<&mut Self> { + for func in section { + self.function(func?); + } + Ok(self) + } } impl Encode for FunctionSection { diff --git a/crates/wasm-encoder/src/core/globals.rs b/crates/wasm-encoder/src/core/globals.rs index 291ca80472..f208f63f1f 100644 --- a/crates/wasm-encoder/src/core/globals.rs +++ b/crates/wasm-encoder/src/core/globals.rs @@ -60,6 +60,30 @@ impl GlobalSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the globals to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::GlobalSectionReader<'_>, + ) -> Result<&mut Self, crate::ConstExprConversionError> { + for global in section { + self.parse(global?)?; + } + Ok(self) + } + + /// Parses the single [`wasmparser::Global`] provided and adds it to this + /// section. + #[cfg(feature = "wasmparser")] + pub fn parse( + &mut self, + global: wasmparser::Global<'_>, + ) -> Result<&mut Self, crate::ConstExprConversionError> { + self.global(global.ty.try_into().unwrap(), &global.init_expr.try_into()?); + Ok(self) + } } impl Encode for GlobalSection { diff --git a/crates/wasm-encoder/src/core/imports.rs b/crates/wasm-encoder/src/core/imports.rs index d4db2e65c5..5eac839cde 100644 --- a/crates/wasm-encoder/src/core/imports.rs +++ b/crates/wasm-encoder/src/core/imports.rs @@ -142,6 +142,31 @@ impl ImportSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the imports to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::ImportSectionReader<'_>, + ) -> wasmparser::Result<&mut Self> { + for import in section { + self.parse(import?); + } + Ok(self) + } + + /// Parses the single [`wasmparser::Import`] provided and adds it to this + /// section. + #[cfg(feature = "wasmparser")] + pub fn parse(&mut self, import: wasmparser::Import<'_>) -> &mut Self { + self.import( + import.module, + import.name, + EntityType::try_from(import.ty).unwrap(), + ); + self + } } impl Encode for ImportSection { diff --git a/crates/wasm-encoder/src/core/memories.rs b/crates/wasm-encoder/src/core/memories.rs index d64ef01210..b3942dfa09 100644 --- a/crates/wasm-encoder/src/core/memories.rs +++ b/crates/wasm-encoder/src/core/memories.rs @@ -51,6 +51,20 @@ impl MemorySection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the memories to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::MemorySectionReader<'_>, + ) -> wasmparser::Result<&mut Self> { + for memory in section { + let memory = memory?; + self.memory(memory.into()); + } + Ok(self) + } } impl Encode for MemorySection { diff --git a/crates/wasm-encoder/src/core/tables.rs b/crates/wasm-encoder/src/core/tables.rs index 7467f5f7e7..24bfe03a0e 100644 --- a/crates/wasm-encoder/src/core/tables.rs +++ b/crates/wasm-encoder/src/core/tables.rs @@ -62,6 +62,37 @@ impl TableSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the tables to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::TableSectionReader<'_>, + ) -> Result<&mut Self, crate::ConstExprConversionError> { + for table in section { + self.parse(table?)?; + } + Ok(self) + } + + /// Parses a single [`wasmparser::Table`] and adds it to this section. + #[cfg(feature = "wasmparser")] + pub fn parse( + &mut self, + table: wasmparser::Table<'_>, + ) -> Result<&mut Self, crate::ConstExprConversionError> { + let ty = table.ty.try_into().unwrap(); + match table.init { + wasmparser::TableInit::RefNull => { + self.table(ty); + } + wasmparser::TableInit::Expr(e) => { + self.table_with_init(ty, &e.try_into()?); + } + } + Ok(self) + } } impl Encode for TableSection { diff --git a/crates/wasm-encoder/src/core/tags.rs b/crates/wasm-encoder/src/core/tags.rs index 9b4c728a8c..2d6a88d995 100644 --- a/crates/wasm-encoder/src/core/tags.rs +++ b/crates/wasm-encoder/src/core/tags.rs @@ -46,6 +46,20 @@ impl TagSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the tags to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::TagSectionReader<'_>, + ) -> wasmparser::Result<&mut Self> { + for tag in section { + let tag = tag?; + self.tag(tag.into()); + } + Ok(self) + } } impl Encode for TagSection { diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs index 0fa12f68fc..0f53326b08 100644 --- a/crates/wasm-encoder/src/core/types.rs +++ b/crates/wasm-encoder/src/core/types.rs @@ -757,6 +757,31 @@ impl TypeSection { self.num_added += 1; self } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the types to this section. + #[cfg(feature = "wasmparser")] + pub fn parse_section( + &mut self, + section: wasmparser::TypeSectionReader<'_>, + ) -> wasmparser::Result<&mut Self> { + for rec_group in section { + self.parse(rec_group?); + } + Ok(self) + } + + /// Parses a single [`wasmparser::RecGroup`] and adds it to this section. + #[cfg(feature = "wasmparser")] + pub fn parse(&mut self, rec_group: wasmparser::RecGroup) -> &mut Self { + if rec_group.is_explicit_rec_group() { + self.rec(rec_group.into_types().map(|t| t.try_into().unwrap())); + } else { + let ty = rec_group.into_types().next().unwrap(); + self.subtype(&SubType::try_from(ty).unwrap()); + } + self + } } impl Encode for TypeSection { diff --git a/crates/wasm-encoder/src/lib.rs b/crates/wasm-encoder/src/lib.rs index 930c63ec9d..ae30a643dd 100644 --- a/crates/wasm-encoder/src/lib.rs +++ b/crates/wasm-encoder/src/lib.rs @@ -68,6 +68,7 @@ //! assert!(wasmparser::validate(&wasm_bytes).is_ok()); //! ``` +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![deny(missing_docs, missing_debug_implementations)] mod component; diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs index 839879bc71..706e7629ad 100644 --- a/crates/wasm-mutate/src/mutators/translate.rs +++ b/crates/wasm-mutate/src/mutators/translate.rs @@ -399,14 +399,6 @@ pub fn op(t: &mut dyn Translator, op: &Operator<'_>) -> Result (I::V128Const($arg.i128())); (build TryTable $table:ident) => (unimplemented_try_table()); (build $op:ident $arg:ident) => (I::$op($arg)); - (build CallIndirect $ty:ident $table:ident) => (I::CallIndirect { - ty: $ty, - table: $table, - }); - (build ReturnCallIndirect $ty:ident $table:ident) => (I::ReturnCallIndirect { - ty: $ty, - table: $table, - }); (build $op:ident $($arg:ident)*) => (I::$op { $($arg),* }); } diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index 29514f7ac0..096a1ef7eb 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -1673,59 +1673,6 @@ impl Module { #[cfg(feature = "wasmparser")] fn _required_exports(&mut self, u: &mut Unstructured, example_module: &[u8]) -> Result<()> { - fn convert_heap_type(ty: &wasmparser::HeapType) -> HeapType { - use wasmparser::AbstractHeapType::*; - match ty { - wasmparser::HeapType::Concrete(_) => { - panic!("Unable to handle concrete types in exports") - } - wasmparser::HeapType::Abstract { shared, ty } => { - let ty = match ty { - Func => AbstractHeapType::Func, - Extern => AbstractHeapType::Extern, - Any => AbstractHeapType::Any, - None => AbstractHeapType::None, - NoExtern => AbstractHeapType::NoExtern, - NoFunc => AbstractHeapType::NoFunc, - Eq => AbstractHeapType::Eq, - Struct => AbstractHeapType::Struct, - Array => AbstractHeapType::Array, - I31 => AbstractHeapType::I31, - Exn => AbstractHeapType::Exn, - NoExn => AbstractHeapType::NoExn, - }; - HeapType::Abstract { - shared: *shared, - ty, - } - } - } - } - - fn convert_val_type(ty: &wasmparser::ValType) -> ValType { - match ty { - wasmparser::ValType::I32 => ValType::I32, - wasmparser::ValType::I64 => ValType::I64, - wasmparser::ValType::F32 => ValType::F32, - wasmparser::ValType::F64 => ValType::F64, - wasmparser::ValType::V128 => ValType::V128, - wasmparser::ValType::Ref(r) => ValType::Ref(RefType { - nullable: r.is_nullable(), - heap_type: convert_heap_type(&r.heap_type()), - }), - } - } - - fn convert_export_kind(kind: &wasmparser::ExternalKind) -> ExportKind { - match kind { - wasmparser::ExternalKind::Func => ExportKind::Func, - wasmparser::ExternalKind::Table => ExportKind::Table, - wasmparser::ExternalKind::Memory => ExportKind::Memory, - wasmparser::ExternalKind::Global => ExportKind::Global, - wasmparser::ExternalKind::Tag => ExportKind::Tag, - } - } - let mut required_exports: Vec = vec![]; let mut validator = wasmparser::Validator::new(); let exports_types = validator @@ -1776,13 +1723,15 @@ impl Module { let new_type = Rc::new(FuncType { params: func_type .params() - .into_iter() - .map(convert_val_type) + .iter() + .copied() + .map(|t| t.try_into().unwrap()) .collect(), results: func_type .results() - .into_iter() - .map(convert_val_type) + .iter() + .copied() + .map(|t| t.try_into().unwrap()) .collect(), }); self.rec_groups.push(self.types.len()..self.types.len() + 1); @@ -1803,15 +1752,9 @@ impl Module { } } // For globals, add a new global. - wasmparser::types::EntityType::Global(global_type) => self - .add_arbitrary_global_of_type( - GlobalType { - val_type: convert_val_type(&global_type.content_type), - mutable: global_type.mutable, - shared: global_type.shared, - }, - u, - )?, + wasmparser::types::EntityType::Global(global_type) => { + self.add_arbitrary_global_of_type(global_type.try_into().unwrap(), u)? + } wasmparser::types::EntityType::Table(_) | wasmparser::types::EntityType::Memory(_) | wasmparser::types::EntityType::Tag(_) => { @@ -1821,11 +1764,8 @@ impl Module { ) } }; - self.exports.push(( - export.name.to_string(), - convert_export_kind(&export.kind), - new_index, - )); + self.exports + .push((export.name.to_string(), export.kind.into(), new_index)); self.export_names.insert(export.name.to_string()); } diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index d93ce0c012..97fdaa36f2 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -2091,8 +2091,8 @@ fn call_indirect( builder.pop_operands(module, &ty.params); builder.push_operands(&ty.results); instructions.push(Instruction::CallIndirect { - ty: *type_idx as u32, - table, + type_index: *type_idx as u32, + table_index: table, }); Ok(()) } @@ -2228,8 +2228,8 @@ fn return_call_indirect( builder.pop_operands(module, &ty.params); builder.push_operands(&ty.results); instructions.push(Instruction::ReturnCallIndirect { - ty: *type_idx as u32, - table, + type_index: *type_idx as u32, + table_index: table, }); Ok(()) } diff --git a/crates/wasmparser/Cargo.toml b/crates/wasmparser/Cargo.toml index d65cd46767..117681d72e 100644 --- a/crates/wasmparser/Cargo.toml +++ b/crates/wasmparser/Cargo.toml @@ -16,6 +16,9 @@ rust-version.workspace = true [lints] workspace = true +[package.metadata.docs.rs] +all-features = true + [dependencies] bitflags = "2.4.1" indexmap = { workspace = true, optional = true } diff --git a/crates/wasmparser/src/readers/core/operators.rs b/crates/wasmparser/src/readers/core/operators.rs index 69f8baec40..ff3ef8430a 100644 --- a/crates/wasmparser/src/readers/core/operators.rs +++ b/crates/wasmparser/src/readers/core/operators.rs @@ -78,6 +78,12 @@ impl Ieee32 { } } +impl From for f32 { + fn from(bits: Ieee32) -> f32 { + f32::from_bits(bits.bits()) + } +} + /// An IEEE binary64 immediate floating point value, represented as a u64 /// containing the bit pattern. /// @@ -92,6 +98,12 @@ impl Ieee64 { } } +impl From for f64 { + fn from(bits: Ieee64) -> f64 { + f64::from_bits(bits.bits()) + } +} + /// Represents a 128-bit vector value. #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] pub struct V128(pub(crate) [u8; 16]); @@ -108,6 +120,18 @@ impl V128 { } } +impl From for i128 { + fn from(bits: V128) -> i128 { + bits.i128() + } +} + +impl From for u128 { + fn from(bits: V128) -> u128 { + u128::from_le_bytes(bits.0) + } +} + /// Represents the memory ordering for atomic instructions. /// /// For an in-depth explanation of memory orderings, see the C++ documentation diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index 56eecf06a4..f045cb8366 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -1334,8 +1334,8 @@ impl<'a> EncodingState<'a> { } func.instruction(&Instruction::I32Const(func_index as i32)); func.instruction(&Instruction::CallIndirect { - ty: type_index, - table: 0, + type_index, + table_index: 0, }); func.instruction(&Instruction::End); code.function(&func); diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs index bb857f94e2..0fa13d85f4 100644 --- a/crates/wit-component/src/gc.rs +++ b/crates/wit-component/src/gc.rs @@ -568,13 +568,7 @@ impl<'a> Module<'a> { let mut num_memories = 0; for (i, mem) in self.live_memories() { map.memories.push(i); - let ty = wasm_encoder::MemoryType { - minimum: mem.ty.initial, - maximum: mem.ty.maximum, - shared: mem.ty.shared, - memory64: mem.ty.memory64, - page_size_log2: mem.ty.page_size_log2, - }; + let ty = mem.ty.into(); match &mem.def { Definition::Import(m, n) => { imports.import(m, n, ty); @@ -1165,12 +1159,6 @@ macro_rules! define_encode { (mk BrTable $arg:ident) => ({ BrTable($arg.0, $arg.1) }); - (mk CallIndirect $ty:ident $table:ident) => ({ - CallIndirect { ty: $ty, table: $table } - }); - (mk ReturnCallIndirect $ty:ident $table:ident) => ( - ReturnCallIndirect { ty: $ty, table: $table } - ); (mk TryTable $try_table:ident) => ({ let _ = $try_table; unimplemented_try_table() diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs index e3923c1e3c..855fc0f3a6 100644 --- a/crates/wit-component/src/linking.rs +++ b/crates/wit-component/src/linking.rs @@ -434,8 +434,8 @@ fn make_env_module<'a>( } function.instruction(&Ins::I32Const(i32::try_from(table_offset).unwrap())); function.instruction(&Ins::CallIndirect { - ty: u32::try_from(index).unwrap(), - table: 0, + type_index: u32::try_from(index).unwrap(), + table_index: 0, }); function.instruction(&Ins::End); code.function(&function); From 3e38098d60b34b92d93b46740fbb560433888964 Mon Sep 17 00:00:00 2001 From: Yosh Date: Thu, 20 Jun 2024 19:06:28 +0200 Subject: [PATCH 24/58] Initialize a test suite for `wit-encoder` (#1626) * change `wit-encoder` indent to 2 * test empty `Result`s in `wit-encoder` * import wit test files for `wit-encoder` * use external wit test file for `functions.rs` test * add `empty` test * add `wit-encoder` overlap tests * fix more existing tests * Add `include` capabilities to `wit-encoder` * tidy up * rebase on main * check in `.gitattributes` to normalize line endings Rebasing brok * remove additional wit files * inline tests --- .gitattributes | 1 + Cargo.lock | 7 + Cargo.toml | 1 + crates/wit-encoder/Cargo.toml | 3 + crates/wit-encoder/src/function.rs | 13 +- crates/wit-encoder/src/include.rs | 26 ++++ crates/wit-encoder/src/lib.rs | 2 + crates/wit-encoder/src/package.rs | 13 +- crates/wit-encoder/src/render.rs | 2 +- crates/wit-encoder/src/ty.rs | 7 +- crates/wit-encoder/src/world.rs | 34 ++++- crates/wit-encoder/tests/empty.rs | 12 ++ crates/wit-encoder/tests/functions.rs | 70 ++++----- .../tests/import-export-overlap1.rs | 23 +++ .../tests/import-export-overlap2.rs | 23 +++ crates/wit-encoder/tests/include-reps.rs | 42 +++++ crates/wit-encoder/tests/type_defs.rs | 144 +++++++++--------- crates/wit-encoder/tests/world.rs | 25 +-- 18 files changed, 314 insertions(+), 134 deletions(-) create mode 100644 crates/wit-encoder/src/include.rs create mode 100644 crates/wit-encoder/tests/empty.rs create mode 100644 crates/wit-encoder/tests/import-export-overlap1.rs create mode 100644 crates/wit-encoder/tests/import-export-overlap2.rs create mode 100644 crates/wit-encoder/tests/include-reps.rs diff --git a/.gitattributes b/.gitattributes index 684c5c1eef..274b96bad3 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,2 +1,3 @@ +* text=auto *.waves text eol=lf *.out text eol=lf diff --git a/Cargo.lock b/Cargo.lock index 5ed751ad42..ee75653594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -801,6 +801,12 @@ dependencies = [ "serde", ] +[[package]] +name = "indoc" +version = "2.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" + [[package]] name = "instant" version = "0.1.12" @@ -2205,6 +2211,7 @@ dependencies = [ name = "wit-encoder" version = "0.211.1" dependencies = [ + "indoc", "pretty_assertions", "semver", ] diff --git a/Cargo.toml b/Cargo.toml index a8b01554ff..d7d9e3d4f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -84,6 +84,7 @@ bitflags = "2.5.0" hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] } ahash = { version = "0.8.11", default-features = false } termcolor = "1.2.0" +indoc = "2.0.5" wasm-compose = { version = "0.211.1", path = "crates/wasm-compose" } wasm-encoder = { version = "0.211.1", path = "crates/wasm-encoder" } diff --git a/crates/wit-encoder/Cargo.toml b/crates/wit-encoder/Cargo.toml index ae5da01377..00f80ba338 100644 --- a/crates/wit-encoder/Cargo.toml +++ b/crates/wit-encoder/Cargo.toml @@ -13,3 +13,6 @@ workspace = true [dependencies] semver = { workspace = true } pretty_assertions = { workspace = true } + +[dev-dependencies] +indoc = { workspace = true } diff --git a/crates/wit-encoder/src/function.rs b/crates/wit-encoder/src/function.rs index fa3e81e2f2..e0ffe79e8c 100644 --- a/crates/wit-encoder/src/function.rs +++ b/crates/wit-encoder/src/function.rs @@ -108,7 +108,7 @@ where impl Results { // For the common case of an empty results list. pub fn empty() -> Results { - Results::Named(Default::default()) + Results::Named(Params::empty()) } pub fn anon(type_: Type) -> Results { @@ -166,3 +166,14 @@ impl StandaloneFunc { self.docs = docs.map(|d| d.into()); } } + +#[cfg(test)] +mod test { + use crate::Results; + + #[test] + fn is_empty() { + let res = Results::empty(); + assert!(res.is_empty()); + } +} diff --git a/crates/wit-encoder/src/include.rs b/crates/wit-encoder/src/include.rs new file mode 100644 index 0000000000..747186ccff --- /dev/null +++ b/crates/wit-encoder/src/include.rs @@ -0,0 +1,26 @@ +use std::fmt; + +use crate::{Ident, Render}; + +/// Enable the union of a world with another world +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Include { + use_path: Ident, + include_names_list: Vec, +} + +impl Include { + pub fn new(use_path: impl Into) -> Self { + Self { + use_path: use_path.into(), + include_names_list: vec![], + } + } +} + +impl Render for Include { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &crate::RenderOpts) -> fmt::Result { + write!(f, "{}include {};\n", opts.spaces(), self.use_path)?; + Ok(()) + } +} diff --git a/crates/wit-encoder/src/lib.rs b/crates/wit-encoder/src/lib.rs index 908904da46..c4e8eb4fc2 100644 --- a/crates/wit-encoder/src/lib.rs +++ b/crates/wit-encoder/src/lib.rs @@ -8,6 +8,7 @@ mod enum_; mod flags; mod function; mod ident; +mod include; mod interface; mod package; mod record; @@ -24,6 +25,7 @@ pub use enum_::*; pub use flags::*; pub use function::*; pub use ident::*; +pub use include::*; pub use interface::*; pub use package::*; pub use record::*; diff --git a/crates/wit-encoder/src/package.rs b/crates/wit-encoder/src/package.rs index d25abfc4e8..2c5eca4c23 100644 --- a/crates/wit-encoder/src/package.rs +++ b/crates/wit-encoder/src/package.rs @@ -41,16 +41,21 @@ impl Package { impl Render for Package { fn render(&self, f: &mut fmt::Formatter<'_>, opts: &RenderOpts) -> fmt::Result { write!(f, "{}package {};\n", opts.spaces(), self.name)?; - write!(f, "\n")?; for item in &self.items { + write!(f, "\n")?; match item { PackageItem::Interface(interface) => { if let Some(docs) = &interface.docs { docs.render(f, opts)?; } - write!(f, "{}interface {} {{\n", opts.spaces(), interface.name)?; - interface.items.render(f, &opts.indent())?; - write!(f, "{}}}\n", opts.spaces())?; + write!(f, "{}interface {} {{", opts.spaces(), interface.name)?; + if !interface.items.is_empty() { + write!(f, "\n")?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } else { + write!(f, "}}\n")?; + } } PackageItem::World(world) => { world.render(f, opts)?; diff --git a/crates/wit-encoder/src/render.rs b/crates/wit-encoder/src/render.rs index 2339e32077..62cda3c8cc 100644 --- a/crates/wit-encoder/src/render.rs +++ b/crates/wit-encoder/src/render.rs @@ -11,7 +11,7 @@ pub struct RenderOpts { impl Default for RenderOpts { fn default() -> Self { Self { - indent_width: 4, + indent_width: 2, ident_count: 0, } } diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs index dea714e4ff..ec2e0a08ae 100644 --- a/crates/wit-encoder/src/ty.rs +++ b/crates/wit-encoder/src/ty.rs @@ -317,8 +317,11 @@ impl Render for TypeDef { if let Some(docs) = &self.docs { docs.render(f, opts)?; } - write!(f, "{}record {} {{\n", opts.spaces(), self.name)?; - for field in &record.fields { + write!(f, "{}record {} {{", opts.spaces(), self.name)?; + for (index, field) in record.fields.iter().enumerate() { + if index == 0 { + write!(f, "\n")?; + } let opts = opts.indent(); if let Some(docs) = &field.docs { docs.render(f, &opts)?; diff --git a/crates/wit-encoder/src/world.rs b/crates/wit-encoder/src/world.rs index 867d550550..5f83767878 100644 --- a/crates/wit-encoder/src/world.rs +++ b/crates/wit-encoder/src/world.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{ident::Ident, Docs, Interface, Render, RenderOpts, StandaloneFunc}; +use crate::{ident::Ident, Docs, Include, Interface, Render, RenderOpts, StandaloneFunc}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct World { @@ -52,6 +52,9 @@ impl World { pub fn function_export(&mut self, value: StandaloneFunc) { self.item(WorldItem::function_export(value)); } + pub fn include(&mut self, value: impl Into) { + self.item(WorldItem::include(value)); + } /// Set the documentation pub fn docs(&mut self, docs: Option>) { @@ -88,18 +91,28 @@ impl Render for World { docs.render(f, opts)?; } import(f, opts)?; - write!(f, "{}: interface {{\n", interface.name)?; - interface.items.render(f, &opts.indent())?; - write!(f, "{}}}\n", opts.spaces())?; + write!(f, "{}: interface {{", interface.name)?; + if !interface.items.is_empty() { + write!(f, "\n")?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } else { + write!(f, "}}\n")?; + } } WorldItem::InlineInterfaceExport(interface) => { if let Some(docs) = &interface.docs { docs.render(f, opts)?; } export(f, opts)?; - write!(f, "{}: interface {{\n", interface.name)?; - interface.items.render(f, &opts.indent())?; - write!(f, "{}}}\n", opts.spaces())?; + write!(f, "{}: interface {{", interface.name)?; + if !interface.items.is_empty() { + write!(f, "\n")?; + interface.items.render(f, &opts.indent())?; + write!(f, "{}}}\n", opts.spaces())?; + } else { + write!(f, "}}\n")?; + } } WorldItem::NamedInterfaceImport(interface) => { if let Some(docs) = &interface.docs { @@ -129,6 +142,7 @@ impl Render for World { export(f, opts)?; render_function(f, opts, function)?; } + WorldItem::Include(include) => include.render(f, opts)?, } } let opts = &opts.outdent(); @@ -156,6 +170,9 @@ pub enum WorldItem { /// A function is being directly exported from this world. FunctionExport(StandaloneFunc), + + /// Include type + Include(Include), } impl WorldItem { @@ -177,6 +194,9 @@ impl WorldItem { pub fn function_export(value: StandaloneFunc) -> Self { Self::FunctionExport(value) } + pub fn include(value: impl Into) -> Self { + Self::Include(Include::new(value)) + } } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] diff --git a/crates/wit-encoder/tests/empty.rs b/crates/wit-encoder/tests/empty.rs new file mode 100644 index 0000000000..ecf28aca18 --- /dev/null +++ b/crates/wit-encoder/tests/empty.rs @@ -0,0 +1,12 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Package, PackageName}; + +const PACKAGE: &str = indoc::indoc! {" + package foo:empty; +"}; + +#[test] +fn concrete_types() { + let package = Package::new(PackageName::new("foo", "empty", None)); + assert_eq!(package.to_string(), PACKAGE); +} diff --git a/crates/wit-encoder/tests/functions.rs b/crates/wit-encoder/tests/functions.rs index fd5c5e8c6c..b65a7b45ef 100644 --- a/crates/wit-encoder/tests/functions.rs +++ b/crates/wit-encoder/tests/functions.rs @@ -1,32 +1,33 @@ use pretty_assertions::assert_eq; -use wit_encoder::{Params, Result_, Results, StandaloneFunc, Type}; +use wit_encoder::{ + Interface, Package, PackageName, Params, Result_, Results, StandaloneFunc, Type, +}; -const PACKAGE: &str = "package foo:functions; +const PACKAGE: &str = indoc::indoc! {" + package foo:functions; -interface functions { - f1: func(); - f2: func(a: u32); - f3: func() -> u32; - /// this is a documentation comment - /// for the f4 function - f4: func() -> tuple; - f5: func(a: f32, b: f32) -> tuple; - f6: func(a: option) -> result; - f7: func() -> (u: u32, f: f32); - f8: func() -> (u: u32); - f9: func() -> result; - f10: func() -> result<_, f32>; - f11: func() -> result; -} -"; + interface functions { + f1: func(); + f2: func(a: u32); + f4: func() -> u32; + f6: func() -> tuple; + f7: func(a: f32, b: f32) -> tuple; + f8: func(a: option) -> result; + f9: func() -> (u: u32, f: f32); + f10: func() -> (u: u32); + f11: func() -> result; + f12: func() -> result<_, f32>; + f13: func() -> result; + } +"}; #[test] -fn smoke() { - let name = wit_encoder::PackageName::new("foo", "functions", None); - let mut package = wit_encoder::Package::new(name); +fn concrete_types() { + let name = PackageName::new("foo", "functions", None); + let mut package = Package::new(name); package.interface({ - let mut interface = wit_encoder::Interface::new("functions"); + let mut interface = Interface::new("functions"); interface.function(StandaloneFunc::new("f1")); interface.function({ let mut func = StandaloneFunc::new("f2"); @@ -34,55 +35,54 @@ fn smoke() { func }); interface.function({ - let mut func = StandaloneFunc::new("f3"); - func.results(Type::U32); + let mut func = StandaloneFunc::new("f4"); + func.results(Results::anon(Type::U32)); func }); interface.function({ - let mut func = StandaloneFunc::new("f4"); - func.results(Type::tuple(vec![Type::U32, Type::U32])); - func.docs(Some("this is a documentation comment\nfor the f4 function")); + let mut func = StandaloneFunc::new("f6"); + func.results(Results::anon(Type::tuple(vec![Type::U32, Type::U32]))); func }); interface.function({ - let mut func = StandaloneFunc::new("f5"); + let mut func = StandaloneFunc::new("f7"); func.params(Params::from_iter([("a", Type::F32), ("b", Type::F32)])); func.results(Type::tuple(vec![Type::U32, Type::U32])); func }); interface.function({ - let mut func = StandaloneFunc::new("f6"); + let mut func = StandaloneFunc::new("f8"); func.params(Params::from_iter([("a", Type::option(Type::U32))])); func.results(Type::result(Result_::both(Type::U32, Type::F32))); func }); interface.function({ - let mut func = StandaloneFunc::new("f7"); + let mut func = StandaloneFunc::new("f9"); func.results(Results::named(vec![("u", Type::U32), ("f", Type::F32)])); func }); interface.function({ - let mut func = StandaloneFunc::new("f8"); + let mut func = StandaloneFunc::new("f10"); func.results(Results::named(vec![("u", Type::U32)])); func }); interface.function({ - let mut func = StandaloneFunc::new("f9"); + let mut func = StandaloneFunc::new("f11"); func.results(Type::result(Result_::ok(Type::F32))); func }); interface.function({ - let mut func = StandaloneFunc::new("f10"); + let mut func = StandaloneFunc::new("f12"); func.results(Type::result(Result_::err(Type::F32))); func }); interface.function({ - let mut func = StandaloneFunc::new("f11"); + let mut func = StandaloneFunc::new("f13"); func.results(Type::result(Result_::empty())); func }); interface }); - assert_eq!(PACKAGE, package.to_string()); + assert_eq!(package.to_string(), PACKAGE); } diff --git a/crates/wit-encoder/tests/import-export-overlap1.rs b/crates/wit-encoder/tests/import-export-overlap1.rs new file mode 100644 index 0000000000..e4a571256e --- /dev/null +++ b/crates/wit-encoder/tests/import-export-overlap1.rs @@ -0,0 +1,23 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Package, PackageName, StandaloneFunc, World}; + +const PACKAGE: &str = indoc::indoc! {" + package foo:foo; + + world foo { + import a: func(); + export a: func(); + } +"}; + +#[test] +fn concrete_types() { + let mut package = Package::new(PackageName::new("foo", "foo", None)); + + let mut world = World::new("foo"); + world.function_import(StandaloneFunc::new("a")); + world.function_export(StandaloneFunc::new("a")); + package.world(world); + + assert_eq!(package.to_string(), PACKAGE); +} diff --git a/crates/wit-encoder/tests/import-export-overlap2.rs b/crates/wit-encoder/tests/import-export-overlap2.rs new file mode 100644 index 0000000000..da98ca1e9e --- /dev/null +++ b/crates/wit-encoder/tests/import-export-overlap2.rs @@ -0,0 +1,23 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Interface, Package, PackageName, StandaloneFunc, World}; + +const PACKAGE: &str = indoc::indoc! {" + package foo:foo; + + world foo { + import a: func(); + export a: interface {} + } +"}; + +#[test] +fn concrete_types() { + let mut package = Package::new(PackageName::new("foo", "foo", None)); + + let mut world = World::new("foo"); + world.function_import(StandaloneFunc::new("a")); + world.inline_interface_export(Interface::new("a")); + package.world(world); + + assert_eq!(package.to_string(), PACKAGE); +} diff --git a/crates/wit-encoder/tests/include-reps.rs b/crates/wit-encoder/tests/include-reps.rs new file mode 100644 index 0000000000..5341e053a5 --- /dev/null +++ b/crates/wit-encoder/tests/include-reps.rs @@ -0,0 +1,42 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Interface, Package, PackageName, World}; + +const PACKAGE: &str = indoc::indoc! {" + package foo:foo; + + interface a {} + + interface b {} + + world bar { + import a; + export b; + } + + world foo { + include bar; + include bar; + include bar; + } +"}; + +#[test] +fn concrete_types() { + let mut package = Package::new(PackageName::new("foo", "foo", None)); + + package.interface(Interface::new("a")); + package.interface(Interface::new("b")); + + let mut world = World::new("bar"); + world.named_interface_import("a"); + world.named_interface_export("b"); + package.world(world); + + let mut world = World::new("foo"); + world.include("bar"); + world.include("bar"); + world.include("bar"); + package.world(world); + + assert_eq!(package.to_string(), PACKAGE); +} diff --git a/crates/wit-encoder/tests/type_defs.rs b/crates/wit-encoder/tests/type_defs.rs index c4723f9ea3..fb2d211a1c 100644 --- a/crates/wit-encoder/tests/type_defs.rs +++ b/crates/wit-encoder/tests/type_defs.rs @@ -3,109 +3,109 @@ use wit_encoder::{ Field, Flag, Params, ResourceFunc, Result_, Results, Type, TypeDef, TypeDefKind, VariantCase, }; -const PACKAGE: &str = "package wit-encoder:tests; +const PACKAGE: &str = indoc::indoc! {" + package wit-encoder:tests; -/// interface documentation -interface type-defs { - type t1 = u8; - type t2 = u16; - type t3 = u32; - type t4 = u64; - type t5 = s8; - type t6 = s16; - type t7 = s32; - type t8 = s64; - type t9a = f32; - type t9b = f32; - type t10a = f64; - type t10b = f64; - type t11 = char; - type t12 = list; - type t13 = string; - type t14 = option; - type t15 = result; - type t16 = result<_, u32>; - type t17 = result; - /// this is a documentation comment - type t18 = result; - record t20 { - } - record t21 { + /// interface documentation + interface type-defs { + type t1 = u8; + type t2 = u16; + type t3 = u32; + type t4 = u64; + type t5 = s8; + type t6 = s16; + type t7 = s32; + type t8 = s64; + type t9a = f32; + type t9b = f32; + type t10a = f64; + type t10b = f64; + type t11 = char; + type t12 = list; + type t13 = string; + type t14 = option; + type t15 = result; + type t16 = result<_, u32>; + type t17 = result; + /// this is a documentation comment + type t18 = result; + record t20 { } + record t21 { a: u32, - } - record t22 { + } + record t22 { a: u32, - } - record t23 { + } + record t23 { a: u32, b: u64, - } - record t24 { + } + record t24 { a: u32, b: u64, - } - record t25 { + } + record t25 { x: u32, - } - record %record { + } + record %record { a: u32, - } - type t26 = tuple<>; - type t27 = tuple; - type t29 = tuple; - flags t30 { - } - flags t31 { + } + type t26 = tuple<>; + type t27 = tuple; + type t29 = tuple; + flags t30 { + } + flags t31 { /// option a a, /// option b b, /// option c c, - } - flags t32 { + } + flags t32 { a, b, c, - } - variant t33 { + } + variant t33 { a, - } - variant t34 { + } + variant t34 { a, b, - } - variant t35 { + } + variant t35 { a, b, - } - variant t36 { + } + variant t36 { a, b(u32), - } - variant t37 { + } + variant t37 { a, b(option), - } - enum t41 { + } + enum t41 { a, b, c, - } - enum t42 { + } + enum t42 { a, b, c, - } - type t43 = bool; - type t44 = string; - type t45 = list>>; - type t46 = t44; - type foo = bar; - type bar = u32; - resource t50 { - } - resource t51 { + } + type t43 = bool; + type t44 = string; + type t45 = list>>; + type t46 = t44; + type foo = bar; + type bar = u32; + resource t50 { + } + resource t51 { /// create a new t51 constructor(a: u32); /// set a @@ -114,9 +114,9 @@ interface type-defs { get-a: func() -> u32; /// do b b: static func(); + } } -} -"; +"}; #[test] fn types() { diff --git a/crates/wit-encoder/tests/world.rs b/crates/wit-encoder/tests/world.rs index 38e71bb9d7..83777cc0ea 100644 --- a/crates/wit-encoder/tests/world.rs +++ b/crates/wit-encoder/tests/world.rs @@ -1,22 +1,23 @@ use pretty_assertions::assert_eq; use wit_encoder::{Interface, StandaloneFunc, Type}; -const PACKAGE: &str = "package foo:functions; +const PACKAGE: &str = indoc::indoc! {" + package foo:functions; -interface error-reporter { -} -world %world { - /// inline interface - export example: interface { + interface error-reporter {} + + world %world { + /// inline interface + export example: interface { /// func docs do-nothing: func(); + } + /// scan stuff + export scan: func() -> list; + import error-reporter; + import print: func(s: string); } - /// scan stuff - export scan: func() -> list; - import error-reporter; - import print: func(s: string); -} -"; +"}; #[test] fn worlds() { From 62cf274234fd54153408c1e2dcddbbfd13e50b76 Mon Sep 17 00:00:00 2001 From: Chris Fallin Date: Sat, 22 Jun 2024 19:45:29 -0700 Subject: [PATCH 25/58] wasm-encoder: add method to allow fetching raw Function bytes. (#1630) When generating function bodies with `wasm-encoder`, it is sometimes useful to take the function bytecode, cache it, then reuse it later. (For my specific use-case, in weval, I would like to cache weval-specialized function bodies and reuse them when creating new modules.) Unfortunately the existing API of `wasm-encoder` makes this *almost* but *not quite* easy: `Function` implements `Encode::encode`, but this will produce a function body *with* the length prefixed. However there's no way to use this unmodified with the next level up the entity hierarchy: `CodeSection::raw` wants the function bytes *without* the length prefixed. This is a fairly annoying if small API gap, and otherwise requires manually stripping the length prefix, a leb128-encoded integer. It's also a small footgun: I naively did not realize this mismatch and tried to do the above, only to get perplexing type errors with locals. This PR adds one method on `wasm_encoder::Function` to return the inner bytes directly, and the doc-comment contains an example showing the intended use-case. --- crates/wasm-encoder/src/core/code.rs | 58 ++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs index 25de73be66..7bee359788 100644 --- a/crates/wasm-encoder/src/core/code.rs +++ b/crates/wasm-encoder/src/core/code.rs @@ -274,6 +274,45 @@ impl Function { self.bytes.len() } + /// Unwraps and returns the raw byte encoding of this function. + /// + /// This encoding doesn't include the variable-width size field + /// that `encode` will write before the added bytes. As such, its + /// length will match the return value of [`byte_len`]. + /// + /// # Use Case + /// + /// This raw byte form is suitable for later using with + /// [`CodeSection::raw`]. Note that it *differs* from what results + /// from [`Function::encode`] precisely due to the *lack* of the + /// length prefix; [`CodeSection::raw`] will use this. Using + /// [`Function::encode`] instead produces bytes that cannot be fed + /// into other wasm-encoder types without stripping off the length + /// prefix, which is awkward and error-prone. + /// + /// This method combined with [`CodeSection::raw`] may be useful + /// together if one wants to save the result of function encoding + /// and use it later: for example, caching the result of some code + /// generation process. + /// + /// For example: + /// + /// ``` + /// # use wasm_encoder::{CodeSection, Function, Instruction}; + /// let mut f = Function::new([]); + /// f.instruction(&Instruction::End); + /// let bytes = f.into_raw_body(); + /// // (save `bytes` somewhere for later use) + /// let mut code = CodeSection::new(); + /// code.raw(&bytes[..]); + /// + /// assert_eq!(2, bytes.len()); // Locals count, then `end` + /// assert_eq!(3, code.byte_len()); // Function length byte, function body + /// ``` + pub fn into_raw_body(self) -> Vec { + self.bytes + } + /// Parses a single instruction from `reader` and adds it to `self`. #[cfg(feature = "wasmparser")] pub fn parse( @@ -3753,4 +3792,23 @@ mod tests { assert_eq!(f1.bytes, f2.bytes) } + + #[test] + fn func_raw_bytes() { + use super::*; + + let mut f = Function::new([(1, ValType::I32), (1, ValType::F32)]); + f.instruction(&Instruction::End); + let mut code_from_func = CodeSection::new(); + code_from_func.function(&f); + let bytes = f.into_raw_body(); + let mut code_from_raw = CodeSection::new(); + code_from_raw.raw(&bytes[..]); + + let mut c1 = vec![]; + code_from_func.encode(&mut c1); + let mut c2 = vec![]; + code_from_raw.encode(&mut c2); + assert_eq!(c1, c2); + } } From a5fa1eb8dcdadf36ce6b7aca1ea00ec5457d9917 Mon Sep 17 00:00:00 2001 From: Yosh Date: Sun, 23 Jun 2024 04:46:42 +0200 Subject: [PATCH 26/58] Support `include` statements in `wasm-parser` (#1631) We already had basic support for `include`, but this tests it and fixes it. --- crates/wit-encoder/src/include.rs | 22 +++++- crates/wit-encoder/src/world.rs | 4 +- crates/wit-encoder/tests/include-aliases.rs | 68 +++++++++++++++++++ crates/wit-encoder/tests/include-reps.rs | 8 +-- .../tests/kebab-name-include-with.rs | 41 +++++++++++ 5 files changed, 135 insertions(+), 8 deletions(-) create mode 100644 crates/wit-encoder/tests/include-aliases.rs create mode 100644 crates/wit-encoder/tests/kebab-name-include-with.rs diff --git a/crates/wit-encoder/src/include.rs b/crates/wit-encoder/src/include.rs index 747186ccff..9f276381a0 100644 --- a/crates/wit-encoder/src/include.rs +++ b/crates/wit-encoder/src/include.rs @@ -6,7 +6,7 @@ use crate::{Ident, Render}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Include { use_path: Ident, - include_names_list: Vec, + include_names_list: Vec<(String, String)>, } impl Include { @@ -16,11 +16,29 @@ impl Include { include_names_list: vec![], } } + + pub fn with(&mut self, id: &str, alias: &str) { + self.include_names_list + .push((id.to_string(), alias.to_string())); + } } impl Render for Include { fn render(&self, f: &mut fmt::Formatter<'_>, opts: &crate::RenderOpts) -> fmt::Result { - write!(f, "{}include {};\n", opts.spaces(), self.use_path)?; + match self.include_names_list.len() { + 0 => write!(f, "{}include {};\n", opts.spaces(), self.use_path)?, + len => { + write!(f, "{}include {} with {{ ", opts.spaces(), self.use_path)?; + for (i, (id, alias)) in self.include_names_list.iter().enumerate() { + if i == len - 1 { + write!(f, "{id} as {alias}")?; + } else { + write!(f, "{id} as {alias}, ")?; + } + } + write!(f, " }};\n")?; + } + } Ok(()) } } diff --git a/crates/wit-encoder/src/world.rs b/crates/wit-encoder/src/world.rs index 5f83767878..7203524bb1 100644 --- a/crates/wit-encoder/src/world.rs +++ b/crates/wit-encoder/src/world.rs @@ -52,8 +52,8 @@ impl World { pub fn function_export(&mut self, value: StandaloneFunc) { self.item(WorldItem::function_export(value)); } - pub fn include(&mut self, value: impl Into) { - self.item(WorldItem::include(value)); + pub fn include(&mut self, include: Include) { + self.item(WorldItem::Include(include)); } /// Set the documentation diff --git a/crates/wit-encoder/tests/include-aliases.rs b/crates/wit-encoder/tests/include-aliases.rs new file mode 100644 index 0000000000..140ce44d85 --- /dev/null +++ b/crates/wit-encoder/tests/include-aliases.rs @@ -0,0 +1,68 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Include, Package, PackageName, StandaloneFunc, World}; + +const PACKAGE: &str = indoc::indoc! {" + package foo:foo; + + world foo { + import a: func(); + } + + world bar { + import a: func(); + import b: func(); + } + + world baz { + import a: func(); + import b: func(); + import c: func(); + } + + world quux { + include foo with { a as b }; + include bar with { a as b, b as c }; + include baz with { a as b, b as c, c as d }; + } +"}; + +#[test] +fn concrete_types() { + let mut package = Package::new(PackageName::new("foo", "foo", None)); + + let mut world = World::new("foo"); + world.function_import(StandaloneFunc::new("a")); + package.world(world); + + let mut world = World::new("bar"); + world.function_import(StandaloneFunc::new("a")); + world.function_import(StandaloneFunc::new("b")); + package.world(world); + + let mut world = World::new("baz"); + world.function_import(StandaloneFunc::new("a")); + world.function_import(StandaloneFunc::new("b")); + world.function_import(StandaloneFunc::new("c")); + package.world(world); + + let mut world = World::new("quux"); + + let mut include = Include::new("foo"); + include.with("a", "b"); + world.include(include); + + let mut include = Include::new("bar"); + include.with("a", "b"); + include.with("b", "c"); + world.include(include); + + let mut include = Include::new("baz"); + include.with("a", "b"); + include.with("b", "c"); + include.with("c", "d"); + world.include(include); + + package.world(world); + + assert_eq!(package.to_string(), PACKAGE); +} diff --git a/crates/wit-encoder/tests/include-reps.rs b/crates/wit-encoder/tests/include-reps.rs index 5341e053a5..a05fb3ac89 100644 --- a/crates/wit-encoder/tests/include-reps.rs +++ b/crates/wit-encoder/tests/include-reps.rs @@ -1,5 +1,5 @@ use pretty_assertions::assert_eq; -use wit_encoder::{Interface, Package, PackageName, World}; +use wit_encoder::{Include, Interface, Package, PackageName, World}; const PACKAGE: &str = indoc::indoc! {" package foo:foo; @@ -33,9 +33,9 @@ fn concrete_types() { package.world(world); let mut world = World::new("foo"); - world.include("bar"); - world.include("bar"); - world.include("bar"); + world.include(Include::new("bar")); + world.include(Include::new("bar")); + world.include(Include::new("bar")); package.world(world); assert_eq!(package.to_string(), PACKAGE); diff --git a/crates/wit-encoder/tests/kebab-name-include-with.rs b/crates/wit-encoder/tests/kebab-name-include-with.rs new file mode 100644 index 0000000000..f8afb2ab17 --- /dev/null +++ b/crates/wit-encoder/tests/kebab-name-include-with.rs @@ -0,0 +1,41 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Include, Package, PackageName, StandaloneFunc, World}; + +const PACKAGE: &str = indoc::indoc! {" + package foo:foo; + + world foo { + import a: func(); + } + + world bar { + import a: func(); + } + + world baz { + include bar; + include foo with { a as b }; + } +"}; + +#[test] +fn concrete_types() { + let mut package = Package::new(PackageName::new("foo", "foo", None)); + + let mut world = World::new("foo"); + world.function_import(StandaloneFunc::new("a")); + package.world(world); + + let mut world = World::new("bar"); + world.function_import(StandaloneFunc::new("a")); + package.world(world); + + let mut world = World::new("baz"); + world.include(Include::new("bar")); + let mut include = Include::new("foo"); + include.with("a", "b"); + world.include(include); + package.world(world); + + assert_eq!(package.to_string(), PACKAGE); +} From 28e96f680752c999f296e5f9d7d31a6be66112f5 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Mon, 24 Jun 2024 12:05:29 -0500 Subject: [PATCH 27/58] Rename usage of `wasm32-wasi` to `wasm32-wasip1` (#1634) * Rename usage of `wasm32-wasi` to `wasm32-wasip1` This'll start warning on nightly soon so go ahead and get a rename in now that stable supports this target name. * Add wasm32-wasip1 target * More wasi installs * Update libdl build * Bless some test changes, more CI tweaks --- .github/workflows/main.yml | 20 +- .github/workflows/playground.yml | 1 + CONTRIBUTING.md | 2 +- ci/build-tarballs.sh | 4 +- crates/wit-component/dl/build.sh | 4 +- crates/wit-component/dl/check.sh | 4 +- crates/wit-component/libdl.so | Bin 1369 -> 1325 bytes .../component.wat | 325 ++++++++---------- .../component.wat | 325 ++++++++---------- playground/package.json | 4 +- tests/cli/dump-llvm-object.wat | 2 +- 11 files changed, 324 insertions(+), 367 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 24fb01f0cf..c3d14f4cf6 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -33,9 +33,9 @@ jobs: - build: aarch64-linux os: ubuntu-latest target: aarch64-unknown-linux-gnu - - build: wasm32-wasi + - build: wasm32-wasip1 os: ubuntu-latest - target: wasm32-wasi + target: wasm32-wasip1 steps: - uses: actions/checkout@v4 with: @@ -44,7 +44,7 @@ jobs: - uses: bytecodealliance/wasmtime/.github/actions/binary-compatible-builds@v20.0.0 with: name: ${{ matrix.build }} - if: matrix.build != 'wasm32-wasi' + if: matrix.build != 'wasm32-wasip1' - run: | echo CARGO_BUILD_TARGET=${{ matrix.target }} >> $GITHUB_ENV rustup target add ${{ matrix.target }} @@ -132,11 +132,12 @@ jobs: submodules: true - uses: bytecodealliance/wasmtime/.github/actions/install-rust@v20.0.0 with: - toolchain: 1.77.0 + toolchain: 1.79.0 + - run: rustup target add wasm32-wasip1 - run: | - curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-21/wasi-sdk-21.0-linux.tar.gz - tar xf wasi-sdk-21.0-linux.tar.gz - export WASI_SDK_PATH=$(pwd)/wasi-sdk-21.0 + curl -LO https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-22/wasi-sdk-22.0-linux.tar.gz + tar xf wasi-sdk-22.0-linux.tar.gz + export WASI_SDK_PATH=$(pwd)/wasi-sdk-22.0 cd crates/wit-component/dl && bash check.sh wasm: @@ -147,13 +148,14 @@ jobs: with: submodules: true - uses: bytecodealliance/wasmtime/.github/actions/install-rust@v20.0.0 + - run: rustup target add wasm32-wasip1 - run: | tag=v10.0.1 curl -LO https://github.com/bytecodealliance/wasmtime/releases/download/${tag}/wasmtime-${tag}-x86_64-linux.tar.xz tar xf wasmtime-${tag}-x86_64-linux.tar.xz echo `pwd`/wasmtime-${tag}-x86_64-linux >> $GITHUB_PATH - echo CARGO_TARGET_WASM32_WASI_RUNNER='wasmtime run --dir . --' >> $GITHUB_ENV - echo CARGO_BUILD_TARGET='wasm32-wasi' >> $GITHUB_ENV + echo CARGO_TARGET_WASM32_WASIP1_RUNNER='wasmtime run --dir . --' >> $GITHUB_ENV + echo CARGO_BUILD_TARGET='wasm32-wasip1' >> $GITHUB_ENV - run: | cargo --locked test --workspace \ --exclude fuzz-stats \ diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index b13631feeb..190dfe99b4 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -30,6 +30,7 @@ jobs: - uses: cargo-bins/cargo-binstall@v1.6.9 - run: cargo binstall cargo-component -y - run: rustup component add rustfmt # needed for cargo-component, apparently? + - run: rustup target add wasm32-wasip1 - run: npm ci working-directory: playground - run: npm run build diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4631656c7a..09a5f5ff34 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -107,7 +107,7 @@ merged once all tests are passing. CI checks currently include: * Code is all formatted correctly (use `cargo fmt` locally to pass this) * Tests pass on Rust stable, beta, and Nightly. * Tests pass on Linux, macOS, and Windows. -* This tool can be compiled to WebAssembly using the `wasm32-wasi` target. +* This tool can be compiled to WebAssembly using the `wasm32-wasip1` target. * Fuzzers can be built. * Various miscellaneous checks such as building the tool with various combinations of Cargo features. diff --git a/ci/build-tarballs.sh b/ci/build-tarballs.sh index 93be037a37..6e7eee790d 100755 --- a/ci/build-tarballs.sh +++ b/ci/build-tarballs.sh @@ -19,8 +19,8 @@ fmt=tar if [ "$platform" = "x86_64-windows" ]; then cp target/release/wasm-tools.exe tmp/$bin_pkgname fmt=zip -elif [ "$target" = "wasm32-wasi" ]; then - cp target/wasm32-wasi/release/wasm-tools.wasm tmp/$bin_pkgname +elif [ "$target" = "wasm32-wasip1" ]; then + cp target/wasm32-wasip1/release/wasm-tools.wasm tmp/$bin_pkgname elif [ "$target" = "" ]; then cp target/release/wasm-tools tmp/$bin_pkgname else diff --git a/crates/wit-component/dl/build.sh b/crates/wit-component/dl/build.sh index 9751d078d6..21dc28d0e3 100644 --- a/crates/wit-component/dl/build.sh +++ b/crates/wit-component/dl/build.sh @@ -7,7 +7,7 @@ set -ex -CARGO_PROFILE_RELEASE_LTO=true RUSTFLAGS="-C relocation-model=pic" cargo build --release --target=wasm32-wasi -$WASI_SDK_PATH/bin/clang -shared -o $1 -Wl,--whole-archive ../../../target/wasm32-wasi/release/libdl.a -Wl,--no-whole-archive +CARGO_PROFILE_RELEASE_LTO=true RUSTFLAGS="-C relocation-model=pic" cargo build --release --target=wasm32-wasip1 +$WASI_SDK_PATH/bin/clang -shared -o $1 -Wl,--whole-archive ../../../target/wasm32-wasip1/release/libdl.a -Wl,--no-whole-archive cargo run --manifest-path ../../../Cargo.toml -- strip $1 -o $1 cargo run --manifest-path ../../../Cargo.toml -- strip --delete name $1 -o $1 diff --git a/crates/wit-component/dl/check.sh b/crates/wit-component/dl/check.sh index 6ae4857bbe..c7b49ae0e7 100644 --- a/crates/wit-component/dl/check.sh +++ b/crates/wit-component/dl/check.sh @@ -1,7 +1,7 @@ set -ex -bash ./build.sh ../../../target/wasm32-wasi/release/tmp.so -if diff ../../../target/wasm32-wasi/release/tmp.so ../libdl.so; then +bash ./build.sh ../../../target/wasm32-wasip1/release/tmp.so +if diff ../../../target/wasm32-wasip1/release/tmp.so ../libdl.so; then exit 0 else echo "libdl.so is out-of-date; please run crates/wit-component/dl/build.sh to update it">&2 diff --git a/crates/wit-component/libdl.so b/crates/wit-component/libdl.so index 8ff016f0ac795d2a46ed9b5f830482e39e3260a3..47e778d6100dbea815078e26d398cc00c266308d 100755 GIT binary patch delta 307 zcmYk%y-EW?5C`!2%0-eKIY@a#X1B>Tf?BE7ZEpq}ltUFg zM}^xG#sQ}3z;o9&vLwHfV)bMPoKQ=xLPID1 z2eI444FWiua)l`ai-0>NOA#iI(L<1Eixl$^cu-NeR$127a|ZoTt+di%?V&3T(Ly1> leeH`3MWy__t39op_0oeZW_g9tmSH-sUa|uA&C~QM`)}JUG>!lO delta 391 zcmXYsJ5B>J5QaSud!sB%R3;nEC#1zJ`Cp?_DZ4GF@k(63;;bfa3J-9&E*;wCsq!SF8c z6-%frK|&-QvNG(GU`hJAC@EyN8qqk@3Ob&yS=Xwmj>mSI3wK6DQDyCSH(es7Ly>*u dW_R5soW~&4kNN1JvfzbM2cx=uGfe-;t2O6SLCpXF diff --git a/crates/wit-component/tests/components/link-dl-openable-builtin-libdl-with-unused/component.wat b/crates/wit-component/tests/components/link-dl-openable-builtin-libdl-with-unused/component.wat index 5554677f3b..22ce23d33e 100644 --- a/crates/wit-component/tests/components/link-dl-openable-builtin-libdl-with-unused/component.wat +++ b/crates/wit-component/tests/components/link-dl-openable-builtin-libdl-with-unused/component.wat @@ -109,7 +109,7 @@ call 2 ) (func (;5;) (type 0) (param i32) (result i32) - (local i32 i32 i32 i32 i32) + (local i32 i32 i32) block ;; label = @1 global.get 0 i32.const 148 @@ -120,53 +120,42 @@ br_if 0 (;@1;) local.get 1 i32.load + i32.const 1 + i32.add local.set 2 - i32.const 0 - local.set 3 i32.const -16 - local.set 4 + local.set 3 block ;; label = @2 - block ;; label = @3 - loop ;; label = @4 - local.get 2 - local.get 3 - local.tee 5 - i32.eq - br_if 1 (;@3;) - local.get 5 - i32.const 1 - i32.add - local.set 3 - local.get 1 - i32.load offset=4 - local.get 4 - i32.const 16 - i32.add - local.tee 4 - i32.add - local.get 0 - i32.ne - br_if 0 (;@4;) - end - i32.const 0 - local.set 3 - local.get 5 + loop ;; label = @3 local.get 2 - i32.lt_u + i32.const -1 + i32.add + local.tee 2 + i32.eqz br_if 1 (;@2;) + local.get 1 + i32.load offset=4 + local.get 3 + i32.const 16 + i32.add + local.tee 3 + i32.add + local.get 0 + i32.ne + br_if 0 (;@3;) end - global.get 0 - local.tee 3 - i32.const 144 - i32.add - local.get 3 i32.const 0 - i32.add - i32.store - i32.const -1 - local.set 3 + return end - local.get 3 + global.get 0 + local.tee 2 + i32.const 144 + i32.add + local.get 2 + i32.const 0 + i32.add + i32.store + i32.const -1 return end call 6 @@ -337,157 +326,145 @@ return end block ;; label = @1 - global.get 0 - i32.const 148 - i32.add - i32.load - local.tee 3 - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.load - local.set 4 - i32.const 0 - local.set 2 - i32.const -16 - local.set 5 block ;; label = @2 - block ;; label = @3 - loop ;; label = @4 - local.get 4 - local.get 2 - local.tee 6 - i32.eq - br_if 1 (;@3;) - local.get 6 - i32.const 1 - i32.add - local.set 2 - local.get 3 - i32.load offset=4 - local.get 5 - i32.const 16 - i32.add - local.tee 5 - i32.add - local.get 0 - i32.ne - br_if 0 (;@4;) - end - local.get 6 - local.get 4 - i32.lt_u - br_if 1 (;@2;) - end global.get 0 - local.tee 2 - i32.const 144 + i32.const 148 i32.add - local.get 2 - i32.const 0 + i32.load + local.tee 3 + i32.eqz + br_if 0 (;@2;) + local.get 3 + i32.load + i32.const 1 i32.add - i32.store - i32.const 0 - return - end - local.get 1 - call 0 - local.set 5 - block ;; label = @2 - block ;; label = @3 - local.get 0 - i32.load offset=8 - local.tee 6 + local.set 2 + i32.const -16 + local.set 4 + loop ;; label = @3 + local.get 2 + i32.const -1 + i32.add + local.tee 2 i32.eqz - br_if 0 (;@3;) - local.get 0 - i32.const 12 + br_if 2 (;@1;) + local.get 3 + i32.load offset=4 + local.get 4 + i32.const 16 i32.add - i32.load - local.set 7 - i32.const 0 - local.set 2 - local.get 6 - local.set 3 - loop ;; label = @4 - local.get 7 - local.get 6 - i32.const 1 - i32.shr_u - local.get 2 - i32.add - local.tee 0 - i32.const 12 - i32.mul - i32.add - local.tee 6 - i32.const 4 - i32.add - i32.load - local.get 1 - local.get 6 - i32.load - local.tee 6 - local.get 5 - local.get 6 - local.get 5 - i32.lt_u - select - call 1 - local.tee 4 - local.get 6 - local.get 5 - i32.sub - local.get 4 - select - local.tee 6 - i32.eqz - br_if 2 (;@2;) + local.tee 4 + i32.add + local.get 0 + i32.ne + br_if 0 (;@3;) + end + local.get 1 + call 0 + local.set 4 + block ;; label = @3 + block ;; label = @4 local.get 0 - local.get 3 - local.get 6 - i32.const 0 - i32.gt_s - select + i32.load offset=8 local.tee 3 + i32.eqz + br_if 0 (;@4;) local.get 0 - i32.const 1 - i32.add - local.get 2 - local.get 6 + i32.load offset=12 + local.set 5 i32.const 0 - i32.lt_s - select - local.tee 2 - i32.sub - local.set 6 + local.set 2 local.get 3 - local.get 2 - i32.gt_u - br_if 0 (;@4;) + local.set 6 + loop ;; label = @5 + local.get 5 + local.get 3 + i32.const 1 + i32.shr_u + local.get 2 + i32.add + local.tee 3 + i32.const 12 + i32.mul + i32.add + local.tee 0 + i32.const 4 + i32.add + i32.load + local.get 1 + local.get 0 + i32.load + local.tee 0 + local.get 4 + local.get 0 + local.get 4 + i32.lt_u + select + call 1 + local.tee 7 + local.get 0 + local.get 4 + i32.sub + local.get 7 + select + local.tee 0 + i32.eqz + br_if 2 (;@3;) + local.get 3 + local.get 6 + local.get 0 + i32.const 0 + i32.gt_s + select + local.tee 6 + local.get 3 + i32.const 1 + i32.add + local.get 2 + local.get 0 + i32.const 0 + i32.lt_s + select + local.tee 2 + i32.sub + local.set 3 + local.get 6 + local.get 2 + i32.gt_u + br_if 0 (;@5;) + end end + global.get 0 + local.tee 2 + i32.const 144 + i32.add + local.get 2 + i32.const 72 + i32.add + i32.store + i32.const 0 + return end - global.get 0 - local.tee 2 - i32.const 144 - i32.add - local.get 2 - i32.const 72 + local.get 5 + local.get 3 + i32.const 12 + i32.mul i32.add - i32.store - i32.const 0 + i32.load offset=8 return end - local.get 7 - local.get 0 - i32.const 12 - i32.mul - i32.add - i32.load offset=8 - return + call 6 + unreachable end - call 6 - unreachable + global.get 0 + local.tee 2 + i32.const 144 + i32.add + local.get 2 + i32.const 0 + i32.add + i32.store + i32.const 0 ) (func (;10;) (type 5) (param i32) global.get 0 diff --git a/crates/wit-component/tests/components/link-dl-openable-builtin-libdl/component.wat b/crates/wit-component/tests/components/link-dl-openable-builtin-libdl/component.wat index 684c79dae2..33b9967597 100644 --- a/crates/wit-component/tests/components/link-dl-openable-builtin-libdl/component.wat +++ b/crates/wit-component/tests/components/link-dl-openable-builtin-libdl/component.wat @@ -119,7 +119,7 @@ call 2 ) (func (;5;) (type 0) (param i32) (result i32) - (local i32 i32 i32 i32 i32) + (local i32 i32 i32) block ;; label = @1 global.get 0 i32.const 148 @@ -130,53 +130,42 @@ br_if 0 (;@1;) local.get 1 i32.load + i32.const 1 + i32.add local.set 2 - i32.const 0 - local.set 3 i32.const -16 - local.set 4 + local.set 3 block ;; label = @2 - block ;; label = @3 - loop ;; label = @4 - local.get 2 - local.get 3 - local.tee 5 - i32.eq - br_if 1 (;@3;) - local.get 5 - i32.const 1 - i32.add - local.set 3 - local.get 1 - i32.load offset=4 - local.get 4 - i32.const 16 - i32.add - local.tee 4 - i32.add - local.get 0 - i32.ne - br_if 0 (;@4;) - end - i32.const 0 - local.set 3 - local.get 5 + loop ;; label = @3 local.get 2 - i32.lt_u + i32.const -1 + i32.add + local.tee 2 + i32.eqz br_if 1 (;@2;) + local.get 1 + i32.load offset=4 + local.get 3 + i32.const 16 + i32.add + local.tee 3 + i32.add + local.get 0 + i32.ne + br_if 0 (;@3;) end - global.get 0 - local.tee 3 - i32.const 144 - i32.add - local.get 3 i32.const 0 - i32.add - i32.store - i32.const -1 - local.set 3 + return end - local.get 3 + global.get 0 + local.tee 2 + i32.const 144 + i32.add + local.get 2 + i32.const 0 + i32.add + i32.store + i32.const -1 return end call 6 @@ -347,157 +336,145 @@ return end block ;; label = @1 - global.get 0 - i32.const 148 - i32.add - i32.load - local.tee 3 - i32.eqz - br_if 0 (;@1;) - local.get 3 - i32.load - local.set 4 - i32.const 0 - local.set 2 - i32.const -16 - local.set 5 block ;; label = @2 - block ;; label = @3 - loop ;; label = @4 - local.get 4 - local.get 2 - local.tee 6 - i32.eq - br_if 1 (;@3;) - local.get 6 - i32.const 1 - i32.add - local.set 2 - local.get 3 - i32.load offset=4 - local.get 5 - i32.const 16 - i32.add - local.tee 5 - i32.add - local.get 0 - i32.ne - br_if 0 (;@4;) - end - local.get 6 - local.get 4 - i32.lt_u - br_if 1 (;@2;) - end global.get 0 - local.tee 2 - i32.const 144 + i32.const 148 i32.add - local.get 2 - i32.const 0 + i32.load + local.tee 3 + i32.eqz + br_if 0 (;@2;) + local.get 3 + i32.load + i32.const 1 i32.add - i32.store - i32.const 0 - return - end - local.get 1 - call 0 - local.set 5 - block ;; label = @2 - block ;; label = @3 - local.get 0 - i32.load offset=8 - local.tee 6 + local.set 2 + i32.const -16 + local.set 4 + loop ;; label = @3 + local.get 2 + i32.const -1 + i32.add + local.tee 2 i32.eqz - br_if 0 (;@3;) - local.get 0 - i32.const 12 + br_if 2 (;@1;) + local.get 3 + i32.load offset=4 + local.get 4 + i32.const 16 i32.add - i32.load - local.set 7 - i32.const 0 - local.set 2 - local.get 6 - local.set 3 - loop ;; label = @4 - local.get 7 - local.get 6 - i32.const 1 - i32.shr_u - local.get 2 - i32.add - local.tee 0 - i32.const 12 - i32.mul - i32.add - local.tee 6 - i32.const 4 - i32.add - i32.load - local.get 1 - local.get 6 - i32.load - local.tee 6 - local.get 5 - local.get 6 - local.get 5 - i32.lt_u - select - call 1 - local.tee 4 - local.get 6 - local.get 5 - i32.sub - local.get 4 - select - local.tee 6 - i32.eqz - br_if 2 (;@2;) + local.tee 4 + i32.add + local.get 0 + i32.ne + br_if 0 (;@3;) + end + local.get 1 + call 0 + local.set 4 + block ;; label = @3 + block ;; label = @4 local.get 0 - local.get 3 - local.get 6 - i32.const 0 - i32.gt_s - select + i32.load offset=8 local.tee 3 + i32.eqz + br_if 0 (;@4;) local.get 0 - i32.const 1 - i32.add - local.get 2 - local.get 6 + i32.load offset=12 + local.set 5 i32.const 0 - i32.lt_s - select - local.tee 2 - i32.sub - local.set 6 + local.set 2 local.get 3 - local.get 2 - i32.gt_u - br_if 0 (;@4;) + local.set 6 + loop ;; label = @5 + local.get 5 + local.get 3 + i32.const 1 + i32.shr_u + local.get 2 + i32.add + local.tee 3 + i32.const 12 + i32.mul + i32.add + local.tee 0 + i32.const 4 + i32.add + i32.load + local.get 1 + local.get 0 + i32.load + local.tee 0 + local.get 4 + local.get 0 + local.get 4 + i32.lt_u + select + call 1 + local.tee 7 + local.get 0 + local.get 4 + i32.sub + local.get 7 + select + local.tee 0 + i32.eqz + br_if 2 (;@3;) + local.get 3 + local.get 6 + local.get 0 + i32.const 0 + i32.gt_s + select + local.tee 6 + local.get 3 + i32.const 1 + i32.add + local.get 2 + local.get 0 + i32.const 0 + i32.lt_s + select + local.tee 2 + i32.sub + local.set 3 + local.get 6 + local.get 2 + i32.gt_u + br_if 0 (;@5;) + end end + global.get 0 + local.tee 2 + i32.const 144 + i32.add + local.get 2 + i32.const 72 + i32.add + i32.store + i32.const 0 + return end - global.get 0 - local.tee 2 - i32.const 144 - i32.add - local.get 2 - i32.const 72 + local.get 5 + local.get 3 + i32.const 12 + i32.mul i32.add - i32.store - i32.const 0 + i32.load offset=8 return end - local.get 7 - local.get 0 - i32.const 12 - i32.mul - i32.add - i32.load offset=8 - return + call 6 + unreachable end - call 6 - unreachable + global.get 0 + local.tee 2 + i32.const 144 + i32.add + local.get 2 + i32.const 0 + i32.add + i32.store + i32.const 0 ) (func (;10;) (type 5) (param i32) global.get 0 diff --git a/playground/package.json b/playground/package.json index 165eede112..d31ef93cba 100644 --- a/playground/package.json +++ b/playground/package.json @@ -2,11 +2,11 @@ "type": "module", "scripts": { "build": "npm run build-component && npm run transpile && npm run typecheck && npm run bundle && npm run copy-files", - "build-component": "cd component && cargo component build --release --target wasm32-wasi", + "build-component": "cd component && cargo component build --release --target wasm32-wasip1", "bundle": "for source in worker.ts parse.ts print.ts; do esbuild --bundle --format=esm src/$source --outdir=dist; done", "copy-files": "cp pages/* component-built/*.wasm dist", "clean": "rm -rf component/src/bindings.rs component-built dist && echo '## you will need to run `cargo clean` seperately to delete cargo artifacts'", - "transpile": "jco transpile --no-nodejs-compat ../target/wasm32-wasi/release/component.wasm --out-dir component-built", + "transpile": "jco transpile --no-nodejs-compat ../target/wasm32-wasip1/release/component.wasm --out-dir component-built", "typecheck": "tsc" }, "dependencies": { diff --git a/tests/cli/dump-llvm-object.wat b/tests/cli/dump-llvm-object.wat index be5463b1a7..fac9bb070e 100644 --- a/tests/cli/dump-llvm-object.wat +++ b/tests/cli/dump-llvm-object.wat @@ -6,7 +6,7 @@ ;; ;; compiled with Rust 1.78.0 with: ;; -;; rustc foo.rs --emit obj --target wasm32-wasi +;; rustc foo.rs --emit obj --target wasm32-wasip1 ;; ;; and then the output of `wasm-tools print` at the time over `foo.o` was ;; pasted below. This works around how the linking and reloc custom sections do From 659f4a6b291396ed94d275dae7e5fe7f008787f5 Mon Sep 17 00:00:00 2001 From: Juniper Tyree <50025784+juntyr@users.noreply.github.com> Date: Tue, 25 Jun 2024 21:14:25 +0300 Subject: [PATCH 28/58] Implement a wasmparser -> wasm-encoder trait (#1628) * Implement a reencoder helper trait * Use new error type in all conversions * Centralise from impls * Remove mut ref returns * rename trait * reencode the data count section * refactor core module parsing helper * Add some more docs * reorder trait members * Review comments plus a reencoder fuzzer * Use `Reencode` in `wit-component` * Add user error variant * Fix Reencode impl * Add an intersperse section hook to allow more complex user module building * Add docs to section intersperse hook --------- Co-authored-by: Alex Crichton --- crates/wasm-encoder/src/component/types.rs | 21 - crates/wasm-encoder/src/core/code.rs | 243 --- crates/wasm-encoder/src/core/custom.rs | 10 - crates/wasm-encoder/src/core/data.rs | 32 - crates/wasm-encoder/src/core/elements.rs | 48 - crates/wasm-encoder/src/core/exports.rs | 33 - crates/wasm-encoder/src/core/functions.rs | 13 - crates/wasm-encoder/src/core/globals.rs | 36 - crates/wasm-encoder/src/core/imports.rs | 39 - crates/wasm-encoder/src/core/memories.rs | 27 - crates/wasm-encoder/src/core/tables.rs | 44 - crates/wasm-encoder/src/core/tags.rs | 33 - crates/wasm-encoder/src/core/types.rs | 185 +- crates/wasm-encoder/src/lib.rs | 2 + crates/wasm-encoder/src/reencode.rs | 1804 ++++++++++++++++++ crates/wasmparser/src/parser.rs | 2 +- crates/wasmparser/src/readers/core/tables.rs | 4 +- crates/wit-component/src/gc.rs | 237 +-- fuzz/fuzz_targets/run.rs | 1 + fuzz/src/lib.rs | 1 + fuzz/src/reencode.rs | 16 + 21 files changed, 1877 insertions(+), 954 deletions(-) create mode 100644 crates/wasm-encoder/src/reencode.rs create mode 100644 fuzz/src/reencode.rs diff --git a/crates/wasm-encoder/src/component/types.rs b/crates/wasm-encoder/src/component/types.rs index 72e0034b6b..08e37da245 100644 --- a/crates/wasm-encoder/src/component/types.rs +++ b/crates/wasm-encoder/src/component/types.rs @@ -527,27 +527,6 @@ impl Encode for PrimitiveValType { } } -#[cfg(feature = "wasmparser")] -impl From for PrimitiveValType { - fn from(ty: wasmparser::PrimitiveValType) -> Self { - match ty { - wasmparser::PrimitiveValType::Bool => PrimitiveValType::Bool, - wasmparser::PrimitiveValType::S8 => PrimitiveValType::S8, - wasmparser::PrimitiveValType::U8 => PrimitiveValType::U8, - wasmparser::PrimitiveValType::S16 => PrimitiveValType::S16, - wasmparser::PrimitiveValType::U16 => PrimitiveValType::U16, - wasmparser::PrimitiveValType::S32 => PrimitiveValType::S32, - wasmparser::PrimitiveValType::U32 => PrimitiveValType::U32, - wasmparser::PrimitiveValType::S64 => PrimitiveValType::S64, - wasmparser::PrimitiveValType::U64 => PrimitiveValType::U64, - wasmparser::PrimitiveValType::F32 => PrimitiveValType::F32, - wasmparser::PrimitiveValType::F64 => PrimitiveValType::F64, - wasmparser::PrimitiveValType::Char => PrimitiveValType::Char, - wasmparser::PrimitiveValType::String => PrimitiveValType::String, - } - } -} - /// Represents a component value type. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum ComponentValType { diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs index 7bee359788..12a4eae25a 100644 --- a/crates/wasm-encoder/src/core/code.rs +++ b/crates/wasm-encoder/src/core/code.rs @@ -103,30 +103,6 @@ impl CodeSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the code to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::CodeSectionReader<'_>, - ) -> wasmparser::Result<&mut Self> { - for code in section { - self.parse(code?)?; - } - Ok(self) - } - - /// Parses a single [`wasmparser::Code`] and adds it to this section. - #[cfg(feature = "wasmparser")] - pub fn parse(&mut self, func: wasmparser::FunctionBody<'_>) -> wasmparser::Result<&mut Self> { - let mut f = Function::new_parsed_locals(&func)?; - let mut reader = func.get_operators_reader()?; - while !reader.eof() { - f.parse(&mut reader)?; - } - Ok(self.function(&f)) - } } impl Encode for CodeSection { @@ -238,18 +214,6 @@ impl Function { Function::new(locals_collected) } - /// Create a new [`Function`] by parsing the locals declarations from the - /// provided [`wasmparser::FunctionBody`]. - #[cfg(feature = "wasmparser")] - pub fn new_parsed_locals(func: &wasmparser::FunctionBody<'_>) -> wasmparser::Result { - let mut locals = Vec::new(); - for pair in func.get_locals_reader()? { - let (cnt, ty) = pair?; - locals.push((cnt, ty.try_into().unwrap())); - } - Ok(Function::new(locals)) - } - /// Write an instruction into this function body. pub fn instruction(&mut self, instruction: &Instruction) -> &mut Self { instruction.encode(&mut self.bytes); @@ -312,15 +276,6 @@ impl Function { pub fn into_raw_body(self) -> Vec { self.bytes } - - /// Parses a single instruction from `reader` and adds it to `self`. - #[cfg(feature = "wasmparser")] - pub fn parse( - &mut self, - reader: &mut wasmparser::OperatorsReader<'_>, - ) -> wasmparser::Result<&mut Self> { - Ok(self.instruction(&reader.read()?.try_into().unwrap())) - } } impl Encode for Function { @@ -360,17 +315,6 @@ impl Encode for MemArg { } } -#[cfg(feature = "wasmparser")] -impl From for MemArg { - fn from(arg: wasmparser::MemArg) -> MemArg { - MemArg { - offset: arg.offset, - align: arg.align.into(), - memory_index: arg.memory, - } - } -} - /// The memory ordering for atomic instructions. /// /// For an in-depth explanation of memory orderings, see the C++ documentation @@ -399,16 +343,6 @@ impl Encode for Ordering { } } -#[cfg(feature = "wasmparser")] -impl From for Ordering { - fn from(arg: wasmparser::Ordering) -> Ordering { - match arg { - wasmparser::Ordering::SeqCst => Ordering::SeqCst, - wasmparser::Ordering::AcqRel => Ordering::AcqRel, - } - } -} - /// Describe an unchecked SIMD lane index. pub type Lane = u8; @@ -433,18 +367,6 @@ impl Encode for BlockType { } } -#[cfg(feature = "wasmparser")] -impl TryFrom for BlockType { - type Error = (); - fn try_from(arg: wasmparser::BlockType) -> Result { - match arg { - wasmparser::BlockType::Empty => Ok(BlockType::Empty), - wasmparser::BlockType::FuncType(n) => Ok(BlockType::FunctionType(n)), - wasmparser::BlockType::Type(t) => Ok(BlockType::Result(t.try_into()?)), - } - } -} - /// WebAssembly instructions. #[derive(Clone, Debug)] #[non_exhaustive] @@ -3401,64 +3323,6 @@ impl Encode for Instruction<'_> { } } -#[cfg(feature = "wasmparser")] -impl TryFrom> for Instruction<'_> { - type Error = (); - - fn try_from(arg: wasmparser::Operator<'_>) -> Result { - use Instruction::*; - - macro_rules! define_match { - ($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { - match arg { - $( - wasmparser::Operator::$op $({ $($arg),* })? => { - $( - $(let $arg = define_match!(map $arg $arg);)* - )? - Ok(define_match!(mk $op $($($arg)*)?)) - } - )* - } - }; - - // No-payload instructions are named the same in wasmparser as they are in - // wasm-encoder - (mk $op:ident) => ($op); - - // Instructions which need "special care" to map from wasmparser to - // wasm-encoder - (mk BrTable $arg:ident) => ({BrTable($arg.0, $arg.1)}); - (mk TryTable $arg:ident) => ({TryTable($arg.0, $arg.1)}); - - // Catch-all for the translation of one payload argument which is typically - // represented as a tuple-enum in wasm-encoder. - (mk $op:ident $arg:ident) => ($op($arg)); - - // Catch-all of everything else where the wasmparser fields are simply - // translated to wasm-encoder fields. - (mk $op:ident $($arg:ident)*) => ($op { $($arg),* }); - - // Special-case BrTable/TryTable conversion of arguments. - (map $arg:ident targets) => (( - $arg.targets().map(|i| i.unwrap()).collect::>().into(), - $arg.default(), - )); - (map $arg:ident try_table) => (( - $arg.ty.try_into().unwrap(), - $arg.catches.into_iter().map(|i| i.into()).collect::>().into(), - )); - - // Everything else is converted with `TryFrom`/`From`. Note that the - // fallibility here has to do with how indexes are represented in - // `wasmparser` which we know when reading directly we'll never hit the - // erroneous cases here, hence the unwrap. - (map $arg:ident $other:ident) => {$other.try_into().unwrap()}; - } - wasmparser::for_each_operator!(define_match) - } -} - #[derive(Clone, Debug)] #[allow(missing_docs)] pub enum Catch { @@ -3493,18 +3357,6 @@ impl Encode for Catch { } } -#[cfg(feature = "wasmparser")] -impl From for Catch { - fn from(arg: wasmparser::Catch) -> Catch { - match arg { - wasmparser::Catch::One { tag, label } => Catch::One { tag, label }, - wasmparser::Catch::OneRef { tag, label } => Catch::OneRef { tag, label }, - wasmparser::Catch::All { label } => Catch::All { label }, - wasmparser::Catch::AllRef { label } => Catch::AllRef { label }, - } - } -} - /// A constant expression. /// /// Usable in contexts such as offsets or initializers. @@ -3669,101 +3521,6 @@ impl Encode for ConstExpr { } } -/// An error when converting a `wasmparser::ConstExpr` into a -/// `wasm_encoder::ConstExpr`. -#[cfg(feature = "wasmparser")] -#[derive(Debug)] -pub enum ConstExprConversionError { - /// There was an error when parsing the const expression. - ParseError(wasmparser::BinaryReaderError), - - /// The const expression is invalid: not actually constant or something like - /// that. - Invalid, - - /// There was a type reference that was canonicalized and no longer - /// references an index into a module's types space, so we cannot encode it - /// into a Wasm binary again. - CanonicalizedTypeReference, -} - -#[cfg(feature = "wasmparser")] -impl From for ConstExprConversionError { - fn from(err: wasmparser::BinaryReaderError) -> ConstExprConversionError { - ConstExprConversionError::ParseError(err) - } -} - -#[cfg(feature = "wasmparser")] -impl std::fmt::Display for ConstExprConversionError { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - Self::ParseError(_e) => { - write!(f, "There was an error when parsing the const expression") - } - Self::Invalid => write!(f, "The const expression was invalid"), - Self::CanonicalizedTypeReference => write!( - f, - "There was a canonicalized type reference without type index information" - ), - } - } -} - -#[cfg(feature = "wasmparser")] -impl std::error::Error for ConstExprConversionError { - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { - match self { - Self::ParseError(e) => Some(e), - Self::Invalid | Self::CanonicalizedTypeReference => None, - } - } -} - -#[cfg(feature = "wasmparser")] -impl<'a> TryFrom> for ConstExpr { - type Error = ConstExprConversionError; - - fn try_from(const_expr: wasmparser::ConstExpr) -> Result { - let mut ops = const_expr.get_operators_reader().into_iter(); - - let result = match ops.next() { - Some(Ok(wasmparser::Operator::I32Const { value })) => ConstExpr::i32_const(value), - Some(Ok(wasmparser::Operator::I64Const { value })) => ConstExpr::i64_const(value), - Some(Ok(wasmparser::Operator::F32Const { value })) => { - ConstExpr::f32_const(f32::from_bits(value.bits())) - } - Some(Ok(wasmparser::Operator::F64Const { value })) => { - ConstExpr::f64_const(f64::from_bits(value.bits())) - } - Some(Ok(wasmparser::Operator::V128Const { value })) => { - ConstExpr::v128_const(i128::from_le_bytes(*value.bytes())) - } - Some(Ok(wasmparser::Operator::RefNull { hty })) => ConstExpr::ref_null( - HeapType::try_from(hty) - .map_err(|_| ConstExprConversionError::CanonicalizedTypeReference)?, - ), - Some(Ok(wasmparser::Operator::RefFunc { function_index })) => { - ConstExpr::ref_func(function_index) - } - Some(Ok(wasmparser::Operator::GlobalGet { global_index })) => { - ConstExpr::global_get(global_index) - } - - // TODO: support the extended-const proposal. - Some(Ok(_op)) => return Err(ConstExprConversionError::Invalid), - - Some(Err(e)) => return Err(ConstExprConversionError::ParseError(e)), - None => return Err(ConstExprConversionError::Invalid), - }; - - match (ops.next(), ops.next()) { - (Some(Ok(wasmparser::Operator::End)), None) => Ok(result), - _ => Err(ConstExprConversionError::Invalid), - } - } -} - #[cfg(test)] mod tests { #[test] diff --git a/crates/wasm-encoder/src/core/custom.rs b/crates/wasm-encoder/src/core/custom.rs index 75cae490c5..870ee62617 100644 --- a/crates/wasm-encoder/src/core/custom.rs +++ b/crates/wasm-encoder/src/core/custom.rs @@ -44,16 +44,6 @@ impl Section for RawCustomSection<'_> { } } -#[cfg(feature = "wasmparser")] -impl<'a> From> for CustomSection<'a> { - fn from(section: wasmparser::CustomSectionReader<'a>) -> Self { - CustomSection { - data: section.data().into(), - name: section.name().into(), - } - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/crates/wasm-encoder/src/core/data.rs b/crates/wasm-encoder/src/core/data.rs index 7f8dd795d1..6f408befe5 100644 --- a/crates/wasm-encoder/src/core/data.rs +++ b/crates/wasm-encoder/src/core/data.rs @@ -151,38 +151,6 @@ impl DataSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the data to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::DataSectionReader<'_>, - ) -> Result<&mut Self, crate::ConstExprConversionError> { - for data in section { - self.parse(data?)?; - } - Ok(self) - } - - /// Parses a single [`wasmparser::Data`] and adds it to this section. - #[cfg(feature = "wasmparser")] - pub fn parse( - &mut self, - data: wasmparser::Data<'_>, - ) -> Result<&mut Self, crate::ConstExprConversionError> { - match data.kind { - wasmparser::DataKind::Active { - memory_index, - offset_expr, - } => Ok(self.active( - memory_index, - &ConstExpr::try_from(offset_expr)?, - data.data.iter().copied(), - )), - wasmparser::DataKind::Passive => Ok(self.passive(data.data.iter().copied())), - } - } } impl Encode for DataSection { diff --git a/crates/wasm-encoder/src/core/elements.rs b/crates/wasm-encoder/src/core/elements.rs index 8d24c4e363..28a1031ed8 100644 --- a/crates/wasm-encoder/src/core/elements.rs +++ b/crates/wasm-encoder/src/core/elements.rs @@ -206,54 +206,6 @@ impl ElementSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the elements to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::ElementSectionReader<'_>, - ) -> Result<&mut Self, crate::ConstExprConversionError> { - for element in section { - self.parse(element?)?; - } - Ok(self) - } - - /// Parses the single [`wasmparser::Element`] provided and adds it to this - /// section. - #[cfg(feature = "wasmparser")] - pub fn parse( - &mut self, - element: wasmparser::Element<'_>, - ) -> Result<&mut Self, crate::ConstExprConversionError> { - let mut funcs; - let mut exprs; - let elements = match element.items { - wasmparser::ElementItems::Functions(f) => { - funcs = Vec::new(); - for func in f { - funcs.push(func?); - } - Elements::Functions(&funcs) - } - wasmparser::ElementItems::Expressions(ty, e) => { - exprs = Vec::new(); - for expr in e { - exprs.push(ConstExpr::try_from(expr?)?); - } - Elements::Expressions(ty.try_into().unwrap(), &exprs) - } - }; - match element.kind { - wasmparser::ElementKind::Active { - table_index, - offset_expr, - } => Ok(self.active(table_index, &ConstExpr::try_from(offset_expr)?, elements)), - wasmparser::ElementKind::Passive => Ok(self.passive(elements)), - wasmparser::ElementKind::Declared => Ok(self.declared(elements)), - } - } } impl Encode for ElementSection { diff --git a/crates/wasm-encoder/src/core/exports.rs b/crates/wasm-encoder/src/core/exports.rs index 7b06fabc06..36c7896362 100644 --- a/crates/wasm-encoder/src/core/exports.rs +++ b/crates/wasm-encoder/src/core/exports.rs @@ -25,19 +25,6 @@ impl Encode for ExportKind { } } -#[cfg(feature = "wasmparser")] -impl From for ExportKind { - fn from(external_kind: wasmparser::ExternalKind) -> Self { - match external_kind { - wasmparser::ExternalKind::Func => ExportKind::Func, - wasmparser::ExternalKind::Table => ExportKind::Table, - wasmparser::ExternalKind::Memory => ExportKind::Memory, - wasmparser::ExternalKind::Global => ExportKind::Global, - wasmparser::ExternalKind::Tag => ExportKind::Tag, - } - } -} - /// An encoder for the export section of WebAssembly module. /// /// # Example @@ -83,26 +70,6 @@ impl ExportSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the exports to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::ExportSectionReader<'_>, - ) -> wasmparser::Result<&mut Self> { - for export in section { - self.parse(export?); - } - Ok(self) - } - - /// Parses the single [`wasmparser::Export`] provided and adds it to this - /// section. - #[cfg(feature = "wasmparser")] - pub fn parse(&mut self, export: wasmparser::Export<'_>) -> &mut Self { - self.export(export.name, export.kind.into(), export.index) - } } impl Encode for ExportSection { diff --git a/crates/wasm-encoder/src/core/functions.rs b/crates/wasm-encoder/src/core/functions.rs index 3cc053ba5b..e21d8c10a7 100644 --- a/crates/wasm-encoder/src/core/functions.rs +++ b/crates/wasm-encoder/src/core/functions.rs @@ -48,19 +48,6 @@ impl FunctionSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the functions to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::FunctionSectionReader<'_>, - ) -> wasmparser::Result<&mut Self> { - for func in section { - self.function(func?); - } - Ok(self) - } } impl Encode for FunctionSection { diff --git a/crates/wasm-encoder/src/core/globals.rs b/crates/wasm-encoder/src/core/globals.rs index f208f63f1f..dcd96ac33f 100644 --- a/crates/wasm-encoder/src/core/globals.rs +++ b/crates/wasm-encoder/src/core/globals.rs @@ -60,30 +60,6 @@ impl GlobalSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the globals to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::GlobalSectionReader<'_>, - ) -> Result<&mut Self, crate::ConstExprConversionError> { - for global in section { - self.parse(global?)?; - } - Ok(self) - } - - /// Parses the single [`wasmparser::Global`] provided and adds it to this - /// section. - #[cfg(feature = "wasmparser")] - pub fn parse( - &mut self, - global: wasmparser::Global<'_>, - ) -> Result<&mut Self, crate::ConstExprConversionError> { - self.global(global.ty.try_into().unwrap(), &global.init_expr.try_into()?); - Ok(self) - } } impl Encode for GlobalSection { @@ -122,15 +98,3 @@ impl Encode for GlobalType { sink.push(flag); } } - -#[cfg(feature = "wasmparser")] -impl TryFrom for GlobalType { - type Error = (); - fn try_from(global_ty: wasmparser::GlobalType) -> Result { - Ok(GlobalType { - val_type: global_ty.content_type.try_into()?, - mutable: global_ty.mutable, - shared: global_ty.shared, - }) - } -} diff --git a/crates/wasm-encoder/src/core/imports.rs b/crates/wasm-encoder/src/core/imports.rs index 5eac839cde..b3a0879da1 100644 --- a/crates/wasm-encoder/src/core/imports.rs +++ b/crates/wasm-encoder/src/core/imports.rs @@ -73,20 +73,6 @@ impl From for EntityType { } } -#[cfg(feature = "wasmparser")] -impl TryFrom for EntityType { - type Error = (); - fn try_from(type_ref: wasmparser::TypeRef) -> Result { - Ok(match type_ref { - wasmparser::TypeRef::Func(i) => EntityType::Function(i), - wasmparser::TypeRef::Table(t) => EntityType::Table(t.try_into()?), - wasmparser::TypeRef::Memory(m) => EntityType::Memory(m.into()), - wasmparser::TypeRef::Global(g) => EntityType::Global(g.try_into()?), - wasmparser::TypeRef::Tag(t) => EntityType::Tag(t.into()), - }) - } -} - /// An encoder for the import section of WebAssembly modules. /// /// # Example @@ -142,31 +128,6 @@ impl ImportSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the imports to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::ImportSectionReader<'_>, - ) -> wasmparser::Result<&mut Self> { - for import in section { - self.parse(import?); - } - Ok(self) - } - - /// Parses the single [`wasmparser::Import`] provided and adds it to this - /// section. - #[cfg(feature = "wasmparser")] - pub fn parse(&mut self, import: wasmparser::Import<'_>) -> &mut Self { - self.import( - import.module, - import.name, - EntityType::try_from(import.ty).unwrap(), - ); - self - } } impl Encode for ImportSection { diff --git a/crates/wasm-encoder/src/core/memories.rs b/crates/wasm-encoder/src/core/memories.rs index b3942dfa09..d0c9087fff 100644 --- a/crates/wasm-encoder/src/core/memories.rs +++ b/crates/wasm-encoder/src/core/memories.rs @@ -51,20 +51,6 @@ impl MemorySection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the memories to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::MemorySectionReader<'_>, - ) -> wasmparser::Result<&mut Self> { - for memory in section { - let memory = memory?; - self.memory(memory.into()); - } - Ok(self) - } } impl Encode for MemorySection { @@ -127,16 +113,3 @@ impl Encode for MemoryType { } } } - -#[cfg(feature = "wasmparser")] -impl From for MemoryType { - fn from(memory_ty: wasmparser::MemoryType) -> Self { - MemoryType { - minimum: memory_ty.initial, - maximum: memory_ty.maximum, - memory64: memory_ty.memory64, - shared: memory_ty.shared, - page_size_log2: memory_ty.page_size_log2, - } - } -} diff --git a/crates/wasm-encoder/src/core/tables.rs b/crates/wasm-encoder/src/core/tables.rs index 24bfe03a0e..6b35915b0d 100644 --- a/crates/wasm-encoder/src/core/tables.rs +++ b/crates/wasm-encoder/src/core/tables.rs @@ -62,37 +62,6 @@ impl TableSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the tables to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::TableSectionReader<'_>, - ) -> Result<&mut Self, crate::ConstExprConversionError> { - for table in section { - self.parse(table?)?; - } - Ok(self) - } - - /// Parses a single [`wasmparser::Table`] and adds it to this section. - #[cfg(feature = "wasmparser")] - pub fn parse( - &mut self, - table: wasmparser::Table<'_>, - ) -> Result<&mut Self, crate::ConstExprConversionError> { - let ty = table.ty.try_into().unwrap(); - match table.init { - wasmparser::TableInit::RefNull => { - self.table(ty); - } - wasmparser::TableInit::Expr(e) => { - self.table_with_init(ty, &e.try_into()?); - } - } - Ok(self) - } } impl Encode for TableSection { @@ -150,16 +119,3 @@ impl Encode for TableType { } } } - -#[cfg(feature = "wasmparser")] -impl TryFrom for TableType { - type Error = (); - fn try_from(table_ty: wasmparser::TableType) -> Result { - Ok(TableType { - element_type: table_ty.element_type.try_into()?, - minimum: table_ty.initial, - maximum: table_ty.maximum, - table64: table_ty.table64, - }) - } -} diff --git a/crates/wasm-encoder/src/core/tags.rs b/crates/wasm-encoder/src/core/tags.rs index 2d6a88d995..413055f2af 100644 --- a/crates/wasm-encoder/src/core/tags.rs +++ b/crates/wasm-encoder/src/core/tags.rs @@ -46,20 +46,6 @@ impl TagSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the tags to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::TagSectionReader<'_>, - ) -> wasmparser::Result<&mut Self> { - for tag in section { - let tag = tag?; - self.tag(tag.into()); - } - Ok(self) - } } impl Encode for TagSection { @@ -82,15 +68,6 @@ pub enum TagKind { Exception = 0x0, } -#[cfg(feature = "wasmparser")] -impl From for TagKind { - fn from(kind: wasmparser::TagKind) -> Self { - match kind { - wasmparser::TagKind::Exception => TagKind::Exception, - } - } -} - /// A tag's type. #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct TagType { @@ -106,13 +83,3 @@ impl Encode for TagType { self.func_type_idx.encode(sink); } } - -#[cfg(feature = "wasmparser")] -impl From for TagType { - fn from(tag_ty: wasmparser::TagType) -> Self { - TagType { - kind: tag_ty.kind.into(), - func_type_idx: tag_ty.func_type_idx, - } - } -} diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs index 0f53326b08..de6705f913 100644 --- a/crates/wasm-encoder/src/core/types.rs +++ b/crates/wasm-encoder/src/core/types.rs @@ -23,22 +23,6 @@ impl Encode for SubType { } } -#[cfg(feature = "wasmparser")] -impl TryFrom for SubType { - type Error = (); - - fn try_from(sub_ty: wasmparser::SubType) -> Result { - Ok(SubType { - is_final: sub_ty.is_final, - supertype_idx: sub_ty - .supertype_idx - .map(|i| i.as_module_index().ok_or(())) - .transpose()?, - composite_type: sub_ty.composite_type.try_into()?, - }) - } -} - /// Represents a composite type in a WebAssembly module. #[derive(Debug, Clone)] pub enum CompositeType { @@ -68,18 +52,6 @@ impl Encode for CompositeType { } } -#[cfg(feature = "wasmparser")] -impl TryFrom for CompositeType { - type Error = (); - fn try_from(composite_ty: wasmparser::CompositeType) -> Result { - Ok(match composite_ty { - wasmparser::CompositeType::Func(f) => CompositeType::Func(f.try_into()?), - wasmparser::CompositeType::Array(a) => CompositeType::Array(a.try_into()?), - wasmparser::CompositeType::Struct(s) => CompositeType::Struct(s.try_into()?), - }) - } -} - /// Represents a type of a function in a WebAssembly module. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FuncType { @@ -89,30 +61,10 @@ pub struct FuncType { len_params: usize, } -#[cfg(feature = "wasmparser")] -impl TryFrom for FuncType { - type Error = (); - fn try_from(func_ty: wasmparser::FuncType) -> Result { - let mut buf = Vec::with_capacity(func_ty.params().len() + func_ty.results().len()); - for ty in func_ty.params().iter().chain(func_ty.results()).copied() { - buf.push(ty.try_into()?); - } - Ok(FuncType::from_parts(buf.into(), func_ty.params().len())) - } -} - /// Represents a type of an array in a WebAssembly module. #[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] pub struct ArrayType(pub FieldType); -#[cfg(feature = "wasmparser")] -impl TryFrom for ArrayType { - type Error = (); - fn try_from(array_ty: wasmparser::ArrayType) -> Result { - Ok(ArrayType(array_ty.0.try_into()?)) - } -} - /// Represents a type of a struct in a WebAssembly module. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct StructType { @@ -120,21 +72,6 @@ pub struct StructType { pub fields: Box<[FieldType]>, } -#[cfg(feature = "wasmparser")] -impl TryFrom for StructType { - type Error = (); - fn try_from(struct_ty: wasmparser::StructType) -> Result { - Ok(StructType { - fields: struct_ty - .fields - .iter() - .cloned() - .map(TryInto::try_into) - .collect::>()?, - }) - } -} - /// Field type in composite types (structs, arrays). #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub struct FieldType { @@ -144,17 +81,6 @@ pub struct FieldType { pub mutable: bool, } -#[cfg(feature = "wasmparser")] -impl TryFrom for FieldType { - type Error = (); - fn try_from(field_ty: wasmparser::FieldType) -> Result { - Ok(FieldType { - element_type: field_ty.element_type.try_into()?, - mutable: field_ty.mutable, - }) - } -} - /// Storage type for composite type fields. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum StorageType { @@ -166,18 +92,6 @@ pub enum StorageType { Val(ValType), } -#[cfg(feature = "wasmparser")] -impl TryFrom for StorageType { - type Error = (); - fn try_from(storage_ty: wasmparser::StorageType) -> Result { - Ok(match storage_ty { - wasmparser::StorageType::I8 => StorageType::I8, - wasmparser::StorageType::I16 => StorageType::I16, - wasmparser::StorageType::Val(v) => StorageType::Val(v.try_into()?), - }) - } -} - impl StorageType { /// Is this storage type defaultable? pub fn is_defaultable(&self) -> bool { @@ -216,21 +130,6 @@ pub enum ValType { Ref(RefType), } -#[cfg(feature = "wasmparser")] -impl TryFrom for ValType { - type Error = (); - fn try_from(val_ty: wasmparser::ValType) -> Result { - Ok(match val_ty { - wasmparser::ValType::I32 => ValType::I32, - wasmparser::ValType::I64 => ValType::I64, - wasmparser::ValType::F32 => ValType::F32, - wasmparser::ValType::F64 => ValType::F64, - wasmparser::ValType::V128 => ValType::V128, - wasmparser::ValType::Ref(r) => ValType::Ref(r.try_into()?), - }) - } -} - impl ValType { /// Is this a numeric value type? pub fn is_numeric(&self) -> bool { @@ -442,18 +341,6 @@ impl Encode for RefType { } } -#[cfg(feature = "wasmparser")] -impl TryFrom for RefType { - type Error = (); - - fn try_from(ref_type: wasmparser::RefType) -> Result { - Ok(RefType { - nullable: ref_type.is_nullable(), - heap_type: ref_type.heap_type().try_into()?, - }) - } -} - impl From for ValType { fn from(ty: RefType) -> ValType { ValType::Ref(ty) @@ -517,21 +404,6 @@ impl Encode for HeapType { } } -#[cfg(feature = "wasmparser")] -impl TryFrom for HeapType { - type Error = (); - - fn try_from(heap_type: wasmparser::HeapType) -> Result { - Ok(match heap_type { - wasmparser::HeapType::Concrete(i) => HeapType::Concrete(i.as_module_index().ok_or(())?), - wasmparser::HeapType::Abstract { shared, ty } => HeapType::Abstract { - shared, - ty: ty.into(), - }, - }) - } -} - /// An abstract heap type. #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)] pub enum AbstractHeapType { @@ -607,27 +479,6 @@ impl Encode for AbstractHeapType { } } -#[cfg(feature = "wasmparser")] -impl From for AbstractHeapType { - fn from(value: wasmparser::AbstractHeapType) -> Self { - use wasmparser::AbstractHeapType::*; - match value { - Func => AbstractHeapType::Func, - Extern => AbstractHeapType::Extern, - Any => AbstractHeapType::Any, - None => AbstractHeapType::None, - NoExtern => AbstractHeapType::NoExtern, - NoFunc => AbstractHeapType::NoFunc, - Eq => AbstractHeapType::Eq, - Struct => AbstractHeapType::Struct, - Array => AbstractHeapType::Array, - I31 => AbstractHeapType::I31, - Exn => AbstractHeapType::Exn, - NoExn => AbstractHeapType::NoExn, - } - } -} - /// An encoder for the type section of WebAssembly modules. /// /// # Example @@ -679,6 +530,17 @@ impl TypeSection { self } + /// Define a function type in this type section. + pub fn func_type(&mut self, ty: &FuncType) -> &mut Self { + Self::encode_function( + &mut self.bytes, + ty.params().iter().cloned(), + ty.results().iter().cloned(), + ); + self.num_added += 1; + self + } + fn encode_function(sink: &mut Vec, params: P, results: R) where P: IntoIterator, @@ -757,31 +619,6 @@ impl TypeSection { self.num_added += 1; self } - - /// Parses the input `section` given from the `wasmparser` crate and adds - /// all the types to this section. - #[cfg(feature = "wasmparser")] - pub fn parse_section( - &mut self, - section: wasmparser::TypeSectionReader<'_>, - ) -> wasmparser::Result<&mut Self> { - for rec_group in section { - self.parse(rec_group?); - } - Ok(self) - } - - /// Parses a single [`wasmparser::RecGroup`] and adds it to this section. - #[cfg(feature = "wasmparser")] - pub fn parse(&mut self, rec_group: wasmparser::RecGroup) -> &mut Self { - if rec_group.is_explicit_rec_group() { - self.rec(rec_group.into_types().map(|t| t.try_into().unwrap())); - } else { - let ty = rec_group.into_types().next().unwrap(); - self.subtype(&SubType::try_from(ty).unwrap()); - } - self - } } impl Encode for TypeSection { diff --git a/crates/wasm-encoder/src/lib.rs b/crates/wasm-encoder/src/lib.rs index ae30a643dd..756bf517d0 100644 --- a/crates/wasm-encoder/src/lib.rs +++ b/crates/wasm-encoder/src/lib.rs @@ -74,6 +74,8 @@ mod component; mod core; mod raw; +#[cfg(feature = "wasmparser")] +pub mod reencode; pub use self::component::*; pub use self::core::*; diff --git a/crates/wasm-encoder/src/reencode.rs b/crates/wasm-encoder/src/reencode.rs new file mode 100644 index 0000000000..8624b81911 --- /dev/null +++ b/crates/wasm-encoder/src/reencode.rs @@ -0,0 +1,1804 @@ +//! Conversions from `wasmparser` to `wasm-encoder` to [`Reencode`] parsed wasm. +//! +//! The [`RoundtripReencoder`] allows encoding identical wasm to the parsed +//! input. + +use std::convert::Infallible; + +#[allow(missing_docs)] // FIXME +pub trait Reencode { + type Error; + + fn data_index(&mut self, data: u32) -> u32 { + utils::data_index(self, data) + } + + fn element_index(&mut self, element: u32) -> u32 { + utils::element_index(self, element) + } + + fn function_index(&mut self, func: u32) -> u32 { + utils::function_index(self, func) + } + + fn global_index(&mut self, global: u32) -> u32 { + utils::global_index(self, global) + } + + fn memory_index(&mut self, memory: u32) -> u32 { + utils::memory_index(self, memory) + } + + fn table_index(&mut self, table: u32) -> u32 { + utils::table_index(self, table) + } + + fn tag_index(&mut self, tag: u32) -> u32 { + utils::tag_index(self, tag) + } + + fn type_index(&mut self, ty: u32) -> u32 { + utils::type_index(self, ty) + } + + fn abstract_heap_type( + &mut self, + value: wasmparser::AbstractHeapType, + ) -> crate::AbstractHeapType { + utils::abstract_heap_type(self, value) + } + + fn array_type( + &mut self, + array_ty: wasmparser::ArrayType, + ) -> Result> { + utils::array_type(self, array_ty) + } + + fn block_type( + &mut self, + arg: wasmparser::BlockType, + ) -> Result> { + utils::block_type(self, arg) + } + + fn component_primitive_val_type( + &mut self, + ty: wasmparser::PrimitiveValType, + ) -> crate::component::PrimitiveValType { + utils::component_primitive_val_type(self, ty) + } + + fn const_expr( + &mut self, + const_expr: wasmparser::ConstExpr, + ) -> Result> { + utils::const_expr(self, const_expr) + } + + fn catch(&mut self, arg: wasmparser::Catch) -> crate::Catch { + utils::catch(self, arg) + } + + fn composite_type( + &mut self, + composite_ty: wasmparser::CompositeType, + ) -> Result> { + utils::composite_type(self, composite_ty) + } + + fn entity_type( + &mut self, + type_ref: wasmparser::TypeRef, + ) -> Result> { + utils::entity_type(self, type_ref) + } + + fn export_kind(&mut self, external_kind: wasmparser::ExternalKind) -> crate::ExportKind { + utils::export_kind(self, external_kind) + } + + fn field_type( + &mut self, + field_ty: wasmparser::FieldType, + ) -> Result> { + utils::field_type(self, field_ty) + } + + fn func_type( + &mut self, + func_ty: wasmparser::FuncType, + ) -> Result> { + utils::func_type(self, func_ty) + } + + fn global_type( + &mut self, + global_ty: wasmparser::GlobalType, + ) -> Result> { + utils::global_type(self, global_ty) + } + + fn heap_type( + &mut self, + heap_type: wasmparser::HeapType, + ) -> Result> { + utils::heap_type(self, heap_type) + } + + fn instruction<'a>( + &mut self, + arg: wasmparser::Operator<'a>, + ) -> Result, Error> { + utils::instruction(self, arg) + } + + fn memory_type(&mut self, memory_ty: wasmparser::MemoryType) -> crate::MemoryType { + utils::memory_type(self, memory_ty) + } + + fn mem_arg(&mut self, arg: wasmparser::MemArg) -> crate::MemArg { + utils::mem_arg(self, arg) + } + + fn ordering(&mut self, arg: wasmparser::Ordering) -> crate::Ordering { + utils::ordering(self, arg) + } + + fn ref_type( + &mut self, + ref_type: wasmparser::RefType, + ) -> Result> { + utils::ref_type(self, ref_type) + } + + fn storage_type( + &mut self, + storage_ty: wasmparser::StorageType, + ) -> Result> { + utils::storage_type(self, storage_ty) + } + + fn struct_type( + &mut self, + struct_ty: wasmparser::StructType, + ) -> Result> { + utils::struct_type(self, struct_ty) + } + + fn sub_type( + &mut self, + sub_ty: wasmparser::SubType, + ) -> Result> { + utils::sub_type(self, sub_ty) + } + + fn table_type( + &mut self, + table_ty: wasmparser::TableType, + ) -> Result> { + utils::table_type(self, table_ty) + } + + fn tag_kind(&mut self, kind: wasmparser::TagKind) -> crate::TagKind { + utils::tag_kind(self, kind) + } + + fn tag_type(&mut self, tag_ty: wasmparser::TagType) -> crate::TagType { + utils::tag_type(self, tag_ty) + } + + fn val_type( + &mut self, + val_ty: wasmparser::ValType, + ) -> Result> { + utils::val_type(self, val_ty) + } + + /// Parses the input `section` given from the `wasmparser` crate and + /// adds the custom section to the `module`. + fn parse_custom_section( + &mut self, + module: &mut crate::Module, + section: wasmparser::CustomSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_custom_section(self, module, section) + } + + /// Converts the input `section` given from the `wasmparser` crate into an + /// encoded custom section. + fn custom_section<'a>( + &mut self, + section: wasmparser::CustomSectionReader<'a>, + ) -> crate::CustomSection<'a> { + utils::custom_section(self, section) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the code to the `code` section. + fn parse_code_section( + &mut self, + code: &mut crate::CodeSection, + section: wasmparser::CodeSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_code_section(self, code, section) + } + + /// Parses a single [`wasmparser::FunctionBody`] and adds it to the `code` section. + fn parse_function_body( + &mut self, + code: &mut crate::CodeSection, + func: wasmparser::FunctionBody<'_>, + ) -> Result<(), Error> { + utils::parse_function_body(self, code, func) + } + + /// Create a new [`crate::Function`] by parsing the locals declarations from the + /// provided [`wasmparser::FunctionBody`]. + fn new_function_with_parsed_locals( + &mut self, + func: &wasmparser::FunctionBody<'_>, + ) -> Result> { + utils::new_function_with_parsed_locals(self, func) + } + + /// Parses a single instruction from `reader` and adds it to `function`. + fn parse_instruction( + &mut self, + function: &mut crate::Function, + reader: &mut wasmparser::OperatorsReader<'_>, + ) -> Result<(), Error> { + utils::parse_instruction(self, function, reader) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the data to the `data` section. + fn parse_data_section( + &mut self, + data: &mut crate::DataSection, + section: wasmparser::DataSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_data_section(self, data, section) + } + + /// Parses a single [`wasmparser::Data`] and adds it to the `data` section. + fn parse_data( + &mut self, + data: &mut crate::DataSection, + datum: wasmparser::Data<'_>, + ) -> Result<(), Error> { + utils::parse_data(self, data, datum) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the elements to the `element` section. + fn parse_element_section( + &mut self, + elements: &mut crate::ElementSection, + section: wasmparser::ElementSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_element_section(self, elements, section) + } + + /// Parses the single [`wasmparser::Element`] provided and adds it to the + /// `element` section. + fn parse_element( + &mut self, + elements: &mut crate::ElementSection, + element: wasmparser::Element<'_>, + ) -> Result<(), Error> { + utils::parse_element(self, elements, element) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the exports to the `exports` section. + fn parse_export_section( + &mut self, + exports: &mut crate::ExportSection, + section: wasmparser::ExportSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_export_section(self, exports, section) + } + + /// Parses the single [`wasmparser::Export`] provided and adds it to the + /// `exports` section. + fn parse_export(&mut self, exports: &mut crate::ExportSection, export: wasmparser::Export<'_>) { + utils::parse_export(self, exports, export) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the functions to the `functions` section. + fn parse_function_section( + &mut self, + functions: &mut crate::FunctionSection, + section: wasmparser::FunctionSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_function_section(self, functions, section) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the globals to the `globals` section. + fn parse_global_section( + &mut self, + globals: &mut crate::GlobalSection, + section: wasmparser::GlobalSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_global_section(self, globals, section) + } + + /// Parses the single [`wasmparser::Global`] provided and adds it to the + /// `globals` section. + fn parse_global( + &mut self, + globals: &mut crate::GlobalSection, + global: wasmparser::Global<'_>, + ) -> Result<(), Error> { + utils::parse_global(self, globals, global) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the imports to the `import` section. + fn parse_import_section( + &mut self, + imports: &mut crate::ImportSection, + section: wasmparser::ImportSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_import_section(self, imports, section) + } + + /// Parses the single [`wasmparser::Import`] provided and adds it to the + /// `import` section. + fn parse_import( + &mut self, + imports: &mut crate::ImportSection, + import: wasmparser::Import<'_>, + ) -> Result<(), Error> { + utils::parse_import(self, imports, import) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the memories to the `memories` section. + fn parse_memory_section( + &mut self, + memories: &mut crate::MemorySection, + section: wasmparser::MemorySectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_memory_section(self, memories, section) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the tables to the `tables` section. + fn parse_table_section( + &mut self, + tables: &mut crate::TableSection, + section: wasmparser::TableSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_table_section(self, tables, section) + } + + /// Parses a single [`wasmparser::Table`] and adds it to the `tables` section. + fn parse_table( + &mut self, + tables: &mut crate::TableSection, + table: wasmparser::Table<'_>, + ) -> Result<(), Error> { + utils::parse_table(self, tables, table) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the tags to the `tags` section. + fn parse_tag_section( + &mut self, + tags: &mut crate::TagSection, + section: wasmparser::TagSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_tag_section(self, tags, section) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the types to the `types` section. + fn parse_type_section( + &mut self, + types: &mut crate::TypeSection, + section: wasmparser::TypeSectionReader<'_>, + ) -> Result<(), Error> { + utils::parse_type_section(self, types, section) + } + + /// Parses a single [`wasmparser::RecGroup`] and adds it to the `types` section. + fn parse_recursive_type_group( + &mut self, + types: &mut crate::TypeSection, + rec_group: wasmparser::RecGroup, + ) -> Result<(), Error> { + utils::parse_recursive_type_group(self, types, rec_group) + } + + fn parse_unknown_section( + &mut self, + module: &mut crate::Module, + id: u8, + contents: &[u8], + ) -> Result<(), Error> { + utils::parse_unknown_section(self, module, id, contents) + } + + /// A hook method that is called inside [`Reencode::parse_core_module`] + /// before and after every non-custom core wasm section. + /// + /// This method can be used to insert new custom sections in between those + /// sections, or to detect when a non-custom section is missing and insert + /// it in the [proper order]. + /// + /// The `after` parameter is `None` iff the hook is called before the first + /// non-custom section, and `Some(s)` afterwards, where `s` is the + /// [`SectionId`] of the previous non-custom section. + /// + /// The `before` parameter is `None` iff the hook is called after the last + /// non-custom section, and `Some(s)` beforehand, where `s` is the + /// [`SectionId`] of the following non-custom section. + /// + /// [proper order]: https://webassembly.github.io/spec/core/binary/modules.html#binary-module + /// [`SectionId`]: crate::SectionId + fn intersperse_section_hook( + &mut self, + module: &mut crate::Module, + after: Option, + before: Option, + ) -> Result<(), Error> { + utils::intersperse_section_hook(self, module, after, before) + } + + fn parse_core_module( + &mut self, + module: &mut crate::Module, + parser: wasmparser::Parser, + data: &[u8], + ) -> Result<(), Error> { + utils::parse_core_module(self, module, parser, data) + } +} + +/// An error when re-encoding from `wasmparser` to `wasm-encoder`. +#[derive(Debug)] +pub enum Error { + /// There was a type reference that was canonicalized and no longer + /// references an index into a module's types space, so we cannot encode it + /// into a Wasm binary again. + CanonicalizedHeapTypeReference, + /// The const expression is invalid: not actually constant or something like + /// that. + InvalidConstExpr, + /// There was a section that does not belong into a core wasm module. + UnexpectedNonCoreModuleSection, + /// There was an error when parsing. + ParseError(wasmparser::BinaryReaderError), + /// There was a user-defined error when re-encoding. + UserError(E), +} + +impl From for Error { + fn from(err: wasmparser::BinaryReaderError) -> Self { + Self::ParseError(err) + } +} + +impl std::fmt::Display for Error { + fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + Self::ParseError(_e) => { + write!(fmt, "There was an error when parsing") + } + Self::UserError(e) => write!(fmt, "{e}"), + Self::InvalidConstExpr => write!(fmt, "The const expression was invalid"), + Self::UnexpectedNonCoreModuleSection => write!( + fmt, + "There was a section that does not belong into a core wasm module" + ), + Self::CanonicalizedHeapTypeReference => write!( + fmt, + "There was a canonicalized heap type reference without type index information" + ), + } + } +} + +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + Self::ParseError(e) => Some(e), + Self::UserError(e) => Some(e), + Self::InvalidConstExpr + | Self::CanonicalizedHeapTypeReference + | Self::UnexpectedNonCoreModuleSection => None, + } + } +} + +/// Reencodes `wasmparser` into `wasm-encoder` so that the encoded wasm is +/// identical to the input and can be parsed and encoded again. +#[derive(Debug)] +pub struct RoundtripReencoder; + +impl Reencode for RoundtripReencoder { + type Error = Infallible; +} + +#[allow(missing_docs)] // FIXME +pub mod utils { + use super::{Error, Reencode}; + + pub fn parse_core_module( + reencoder: &mut T, + module: &mut crate::Module, + parser: wasmparser::Parser, + data: &[u8], + ) -> Result<(), Error> { + fn handle_intersperse_section_hook( + reencoder: &mut T, + module: &mut crate::Module, + last_section: &mut Option, + next_section: Option, + ) -> Result<(), Error> { + let after = std::mem::replace(last_section, next_section.clone()); + let before = next_section; + reencoder.intersperse_section_hook(module, after, before) + } + + let mut sections = parser.parse_all(data); + let mut next_section = sections.next(); + let mut last_section = None; + + 'outer: while let Some(section) = next_section { + match section? { + wasmparser::Payload::Version { + encoding: wasmparser::Encoding::Module, + .. + } => (), + wasmparser::Payload::Version { .. } => { + return Err(Error::UnexpectedNonCoreModuleSection) + } + wasmparser::Payload::TypeSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Type), + )?; + let mut types = crate::TypeSection::new(); + reencoder.parse_type_section(&mut types, section)?; + module.section(&types); + } + wasmparser::Payload::ImportSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Import), + )?; + let mut imports = crate::ImportSection::new(); + reencoder.parse_import_section(&mut imports, section)?; + module.section(&imports); + } + wasmparser::Payload::FunctionSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Function), + )?; + let mut functions = crate::FunctionSection::new(); + reencoder.parse_function_section(&mut functions, section)?; + module.section(&functions); + } + wasmparser::Payload::TableSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Table), + )?; + let mut tables = crate::TableSection::new(); + reencoder.parse_table_section(&mut tables, section)?; + module.section(&tables); + } + wasmparser::Payload::MemorySection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Memory), + )?; + let mut memories = crate::MemorySection::new(); + reencoder.parse_memory_section(&mut memories, section)?; + module.section(&memories); + } + wasmparser::Payload::TagSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Tag), + )?; + let mut tags = crate::TagSection::new(); + reencoder.parse_tag_section(&mut tags, section)?; + module.section(&tags); + } + wasmparser::Payload::GlobalSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Global), + )?; + let mut globals = crate::GlobalSection::new(); + reencoder.parse_global_section(&mut globals, section)?; + module.section(&globals); + } + wasmparser::Payload::ExportSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Export), + )?; + let mut exports = crate::ExportSection::new(); + reencoder.parse_export_section(&mut exports, section)?; + module.section(&exports); + } + wasmparser::Payload::StartSection { func, .. } => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Start), + )?; + module.section(&crate::StartSection { + function_index: reencoder.function_index(func), + }); + } + wasmparser::Payload::ElementSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Element), + )?; + let mut elements = crate::ElementSection::new(); + reencoder.parse_element_section(&mut elements, section)?; + module.section(&elements); + } + wasmparser::Payload::DataCountSection { count, .. } => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::DataCount), + )?; + module.section(&crate::DataCountSection { count }); + } + wasmparser::Payload::DataSection(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Data), + )?; + let mut data = crate::DataSection::new(); + reencoder.parse_data_section(&mut data, section)?; + module.section(&data); + } + wasmparser::Payload::CodeSectionStart { count, .. } => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Code), + )?; + let mut codes = crate::CodeSection::new(); + for _ in 0..count { + if let Some(Ok(wasmparser::Payload::CodeSectionEntry(section))) = + sections.next() + { + reencoder.parse_function_body(&mut codes, section)?; + } else { + return Err(Error::UnexpectedNonCoreModuleSection); + } + } + module.section(&codes); + } + wasmparser::Payload::CodeSectionEntry(section) => { + handle_intersperse_section_hook( + reencoder, + module, + &mut last_section, + Some(crate::SectionId::Code), + )?; + // we can't do better than start a new code section here + let mut codes = crate::CodeSection::new(); + reencoder.parse_function_body(&mut codes, section)?; + while let Some(section) = sections.next() { + let section = section?; + if let wasmparser::Payload::CodeSectionEntry(section) = section { + reencoder.parse_function_body(&mut codes, section)?; + } else { + module.section(&codes); + next_section = Some(Ok(section)); + continue 'outer; + } + } + module.section(&codes); + } + wasmparser::Payload::ModuleSection { .. } + | wasmparser::Payload::InstanceSection(_) + | wasmparser::Payload::CoreTypeSection(_) + | wasmparser::Payload::ComponentSection { .. } + | wasmparser::Payload::ComponentInstanceSection(_) + | wasmparser::Payload::ComponentAliasSection(_) + | wasmparser::Payload::ComponentTypeSection(_) + | wasmparser::Payload::ComponentCanonicalSection(_) + | wasmparser::Payload::ComponentStartSection { .. } + | wasmparser::Payload::ComponentImportSection(_) + | wasmparser::Payload::ComponentExportSection(_) => { + return Err(Error::UnexpectedNonCoreModuleSection) + } + wasmparser::Payload::CustomSection(section) => { + reencoder.parse_custom_section(module, section)?; + } + wasmparser::Payload::UnknownSection { id, contents, .. } => { + reencoder.parse_unknown_section(module, id, contents)?; + } + wasmparser::Payload::End(_) => { + handle_intersperse_section_hook(reencoder, module, &mut last_section, None)?; + } + } + + next_section = sections.next(); + } + + Ok(()) + } + + /// A hook method that is called inside [`Reencode::parse_core_module`] + /// before and after every non-custom core wasm section. + /// + /// This method can be used to insert new custom sections in between those + /// sections, or to detect when a non-custom section is missing and insert + /// it in the [proper order]. + /// + /// The `after` parameter is `None` iff the hook is called before the first + /// non-custom section, and `Some(s)` afterwards, where `s` is the + /// [`SectionId`] of the previous non-custom section. + /// + /// The `before` parameter is `None` iff the hook is called after the last + /// non-custom section, and `Some(s)` beforehand, where `s` is the + /// [`SectionId`] of the following non-custom section. + /// + /// [proper order]: https://webassembly.github.io/spec/core/binary/modules.html#binary-module + /// [`SectionId`]: crate::SectionId + pub fn intersperse_section_hook( + _reencoder: &mut T, + _module: &mut crate::Module, + _after: Option, + _before: Option, + ) -> Result<(), Error> { + Ok(()) + } + + pub fn component_primitive_val_type( + _reencoder: &mut T, + ty: wasmparser::PrimitiveValType, + ) -> crate::component::PrimitiveValType { + match ty { + wasmparser::PrimitiveValType::Bool => crate::component::PrimitiveValType::Bool, + wasmparser::PrimitiveValType::S8 => crate::component::PrimitiveValType::S8, + wasmparser::PrimitiveValType::U8 => crate::component::PrimitiveValType::U8, + wasmparser::PrimitiveValType::S16 => crate::component::PrimitiveValType::S16, + wasmparser::PrimitiveValType::U16 => crate::component::PrimitiveValType::U16, + wasmparser::PrimitiveValType::S32 => crate::component::PrimitiveValType::S32, + wasmparser::PrimitiveValType::U32 => crate::component::PrimitiveValType::U32, + wasmparser::PrimitiveValType::S64 => crate::component::PrimitiveValType::S64, + wasmparser::PrimitiveValType::U64 => crate::component::PrimitiveValType::U64, + wasmparser::PrimitiveValType::F32 => crate::component::PrimitiveValType::F32, + wasmparser::PrimitiveValType::F64 => crate::component::PrimitiveValType::F64, + wasmparser::PrimitiveValType::Char => crate::component::PrimitiveValType::Char, + wasmparser::PrimitiveValType::String => crate::component::PrimitiveValType::String, + } + } + + pub fn memory_index(_reencoder: &mut T, memory: u32) -> u32 { + memory + } + + pub fn mem_arg( + reencoder: &mut T, + arg: wasmparser::MemArg, + ) -> crate::MemArg { + crate::MemArg { + offset: arg.offset, + align: arg.align.into(), + memory_index: reencoder.memory_index(arg.memory), + } + } + + pub fn ordering( + _reencoder: &mut T, + arg: wasmparser::Ordering, + ) -> crate::Ordering { + match arg { + wasmparser::Ordering::SeqCst => crate::Ordering::SeqCst, + wasmparser::Ordering::AcqRel => crate::Ordering::AcqRel, + } + } + + pub fn function_index(_reencoder: &mut T, func: u32) -> u32 { + func + } + + pub fn tag_index(_reencoder: &mut T, tag: u32) -> u32 { + tag + } + + pub fn catch(reencoder: &mut T, arg: wasmparser::Catch) -> crate::Catch { + match arg { + wasmparser::Catch::One { tag, label } => crate::Catch::One { + tag: reencoder.tag_index(tag), + label, + }, + wasmparser::Catch::OneRef { tag, label } => crate::Catch::OneRef { + tag: reencoder.tag_index(tag), + label, + }, + wasmparser::Catch::All { label } => crate::Catch::All { label }, + wasmparser::Catch::AllRef { label } => crate::Catch::AllRef { label }, + } + } + + /// Parses the input `section` given from the `wasmparser` crate and + /// adds the custom section to the `module`. + pub fn parse_custom_section( + reencoder: &mut T, + module: &mut crate::Module, + section: wasmparser::CustomSectionReader<'_>, + ) -> Result<(), Error> { + module.section(&reencoder.custom_section(section)); + Ok(()) + } + + /// Converts the input `section` given from the `wasmparser` crate into an + /// encoded custom section. + pub fn custom_section<'a, T: ?Sized + Reencode>( + _reencoder: &mut T, + section: wasmparser::CustomSectionReader<'a>, + ) -> crate::CustomSection<'a> { + crate::CustomSection { + data: section.data().into(), + name: section.name().into(), + } + } + + pub fn export_kind( + _reencoder: &mut T, + external_kind: wasmparser::ExternalKind, + ) -> crate::ExportKind { + match external_kind { + wasmparser::ExternalKind::Func => crate::ExportKind::Func, + wasmparser::ExternalKind::Table => crate::ExportKind::Table, + wasmparser::ExternalKind::Memory => crate::ExportKind::Memory, + wasmparser::ExternalKind::Global => crate::ExportKind::Global, + wasmparser::ExternalKind::Tag => crate::ExportKind::Tag, + } + } + + pub fn memory_type( + _reencoder: &mut T, + memory_ty: wasmparser::MemoryType, + ) -> crate::MemoryType { + crate::MemoryType { + minimum: memory_ty.initial, + maximum: memory_ty.maximum, + memory64: memory_ty.memory64, + shared: memory_ty.shared, + page_size_log2: memory_ty.page_size_log2, + } + } + + pub fn tag_kind( + _reencoder: &mut T, + kind: wasmparser::TagKind, + ) -> crate::TagKind { + match kind { + wasmparser::TagKind::Exception => crate::TagKind::Exception, + } + } + + pub fn type_index(_reencoder: &mut T, ty: u32) -> u32 { + ty + } + + pub fn tag_type( + reencoder: &mut T, + tag_ty: wasmparser::TagType, + ) -> crate::TagType { + crate::TagType { + kind: reencoder.tag_kind(tag_ty.kind), + func_type_idx: reencoder.type_index(tag_ty.func_type_idx), + } + } + + pub fn abstract_heap_type( + _reencoder: &mut T, + value: wasmparser::AbstractHeapType, + ) -> crate::AbstractHeapType { + use wasmparser::AbstractHeapType::*; + match value { + Func => crate::AbstractHeapType::Func, + Extern => crate::AbstractHeapType::Extern, + Any => crate::AbstractHeapType::Any, + None => crate::AbstractHeapType::None, + NoExtern => crate::AbstractHeapType::NoExtern, + NoFunc => crate::AbstractHeapType::NoFunc, + Eq => crate::AbstractHeapType::Eq, + Struct => crate::AbstractHeapType::Struct, + Array => crate::AbstractHeapType::Array, + I31 => crate::AbstractHeapType::I31, + Exn => crate::AbstractHeapType::Exn, + NoExn => crate::AbstractHeapType::NoExn, + } + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the types to the `types` section. + pub fn parse_type_section( + reencoder: &mut T, + types: &mut crate::TypeSection, + section: wasmparser::TypeSectionReader<'_>, + ) -> Result<(), Error> { + for rec_group in section { + reencoder.parse_recursive_type_group(types, rec_group?)?; + } + Ok(()) + } + + /// Parses a single [`wasmparser::RecGroup`] and adds it to the `types` section. + pub fn parse_recursive_type_group( + reencoder: &mut T, + types: &mut crate::TypeSection, + rec_group: wasmparser::RecGroup, + ) -> Result<(), Error> { + if rec_group.is_explicit_rec_group() { + let subtypes = rec_group + .into_types() + .map(|t| reencoder.sub_type(t)) + .collect::, _>>()?; + types.rec(subtypes); + } else { + let ty = rec_group.into_types().next().unwrap(); + types.subtype(&reencoder.sub_type(ty)?); + } + Ok(()) + } + + pub fn sub_type( + reencoder: &mut T, + sub_ty: wasmparser::SubType, + ) -> Result> { + Ok(crate::SubType { + is_final: sub_ty.is_final, + supertype_idx: sub_ty + .supertype_idx + .map(|i| { + i.as_module_index() + .map(|ty| reencoder.type_index(ty)) + .ok_or(Error::CanonicalizedHeapTypeReference) + }) + .transpose()?, + composite_type: reencoder.composite_type(sub_ty.composite_type)?, + }) + } + + pub fn composite_type( + reencoder: &mut T, + composite_ty: wasmparser::CompositeType, + ) -> Result> { + Ok(match composite_ty { + wasmparser::CompositeType::Func(f) => { + crate::CompositeType::Func(reencoder.func_type(f)?) + } + wasmparser::CompositeType::Array(a) => { + crate::CompositeType::Array(reencoder.array_type(a)?) + } + wasmparser::CompositeType::Struct(s) => { + crate::CompositeType::Struct(reencoder.struct_type(s)?) + } + }) + } + + pub fn func_type( + reencoder: &mut T, + func_ty: wasmparser::FuncType, + ) -> Result> { + let mut buf = Vec::with_capacity(func_ty.params().len() + func_ty.results().len()); + for ty in func_ty.params().iter().chain(func_ty.results()).copied() { + buf.push(reencoder.val_type(ty)?); + } + Ok(crate::FuncType::from_parts( + buf.into(), + func_ty.params().len(), + )) + } + + pub fn array_type( + reencoder: &mut T, + array_ty: wasmparser::ArrayType, + ) -> Result> { + Ok(crate::ArrayType(reencoder.field_type(array_ty.0)?)) + } + + pub fn struct_type( + reencoder: &mut T, + struct_ty: wasmparser::StructType, + ) -> Result> { + Ok(crate::StructType { + fields: struct_ty + .fields + .iter() + .map(|field_ty| reencoder.field_type(field_ty.clone())) + .collect::>()?, + }) + } + + pub fn field_type( + reencoder: &mut T, + field_ty: wasmparser::FieldType, + ) -> Result> { + Ok(crate::FieldType { + element_type: reencoder.storage_type(field_ty.element_type)?, + mutable: field_ty.mutable, + }) + } + + pub fn storage_type( + reencoder: &mut T, + storage_ty: wasmparser::StorageType, + ) -> Result> { + Ok(match storage_ty { + wasmparser::StorageType::I8 => crate::StorageType::I8, + wasmparser::StorageType::I16 => crate::StorageType::I16, + wasmparser::StorageType::Val(v) => crate::StorageType::Val(reencoder.val_type(v)?), + }) + } + + pub fn val_type( + reencoder: &mut T, + val_ty: wasmparser::ValType, + ) -> Result> { + Ok(match val_ty { + wasmparser::ValType::I32 => crate::ValType::I32, + wasmparser::ValType::I64 => crate::ValType::I64, + wasmparser::ValType::F32 => crate::ValType::F32, + wasmparser::ValType::F64 => crate::ValType::F64, + wasmparser::ValType::V128 => crate::ValType::V128, + wasmparser::ValType::Ref(r) => crate::ValType::Ref(reencoder.ref_type(r)?), + }) + } + + pub fn ref_type( + reencoder: &mut T, + ref_type: wasmparser::RefType, + ) -> Result> { + Ok(crate::RefType { + nullable: ref_type.is_nullable(), + heap_type: reencoder.heap_type(ref_type.heap_type())?, + }) + } + + pub fn heap_type( + reencoder: &mut T, + heap_type: wasmparser::HeapType, + ) -> Result> { + Ok(match heap_type { + wasmparser::HeapType::Concrete(i) => crate::HeapType::Concrete( + i.as_module_index() + .map(|ty| reencoder.type_index(ty)) + .ok_or(Error::CanonicalizedHeapTypeReference)?, + ), + wasmparser::HeapType::Abstract { shared, ty } => crate::HeapType::Abstract { + shared, + ty: reencoder.abstract_heap_type(ty), + }, + }) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the tables to the `tables` section. + pub fn parse_table_section( + reencoder: &mut T, + tables: &mut crate::TableSection, + section: wasmparser::TableSectionReader<'_>, + ) -> Result<(), Error> { + for table in section { + reencoder.parse_table(tables, table?)?; + } + Ok(()) + } + + /// Parses a single [`wasmparser::Table`] and adds it to the `tables` section. + pub fn parse_table( + reencoder: &mut T, + tables: &mut crate::TableSection, + table: wasmparser::Table<'_>, + ) -> Result<(), Error> { + let ty = reencoder.table_type(table.ty)?; + match table.init { + wasmparser::TableInit::RefNull => { + tables.table(ty); + } + wasmparser::TableInit::Expr(e) => { + tables.table_with_init(ty, &reencoder.const_expr(e)?); + } + } + Ok(()) + } + + pub fn table_type( + reencoder: &mut T, + table_ty: wasmparser::TableType, + ) -> Result> { + Ok(crate::TableType { + element_type: reencoder.ref_type(table_ty.element_type)?, + minimum: table_ty.initial, + maximum: table_ty.maximum, + table64: table_ty.table64, + }) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the tags to the `tags` section. + pub fn parse_tag_section( + reencoder: &mut T, + tags: &mut crate::TagSection, + section: wasmparser::TagSectionReader<'_>, + ) -> Result<(), Error> { + for tag in section { + let tag = tag?; + tags.tag(reencoder.tag_type(tag)); + } + Ok(()) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the exports to the `exports` section. + pub fn parse_export_section( + reencoder: &mut T, + exports: &mut crate::ExportSection, + section: wasmparser::ExportSectionReader<'_>, + ) -> Result<(), Error> { + for export in section { + reencoder.parse_export(exports, export?); + } + Ok(()) + } + + /// Parses the single [`wasmparser::Export`] provided and adds it to the + /// `exports` section. + pub fn parse_export( + reencoder: &mut T, + exports: &mut crate::ExportSection, + export: wasmparser::Export<'_>, + ) { + exports.export( + export.name, + reencoder.export_kind(export.kind), + match export.kind { + wasmparser::ExternalKind::Func => reencoder.function_index(export.index), + wasmparser::ExternalKind::Table => reencoder.table_index(export.index), + wasmparser::ExternalKind::Memory => reencoder.memory_index(export.index), + wasmparser::ExternalKind::Global => reencoder.global_index(export.index), + wasmparser::ExternalKind::Tag => reencoder.tag_index(export.index), + }, + ); + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the globals to the `globals` section. + pub fn parse_global_section( + reencoder: &mut T, + globals: &mut crate::GlobalSection, + section: wasmparser::GlobalSectionReader<'_>, + ) -> Result<(), Error> { + for global in section { + reencoder.parse_global(globals, global?)?; + } + Ok(()) + } + + /// Parses the single [`wasmparser::Global`] provided and adds it to the + /// `globals` section. + pub fn parse_global( + reencoder: &mut T, + globals: &mut crate::GlobalSection, + global: wasmparser::Global<'_>, + ) -> Result<(), Error> { + globals.global( + reencoder.global_type(global.ty)?, + &reencoder.const_expr(global.init_expr)?, + ); + Ok(()) + } + + pub fn global_type( + reencoder: &mut T, + global_ty: wasmparser::GlobalType, + ) -> Result> { + Ok(crate::GlobalType { + val_type: reencoder.val_type(global_ty.content_type)?, + mutable: global_ty.mutable, + shared: global_ty.shared, + }) + } + + pub fn entity_type( + reencoder: &mut T, + type_ref: wasmparser::TypeRef, + ) -> Result> { + Ok(match type_ref { + wasmparser::TypeRef::Func(i) => crate::EntityType::Function(reencoder.type_index(i)), + wasmparser::TypeRef::Table(t) => crate::EntityType::Table(reencoder.table_type(t)?), + wasmparser::TypeRef::Memory(m) => crate::EntityType::Memory(reencoder.memory_type(m)), + wasmparser::TypeRef::Global(g) => crate::EntityType::Global(reencoder.global_type(g)?), + wasmparser::TypeRef::Tag(t) => crate::EntityType::Tag(reencoder.tag_type(t)), + }) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the imports to the `import` section. + pub fn parse_import_section( + reencoder: &mut T, + imports: &mut crate::ImportSection, + section: wasmparser::ImportSectionReader<'_>, + ) -> Result<(), Error> { + for import in section { + reencoder.parse_import(imports, import?)?; + } + Ok(()) + } + + /// Parses the single [`wasmparser::Import`] provided and adds it to the + /// `import` section. + pub fn parse_import( + reencoder: &mut T, + imports: &mut crate::ImportSection, + import: wasmparser::Import<'_>, + ) -> Result<(), Error> { + imports.import( + import.module, + import.name, + reencoder.entity_type(import.ty)?, + ); + Ok(()) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the memories to the `memories` section. + pub fn parse_memory_section( + reencoder: &mut T, + memories: &mut crate::MemorySection, + section: wasmparser::MemorySectionReader<'_>, + ) -> Result<(), Error> { + for memory in section { + let memory = memory?; + memories.memory(reencoder.memory_type(memory)); + } + Ok(()) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the functions to the `functions` section. + pub fn parse_function_section( + reencoder: &mut T, + functions: &mut crate::FunctionSection, + section: wasmparser::FunctionSectionReader<'_>, + ) -> Result<(), Error> { + for func in section { + functions.function(reencoder.type_index(func?)); + } + Ok(()) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the data to the `data` section. + pub fn parse_data_section( + reencoder: &mut T, + data: &mut crate::DataSection, + section: wasmparser::DataSectionReader<'_>, + ) -> Result<(), Error> { + for datum in section { + reencoder.parse_data(data, datum?)?; + } + Ok(()) + } + + /// Parses a single [`wasmparser::Data`] and adds it to the `data` section. + pub fn parse_data( + reencoder: &mut T, + data: &mut crate::DataSection, + datum: wasmparser::Data<'_>, + ) -> Result<(), Error> { + match datum.kind { + wasmparser::DataKind::Active { + memory_index, + offset_expr, + } => data.active( + reencoder.memory_index(memory_index), + &reencoder.const_expr(offset_expr)?, + datum.data.iter().copied(), + ), + wasmparser::DataKind::Passive => data.passive(datum.data.iter().copied()), + }; + Ok(()) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the elements to the `element` section. + pub fn parse_element_section( + reencoder: &mut T, + elements: &mut crate::ElementSection, + section: wasmparser::ElementSectionReader<'_>, + ) -> Result<(), Error> { + for element in section { + reencoder.parse_element(elements, element?)?; + } + Ok(()) + } + + /// Parses the single [`wasmparser::Element`] provided and adds it to the + /// `element` section. + pub fn parse_element( + reencoder: &mut T, + elements: &mut crate::ElementSection, + element: wasmparser::Element<'_>, + ) -> Result<(), Error> { + let mut funcs; + let mut exprs; + let elems = match element.items { + wasmparser::ElementItems::Functions(f) => { + funcs = Vec::new(); + for func in f { + funcs.push(reencoder.function_index(func?)); + } + crate::Elements::Functions(&funcs) + } + wasmparser::ElementItems::Expressions(ty, e) => { + exprs = Vec::new(); + for expr in e { + exprs.push(reencoder.const_expr(expr?)?); + } + crate::Elements::Expressions(reencoder.ref_type(ty)?, &exprs) + } + }; + match element.kind { + wasmparser::ElementKind::Active { + table_index, + offset_expr, + } => elements.active( + table_index.map(|t| reencoder.table_index(t)), + &reencoder.const_expr(offset_expr)?, + elems, + ), + wasmparser::ElementKind::Passive => elements.passive(elems), + wasmparser::ElementKind::Declared => elements.declared(elems), + }; + Ok(()) + } + + pub fn table_index(_reencoder: &mut T, table: u32) -> u32 { + table + } + + pub fn global_index(_reencoder: &mut T, global: u32) -> u32 { + global + } + + pub fn data_index(_reencoder: &mut T, data: u32) -> u32 { + data + } + + pub fn element_index(_reencoder: &mut T, element: u32) -> u32 { + element + } + + pub fn const_expr( + reencoder: &mut T, + const_expr: wasmparser::ConstExpr, + ) -> Result> { + let mut ops = const_expr.get_operators_reader().into_iter(); + + let result = match ops.next() { + Some(Ok(wasmparser::Operator::I32Const { value })) => { + crate::ConstExpr::i32_const(value) + } + Some(Ok(wasmparser::Operator::I64Const { value })) => { + crate::ConstExpr::i64_const(value) + } + Some(Ok(wasmparser::Operator::F32Const { value })) => { + crate::ConstExpr::f32_const(f32::from_bits(value.bits())) + } + Some(Ok(wasmparser::Operator::F64Const { value })) => { + crate::ConstExpr::f64_const(f64::from_bits(value.bits())) + } + Some(Ok(wasmparser::Operator::V128Const { value })) => { + crate::ConstExpr::v128_const(i128::from_le_bytes(*value.bytes())) + } + Some(Ok(wasmparser::Operator::RefNull { hty })) => { + crate::ConstExpr::ref_null(reencoder.heap_type(hty)?) + } + Some(Ok(wasmparser::Operator::RefFunc { function_index })) => { + crate::ConstExpr::ref_func(reencoder.function_index(function_index)) + } + Some(Ok(wasmparser::Operator::GlobalGet { global_index })) => { + crate::ConstExpr::global_get(reencoder.global_index(global_index)) + } + + // TODO: support the extended-const proposal. + Some(Ok(_op)) => return Err(Error::InvalidConstExpr), + + Some(Err(e)) => return Err(Error::ParseError(e)), + None => return Err(Error::InvalidConstExpr), + }; + + match (ops.next(), ops.next()) { + (Some(Ok(wasmparser::Operator::End)), None) => Ok(result), + _ => Err(Error::InvalidConstExpr), + } + } + + pub fn block_type( + reencoder: &mut T, + arg: wasmparser::BlockType, + ) -> Result> { + match arg { + wasmparser::BlockType::Empty => Ok(crate::BlockType::Empty), + wasmparser::BlockType::FuncType(n) => { + Ok(crate::BlockType::FunctionType(reencoder.type_index(n))) + } + wasmparser::BlockType::Type(t) => Ok(crate::BlockType::Result(reencoder.val_type(t)?)), + } + } + + pub fn instruction<'a, T: ?Sized + Reencode>( + reencoder: &mut T, + arg: wasmparser::Operator<'a>, + ) -> Result, Error> { + use crate::Instruction; + + macro_rules! translate { + ($( @$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { + Ok(match arg { + $( + wasmparser::Operator::$op $({ $($arg),* })? => { + $( + $(let $arg = translate!(map $arg $arg);)* + )? + translate!(build $op $($($arg)*)?) + } + )* + }) + }; + + // This case is used to map, based on the name of the field, from the + // wasmparser payload type to the wasm-encoder payload type through + // `Translator` as applicable. + (map $arg:ident tag_index) => (reencoder.tag_index($arg)); + (map $arg:ident function_index) => (reencoder.function_index($arg)); + (map $arg:ident table) => (reencoder.table_index($arg)); + (map $arg:ident table_index) => (reencoder.table_index($arg)); + (map $arg:ident dst_table) => (reencoder.table_index($arg)); + (map $arg:ident src_table) => (reencoder.table_index($arg)); + (map $arg:ident type_index) => (reencoder.type_index($arg)); + (map $arg:ident array_type_index) => (reencoder.type_index($arg)); + (map $arg:ident array_type_index_dst) => (reencoder.type_index($arg)); + (map $arg:ident array_type_index_src) => (reencoder.type_index($arg)); + (map $arg:ident struct_type_index) => (reencoder.type_index($arg)); + (map $arg:ident global_index) => (reencoder.global_index($arg)); + (map $arg:ident mem) => (reencoder.memory_index($arg)); + (map $arg:ident src_mem) => (reencoder.memory_index($arg)); + (map $arg:ident dst_mem) => (reencoder.memory_index($arg)); + (map $arg:ident data_index) => (reencoder.data_index($arg)); + (map $arg:ident elem_index) => (reencoder.element_index($arg)); + (map $arg:ident array_data_index) => (reencoder.data_index($arg)); + (map $arg:ident array_elem_index) => (reencoder.element_index($arg)); + (map $arg:ident blockty) => (reencoder.block_type($arg)?); + (map $arg:ident relative_depth) => ($arg); + (map $arg:ident targets) => (( + $arg + .targets() + .collect::, wasmparser::BinaryReaderError>>()? + .into(), + $arg.default(), + )); + (map $arg:ident ty) => (reencoder.val_type($arg)?); + (map $arg:ident hty) => (reencoder.heap_type($arg)?); + (map $arg:ident from_ref_type) => (reencoder.ref_type($arg)?); + (map $arg:ident to_ref_type) => (reencoder.ref_type($arg)?); + (map $arg:ident memarg) => (reencoder.mem_arg($arg)); + (map $arg:ident ordering) => (reencoder.ordering($arg)); + (map $arg:ident local_index) => ($arg); + (map $arg:ident value) => ($arg); + (map $arg:ident lane) => ($arg); + (map $arg:ident lanes) => ($arg); + (map $arg:ident array_size) => ($arg); + (map $arg:ident field_index) => ($arg); + (map $arg:ident try_table) => ($arg); + + // This case takes the arguments of a wasmparser instruction and creates + // a wasm-encoder instruction. There are a few special cases for where + // the structure of a wasmparser instruction differs from that of + // wasm-encoder. + (build $op:ident) => (Instruction::$op); + (build BrTable $arg:ident) => (Instruction::BrTable($arg.0, $arg.1)); + (build I32Const $arg:ident) => (Instruction::I32Const($arg)); + (build I64Const $arg:ident) => (Instruction::I64Const($arg)); + (build F32Const $arg:ident) => (Instruction::F32Const(f32::from_bits($arg.bits()))); + (build F64Const $arg:ident) => (Instruction::F64Const(f64::from_bits($arg.bits()))); + (build V128Const $arg:ident) => (Instruction::V128Const($arg.i128())); + (build TryTable $table:ident) => (Instruction::TryTable(reencoder.block_type($table.ty)?, { + $table.catches.into_iter().map(|c| reencoder.catch(c)).collect::>().into() + })); + (build $op:ident $arg:ident) => (Instruction::$op($arg)); + (build $op:ident $($arg:ident)*) => (Instruction::$op { $($arg),* }); + } + + wasmparser::for_each_operator!(translate) + } + + /// Parses the input `section` given from the `wasmparser` crate and adds + /// all the code to the `code` section. + pub fn parse_code_section( + reencoder: &mut T, + code: &mut crate::CodeSection, + section: wasmparser::CodeSectionReader<'_>, + ) -> Result<(), Error> { + for func in section { + reencoder.parse_function_body(code, func?)?; + } + Ok(()) + } + + /// Parses a single [`wasmparser::FunctionBody`] and adds it to the `code` section. + pub fn parse_function_body( + reencoder: &mut T, + code: &mut crate::CodeSection, + func: wasmparser::FunctionBody<'_>, + ) -> Result<(), Error> { + let mut f = reencoder.new_function_with_parsed_locals(&func)?; + let mut reader = func.get_operators_reader()?; + while !reader.eof() { + reencoder.parse_instruction(&mut f, &mut reader)?; + } + code.function(&f); + Ok(()) + } + + /// Create a new [`crate::Function`] by parsing the locals declarations from the + /// provided [`wasmparser::FunctionBody`]. + pub fn new_function_with_parsed_locals( + reencoder: &mut T, + func: &wasmparser::FunctionBody<'_>, + ) -> Result> { + let mut locals = Vec::new(); + for pair in func.get_locals_reader()? { + let (cnt, ty) = pair?; + locals.push((cnt, reencoder.val_type(ty)?)); + } + Ok(crate::Function::new(locals)) + } + + /// Parses a single instruction from `reader` and adds it to `function`. + pub fn parse_instruction( + reencoder: &mut T, + function: &mut crate::Function, + reader: &mut wasmparser::OperatorsReader<'_>, + ) -> Result<(), Error> { + function.instruction(&reencoder.instruction(reader.read()?)?); + Ok(()) + } + + pub fn parse_unknown_section( + _reencoder: &mut T, + module: &mut crate::Module, + id: u8, + contents: &[u8], + ) -> Result<(), Error> { + module.section(&crate::RawSection { id, data: contents }); + Ok(()) + } +} + +impl From for crate::PrimitiveValType { + fn from(ty: wasmparser::PrimitiveValType) -> Self { + RoundtripReencoder.component_primitive_val_type(ty) + } +} + +impl From for crate::MemArg { + fn from(arg: wasmparser::MemArg) -> Self { + RoundtripReencoder.mem_arg(arg) + } +} + +impl From for crate::Ordering { + fn from(arg: wasmparser::Ordering) -> Self { + RoundtripReencoder.ordering(arg) + } +} + +impl TryFrom for crate::BlockType { + type Error = Error; + + fn try_from(arg: wasmparser::BlockType) -> Result { + RoundtripReencoder.block_type(arg) + } +} + +impl<'a> TryFrom> for crate::Instruction<'a> { + type Error = Error; + + fn try_from(arg: wasmparser::Operator<'a>) -> Result { + RoundtripReencoder.instruction(arg) + } +} + +impl From for crate::Catch { + fn from(arg: wasmparser::Catch) -> Self { + RoundtripReencoder.catch(arg) + } +} + +impl<'a> TryFrom> for crate::ConstExpr { + type Error = Error; + + fn try_from(const_expr: wasmparser::ConstExpr) -> Result { + RoundtripReencoder.const_expr(const_expr) + } +} + +impl<'a> From> for crate::CustomSection<'a> { + fn from(section: wasmparser::CustomSectionReader<'a>) -> Self { + RoundtripReencoder.custom_section(section) + } +} + +impl From for crate::ExportKind { + fn from(external_kind: wasmparser::ExternalKind) -> Self { + RoundtripReencoder.export_kind(external_kind) + } +} + +impl TryFrom for crate::GlobalType { + type Error = Error; + + fn try_from(global_ty: wasmparser::GlobalType) -> Result { + RoundtripReencoder.global_type(global_ty) + } +} + +impl TryFrom for crate::EntityType { + type Error = Error; + + fn try_from(type_ref: wasmparser::TypeRef) -> Result { + RoundtripReencoder.entity_type(type_ref) + } +} + +impl From for crate::MemoryType { + fn from(memory_ty: wasmparser::MemoryType) -> Self { + RoundtripReencoder.memory_type(memory_ty) + } +} + +impl TryFrom for crate::TableType { + type Error = Error; + + fn try_from(table_ty: wasmparser::TableType) -> Result { + RoundtripReencoder.table_type(table_ty) + } +} + +impl From for crate::TagKind { + fn from(kind: wasmparser::TagKind) -> Self { + RoundtripReencoder.tag_kind(kind) + } +} + +impl From for crate::TagType { + fn from(tag_ty: wasmparser::TagType) -> Self { + RoundtripReencoder.tag_type(tag_ty) + } +} + +impl TryFrom for crate::SubType { + type Error = Error; + + fn try_from(sub_ty: wasmparser::SubType) -> Result { + RoundtripReencoder.sub_type(sub_ty) + } +} + +impl TryFrom for crate::CompositeType { + type Error = Error; + + fn try_from(composite_ty: wasmparser::CompositeType) -> Result { + RoundtripReencoder.composite_type(composite_ty) + } +} + +impl TryFrom for crate::FuncType { + type Error = Error; + + fn try_from(func_ty: wasmparser::FuncType) -> Result { + RoundtripReencoder.func_type(func_ty) + } +} + +impl TryFrom for crate::ArrayType { + type Error = Error; + + fn try_from(array_ty: wasmparser::ArrayType) -> Result { + RoundtripReencoder.array_type(array_ty) + } +} + +impl TryFrom for crate::StructType { + type Error = Error; + + fn try_from(struct_ty: wasmparser::StructType) -> Result { + RoundtripReencoder.struct_type(struct_ty) + } +} + +impl TryFrom for crate::FieldType { + type Error = Error; + + fn try_from(field_ty: wasmparser::FieldType) -> Result { + RoundtripReencoder.field_type(field_ty) + } +} + +impl TryFrom for crate::StorageType { + type Error = Error; + + fn try_from(storage_ty: wasmparser::StorageType) -> Result { + RoundtripReencoder.storage_type(storage_ty) + } +} + +impl TryFrom for crate::ValType { + type Error = Error; + + fn try_from(val_ty: wasmparser::ValType) -> Result { + RoundtripReencoder.val_type(val_ty) + } +} + +impl TryFrom for crate::RefType { + type Error = Error; + + fn try_from(ref_type: wasmparser::RefType) -> Result { + RoundtripReencoder.ref_type(ref_type) + } +} + +impl TryFrom for crate::HeapType { + type Error = Error; + + fn try_from(heap_type: wasmparser::HeapType) -> Result { + crate::reencode::utils::heap_type(&mut crate::reencode::RoundtripReencoder, heap_type) + } +} + +impl From for crate::AbstractHeapType { + fn from(value: wasmparser::AbstractHeapType) -> Self { + RoundtripReencoder.abstract_heap_type(value) + } +} diff --git a/crates/wasmparser/src/parser.rs b/crates/wasmparser/src/parser.rs index a0f54c8acc..54bf612101 100644 --- a/crates/wasmparser/src/parser.rs +++ b/crates/wasmparser/src/parser.rs @@ -71,7 +71,7 @@ enum State { pub enum Chunk<'a> { /// This can be returned at any time and indicates that more data is needed /// to proceed with parsing. Zero bytes were consumed from the input to - /// [`Parser::parse`]. The `usize` value here is a hint as to how many more + /// [`Parser::parse`]. The `u64` value here is a hint as to how many more /// bytes are needed to continue parsing. NeedMoreData(u64), diff --git a/crates/wasmparser/src/readers/core/tables.rs b/crates/wasmparser/src/readers/core/tables.rs index e79ed33d38..6c144a578f 100644 --- a/crates/wasmparser/src/readers/core/tables.rs +++ b/crates/wasmparser/src/readers/core/tables.rs @@ -20,7 +20,7 @@ pub type TableSectionReader<'a> = SectionLimited<'a, Table<'a>>; /// Type information about a table defined in the table section of a WebAssembly /// module. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Table<'a> { /// The type of this table, including its element type and its limits. pub ty: TableType, @@ -29,7 +29,7 @@ pub struct Table<'a> { } /// Different modes of initializing a table. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum TableInit<'a> { /// The table is initialized to all null elements. RefNull, diff --git a/crates/wit-component/src/gc.rs b/crates/wit-component/src/gc.rs index 0fa13d85f4..ed1bd350fe 100644 --- a/crates/wit-component/src/gc.rs +++ b/crates/wit-component/src/gc.rs @@ -4,9 +4,11 @@ use indexmap::{IndexMap, IndexSet}; use std::{ borrow::Cow, collections::{HashMap, HashSet}, + convert::Infallible, mem, ops::Deref, }; +use wasm_encoder::reencode::Reencode; use wasm_encoder::{Encode, EntityType, Instruction, RawCustomSection}; use wasmparser::*; @@ -552,10 +554,8 @@ impl<'a> Module<'a> { for (i, ty) in self.live_types() { map.types.push(i); - types.function( - ty.params().iter().map(|t| map.valty(*t)), - ty.results().iter().map(|t| map.valty(*t)), - ); + let ty = map.func_type(ty.clone())?; + types.func_type(&ty); // Keep track of the "empty type" to see if we can reuse an // existing one or one needs to be injected if a `start` @@ -568,7 +568,7 @@ impl<'a> Module<'a> { let mut num_memories = 0; for (i, mem) in self.live_memories() { map.memories.push(i); - let ty = mem.ty.into(); + let ty = map.memory_type(mem.ty); match &mem.def { Definition::Import(m, n) => { imports.import(m, n, ty); @@ -582,12 +582,7 @@ impl<'a> Module<'a> { for (i, table) in self.live_tables() { map.tables.push(i); - let ty = wasm_encoder::TableType { - minimum: table.ty.initial, - maximum: table.ty.maximum, - element_type: map.refty(table.ty.element_type), - table64: table.ty.table64, - }; + let ty = map.table_type(table.ty.clone())?; match &table.def { Definition::Import(m, n) => { imports.import(m, n, ty); @@ -600,19 +595,14 @@ impl<'a> Module<'a> { for (i, global) in self.live_globals() { map.globals.push(i); - let ty = wasm_encoder::GlobalType { - val_type: map.valty(global.ty.content_type), - mutable: global.ty.mutable, - shared: global.ty.shared, - }; + let ty = map.global_type(global.ty.clone())?; match &global.def { Definition::Import(m, n) => { imports.import(m, n, ty); } Definition::Local(init) => { - let mut bytes = map.operators(init.get_binary_reader())?; - assert_eq!(bytes.pop(), Some(0xb)); - globals.global(ty, &wasm_encoder::ConstExpr::raw(bytes)); + let init = &map.const_expr(init.clone())?; + globals.global(ty, &init); } } } @@ -743,26 +733,27 @@ impl<'a> Module<'a> { .collect::>(); for (i, func) in self.live_funcs() { - let mut body = match &func.def { + let body = match &func.def { Definition::Import(..) => continue, - Definition::Local(body) => body.get_binary_reader(), + Definition::Local(body) => body, }; - let mut locals = Vec::new(); - for _ in 0..body.read_var_u32()? { - let cnt = body.read_var_u32()?; - let ty = body.read()?; - locals.push((cnt, map.valty(ty))); - } - // Prepend an `allocate_stack` call to all exports if we're lazily allocating the stack. - if let (Some(lazy_stack_init_index), true) = - (lazy_stack_init_index, exported_funcs.contains(&i)) - { - Instruction::Call(lazy_stack_init_index).encode(&mut map.buf); + + match (lazy_stack_init_index, exported_funcs.contains(&i)) { + // Prepend an `allocate_stack` call to all exports if we're + // lazily allocating the stack. + (Some(lazy_stack_init_index), true) => { + let mut func = map.new_function_with_parsed_locals(&body)?; + func.instruction(&Instruction::Call(lazy_stack_init_index)); + let mut reader = body.get_operators_reader()?; + while !reader.eof() { + map.parse_instruction(&mut func, &mut reader)?; + } + code.function(&func); + } + _ => { + map.parse_function_body(&mut code, body.clone())?; + } } - let bytes = map.operators(body)?; - let mut func = wasm_encoder::Function::new(locals); - func.raw(bytes); - code.function(&func); } if lazy_stack_init_index.is_some() { @@ -1061,178 +1052,28 @@ struct Encoder { memories: Remap, globals: Remap, tables: Remap, - buf: Vec, } -impl Encoder { - fn operators(&mut self, mut reader: BinaryReader<'_>) -> Result> { - while !reader.eof() { - reader.visit_operator(self)?; - } - Ok(mem::take(&mut self.buf)) - } - - fn memarg(&self, ty: MemArg) -> wasm_encoder::MemArg { - wasm_encoder::MemArg { - offset: ty.offset, - align: ty.align.into(), - memory_index: self.memories.remap(ty.memory), - } - } +impl Reencode for Encoder { + type Error = Infallible; - fn ordering(&self, ord: Ordering) -> wasm_encoder::Ordering { - match ord { - Ordering::AcqRel => wasm_encoder::Ordering::AcqRel, - Ordering::SeqCst => wasm_encoder::Ordering::SeqCst, - } + fn type_index(&mut self, i: u32) -> u32 { + self.types.remap(i) } - - fn blockty(&self, ty: BlockType) -> wasm_encoder::BlockType { - match ty { - BlockType::Empty => wasm_encoder::BlockType::Empty, - BlockType::Type(ty) => wasm_encoder::BlockType::Result(self.valty(ty)), - BlockType::FuncType(ty) => wasm_encoder::BlockType::FunctionType(self.types.remap(ty)), - } + fn function_index(&mut self, i: u32) -> u32 { + self.funcs.remap(i) } - - fn valty(&self, ty: wasmparser::ValType) -> wasm_encoder::ValType { - match ty { - wasmparser::ValType::I32 => wasm_encoder::ValType::I32, - wasmparser::ValType::I64 => wasm_encoder::ValType::I64, - wasmparser::ValType::F32 => wasm_encoder::ValType::F32, - wasmparser::ValType::F64 => wasm_encoder::ValType::F64, - wasmparser::ValType::V128 => wasm_encoder::ValType::V128, - wasmparser::ValType::Ref(rt) => wasm_encoder::ValType::Ref(self.refty(rt)), - } + fn memory_index(&mut self, i: u32) -> u32 { + self.memories.remap(i) } - - fn refty(&self, rt: wasmparser::RefType) -> wasm_encoder::RefType { - wasm_encoder::RefType { - nullable: rt.is_nullable(), - heap_type: self.heapty(rt.heap_type()), - } + fn global_index(&mut self, i: u32) -> u32 { + self.globals.remap(i) } - - fn heapty(&self, ht: wasmparser::HeapType) -> wasm_encoder::HeapType { - match ht { - HeapType::Concrete(idx) => { - wasm_encoder::HeapType::Concrete(self.types.remap(idx.as_module_index().unwrap())) - } - HeapType::Abstract { shared, ty } => { - let ty = ty.into(); - wasm_encoder::HeapType::Abstract { shared, ty } - } - } + fn table_index(&mut self, i: u32) -> u32 { + self.tables.remap(i) } } -// This is a helper macro to translate all `wasmparser` instructions to -// `wasm-encoder` instructions without having to list out every single -// instruction itself. -// -// The general goal of this macro is to have O(unique instruction payload) -// number of cases while also simultaneously adapting between the styles of -// wasmparser and wasm-encoder. -macro_rules! define_encode { - ($(@$p:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident)*) => { - $( - fn $visit(&mut self $(, $($arg: $argty),*)?) { - #[allow(unused_imports)] - use wasm_encoder::Instruction::*; - $( - $( - let $arg = define_encode!(map self $arg $arg); - )* - )? - let insn = define_encode!(mk $op $($($arg)*)?); - insn.encode(&mut self.buf); - } - )* - }; - - // No-payload instructions are named the same in wasmparser as they are in - // wasm-encoder - (mk $op:ident) => ($op); - - // Instructions which need "special care" to map from wasmparser to - // wasm-encoder - (mk BrTable $arg:ident) => ({ - BrTable($arg.0, $arg.1) - }); - (mk TryTable $try_table:ident) => ({ - let _ = $try_table; - unimplemented_try_table() - }); - (mk I32Const $v:ident) => (I32Const($v)); - (mk I64Const $v:ident) => (I64Const($v)); - (mk F32Const $v:ident) => (F32Const(f32::from_bits($v.bits()))); - (mk F64Const $v:ident) => (F64Const(f64::from_bits($v.bits()))); - (mk V128Const $v:ident) => (V128Const($v.i128())); - - // Catch-all for the translation of one payload argument which is typically - // represented as a tuple-enum in wasm-encoder. - (mk $op:ident $arg:ident) => ($op($arg)); - - // Catch-all of everything else where the wasmparser fields are simply - // translated to wasm-encoder fields. - (mk $op:ident $($arg:ident)*) => ($op { $($arg),* }); - - // Individual cases of mapping one argument type to another, similar to the - // `define_visit` macro above. - (map $self:ident $arg:ident memarg) => {$self.memarg($arg)}; - (map $self:ident $arg:ident ordering) => {$self.ordering($arg)}; - (map $self:ident $arg:ident blockty) => {$self.blockty($arg)}; - (map $self:ident $arg:ident hty) => {$self.heapty($arg)}; - (map $self:ident $arg:ident from_ref_type) => {$self.refty($arg)}; - (map $self:ident $arg:ident to_ref_type) => {$self.refty($arg)}; - (map $self:ident $arg:ident tag_index) => {$arg}; - (map $self:ident $arg:ident relative_depth) => {$arg}; - (map $self:ident $arg:ident function_index) => {$self.funcs.remap($arg)}; - (map $self:ident $arg:ident global_index) => {$self.globals.remap($arg)}; - (map $self:ident $arg:ident mem) => {$self.memories.remap($arg)}; - (map $self:ident $arg:ident src_mem) => {$self.memories.remap($arg)}; - (map $self:ident $arg:ident dst_mem) => {$self.memories.remap($arg)}; - (map $self:ident $arg:ident table) => {$self.tables.remap($arg)}; - (map $self:ident $arg:ident table_index) => {$self.tables.remap($arg)}; - (map $self:ident $arg:ident src_table) => {$self.tables.remap($arg)}; - (map $self:ident $arg:ident dst_table) => {$self.tables.remap($arg)}; - (map $self:ident $arg:ident type_index) => {$self.types.remap($arg)}; - (map $self:ident $arg:ident array_type_index) => {$self.types.remap($arg)}; - (map $self:ident $arg:ident array_type_index_dst) => {$self.types.remap($arg)}; - (map $self:ident $arg:ident array_type_index_src) => {$self.types.remap($arg)}; - (map $self:ident $arg:ident struct_type_index) => {$self.types.remap($arg)}; - (map $self:ident $arg:ident ty) => {$self.valty($arg)}; - (map $self:ident $arg:ident local_index) => {$arg}; - (map $self:ident $arg:ident lane) => {$arg}; - (map $self:ident $arg:ident lanes) => {$arg}; - (map $self:ident $arg:ident elem_index) => {$arg}; - (map $self:ident $arg:ident data_index) => {$arg}; - (map $self:ident $arg:ident array_elem_index) => {$arg}; - (map $self:ident $arg:ident array_data_index) => {$arg}; - (map $self:ident $arg:ident table_byte) => {$arg}; - (map $self:ident $arg:ident mem_byte) => {$arg}; - (map $self:ident $arg:ident value) => {$arg}; - (map $self:ident $arg:ident array_size) => {$arg}; - (map $self:ident $arg:ident field_index) => {$arg}; - (map $self:ident $arg:ident from_type_nullable) => {$arg}; - (map $self:ident $arg:ident to_type_nullable) => {$arg}; - (map $self:ident $arg:ident try_table) => {$arg}; - (map $self:ident $arg:ident targets) => (( - $arg.targets().map(|i| i.unwrap()).collect::>().into(), - $arg.default(), - )); -} - -fn unimplemented_try_table() -> wasm_encoder::Instruction<'static> { - unimplemented!() -} - -impl<'a> VisitOperator<'a> for Encoder { - type Output = (); - - wasmparser::for_each_operator!(define_encode); -} - // Minimal definition of a bit vector necessary for the liveness calculations // above. mod bitvec { diff --git a/fuzz/fuzz_targets/run.rs b/fuzz/fuzz_targets/run.rs index b9b196a88c..7ff50dfed2 100644 --- a/fuzz/fuzz_targets/run.rs +++ b/fuzz/fuzz_targets/run.rs @@ -79,4 +79,5 @@ run_fuzzers! { print: unstructured, roundtrip: string, text_parser: string, + reencode: unstructured, } diff --git a/fuzz/src/lib.rs b/fuzz/src/lib.rs index 32c2f8f682..7d140cb636 100644 --- a/fuzz/src/lib.rs +++ b/fuzz/src/lib.rs @@ -7,6 +7,7 @@ pub mod incremental_parse; pub mod mutate; pub mod no_traps; pub mod print; +pub mod reencode; pub mod roundtrip; pub mod roundtrip_wit; pub mod text_parser; diff --git a/fuzz/src/reencode.rs b/fuzz/src/reencode.rs new file mode 100644 index 0000000000..fef897bf31 --- /dev/null +++ b/fuzz/src/reencode.rs @@ -0,0 +1,16 @@ +use arbitrary::{Result, Unstructured}; +use wasm_encoder::reencode::{Reencode, RoundtripReencoder}; + +pub fn run(u: &mut Unstructured<'_>) -> Result<()> { + let (module1, _) = super::generate_valid_module(u, |_, _| Ok(()))?; + + let mut module2 = Default::default(); + RoundtripReencoder + .parse_core_module(&mut module2, wasmparser::Parser::new(0), &module1) + .unwrap(); + + let module2 = module2.finish(); + assert_eq!(module1, module2); + + Ok(()) +} From 2c6f12721425b79266b16e7385fb6f24d2534eed Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Tue, 25 Jun 2024 13:19:32 -0500 Subject: [PATCH 29/58] Limit component model flags to 32 (#1635) * Limit component model flags to 32 This commit is an attempt to explore the design space of WebAssembly/component-model#370. This limits, by default, the number of flags in a component model type to 32 by default. The hope of this issue is to be able to ratchet the maximum number of flags to make it easier on bindings generators to not need to work with arbitrary numbers of flags. The secondary hope is that we can ratchet flags straight to 32 instead of 64 due to it being unlikely that more than 32 flags are in use. Once this percolates there can then be a separate feature for enabling 33-64 flags. * Drop a link --- crates/wasmparser/src/features.rs | 2 + crates/wasmparser/src/validator/component.rs | 22 +++++- .../wit-component/tests/interfaces/flags.wat | 56 +++++++-------- .../wit-component/tests/interfaces/flags.wit | 69 ------------------- .../tests/interfaces/flags.wit.print | 69 ------------------- tests/local/component-model/more-flags.wast | 37 ++++++++++ tests/local/component-model/types.wast | 40 +++++++++++ tests/roundtrip.rs | 6 +- .../component-model/more-flags.wast/0.print | 3 + 9 files changed, 130 insertions(+), 174 deletions(-) create mode 100644 tests/local/component-model/more-flags.wast create mode 100644 tests/snapshots/local/component-model/more-flags.wast/0.print diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs index e73a9e9a2b..6478cf9c91 100644 --- a/crates/wasmparser/src/features.rs +++ b/crates/wasmparser/src/features.rs @@ -146,6 +146,8 @@ define_wasm_features! { pub component_model_values: COMPONENT_MODEL_VALUES(1 << 21) = false; /// Support for the nested namespaces and projects in component model names. pub component_model_nested_names: COMPONENT_MODEL_NESTED_NAMES(1 << 22) = false; + /// Support for more than 32 flags per-type in the component model. + pub component_model_more_flags: COMPONENT_MODEL_MORE_FLAGS(1 << 23) = false; } } diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index f39be44d4f..80dbd2e380 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -348,7 +348,7 @@ impl ComponentState { let id = match ty { crate::ComponentType::Defined(ty) => { - let ty = current(components).create_defined_type(ty, types, offset)?; + let ty = current(components).create_defined_type(ty, types, features, offset)?; types.push(ty).into() } crate::ComponentType::Func(ty) => { @@ -2512,6 +2512,7 @@ impl ComponentState { &self, ty: crate::ComponentDefinedType, types: &TypeList, + features: &WasmFeatures, offset: usize, ) -> Result { match ty { @@ -2529,7 +2530,7 @@ impl ComponentState { self.create_tuple_type(tys.as_ref(), types, offset) } crate::ComponentDefinedType::Flags(names) => { - self.create_flags_type(names.as_ref(), offset) + self.create_flags_type(names.as_ref(), features, offset) } crate::ComponentDefinedType::Enum(cases) => { self.create_enum_type(cases.as_ref(), offset) @@ -2681,7 +2682,12 @@ impl ComponentState { Ok(ComponentDefinedType::Tuple(TupleType { info, types })) } - fn create_flags_type(&self, names: &[&str], offset: usize) -> Result { + fn create_flags_type( + &self, + names: &[&str], + features: &WasmFeatures, + offset: usize, + ) -> Result { let mut names_set = IndexSet::default(); names_set.reserve(names.len()); @@ -2689,6 +2695,16 @@ impl ComponentState { bail!(offset, "flags must have at least one entry"); } + if names.len() > 32 && !features.component_model_more_flags() { + bail!( + offset, + "cannot have more than 32 flags; this was previously \ + accepted and if this is required for your project please \ + leave a comment on \ + https://github.com/WebAssembly/component-model/issues/370" + ); + } + for name in names { let name = to_kebab_str(name, "flag", offset)?; if !names_set.insert(name.to_owned()) { diff --git a/crates/wit-component/tests/interfaces/flags.wat b/crates/wit-component/tests/interfaces/flags.wat index 7c0aa39899..61a6ea97bc 100644 --- a/crates/wit-component/tests/interfaces/flags.wat +++ b/crates/wit-component/tests/interfaces/flags.wat @@ -15,22 +15,18 @@ (export (;9;) "flag16" (type (eq 8))) (type (;10;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) (export (;11;) "flag32" (type (eq 10))) - (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) - (export (;13;) "flag64" (type (eq 12))) - (type (;14;) (func (param "x" 1) (result 1))) - (export (;0;) "roundtrip-flag1" (func (type 14))) - (type (;15;) (func (param "x" 3) (result 3))) - (export (;1;) "roundtrip-flag2" (func (type 15))) - (type (;16;) (func (param "x" 5) (result 5))) - (export (;2;) "roundtrip-flag4" (func (type 16))) - (type (;17;) (func (param "x" 7) (result 7))) - (export (;3;) "roundtrip-flag8" (func (type 17))) - (type (;18;) (func (param "x" 9) (result 9))) - (export (;4;) "roundtrip-flag16" (func (type 18))) - (type (;19;) (func (param "x" 11) (result 11))) - (export (;5;) "roundtrip-flag32" (func (type 19))) - (type (;20;) (func (param "x" 13) (result 13))) - (export (;6;) "roundtrip-flag64" (func (type 20))) + (type (;12;) (func (param "x" 1) (result 1))) + (export (;0;) "roundtrip-flag1" (func (type 12))) + (type (;13;) (func (param "x" 3) (result 3))) + (export (;1;) "roundtrip-flag2" (func (type 13))) + (type (;14;) (func (param "x" 5) (result 5))) + (export (;2;) "roundtrip-flag4" (func (type 14))) + (type (;15;) (func (param "x" 7) (result 7))) + (export (;3;) "roundtrip-flag8" (func (type 15))) + (type (;16;) (func (param "x" 9) (result 9))) + (export (;4;) "roundtrip-flag16" (func (type 16))) + (type (;17;) (func (param "x" 11) (result 11))) + (export (;5;) "roundtrip-flag32" (func (type 17))) ) ) (export (;0;) "foo:flags/imports" (instance (type 0))) @@ -55,22 +51,18 @@ (export (;9;) "flag16" (type (eq 8))) (type (;10;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) (export (;11;) "flag32" (type (eq 10))) - (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) - (export (;13;) "flag64" (type (eq 12))) - (type (;14;) (func (param "x" 1) (result 1))) - (export (;0;) "roundtrip-flag1" (func (type 14))) - (type (;15;) (func (param "x" 3) (result 3))) - (export (;1;) "roundtrip-flag2" (func (type 15))) - (type (;16;) (func (param "x" 5) (result 5))) - (export (;2;) "roundtrip-flag4" (func (type 16))) - (type (;17;) (func (param "x" 7) (result 7))) - (export (;3;) "roundtrip-flag8" (func (type 17))) - (type (;18;) (func (param "x" 9) (result 9))) - (export (;4;) "roundtrip-flag16" (func (type 18))) - (type (;19;) (func (param "x" 11) (result 11))) - (export (;5;) "roundtrip-flag32" (func (type 19))) - (type (;20;) (func (param "x" 13) (result 13))) - (export (;6;) "roundtrip-flag64" (func (type 20))) + (type (;12;) (func (param "x" 1) (result 1))) + (export (;0;) "roundtrip-flag1" (func (type 12))) + (type (;13;) (func (param "x" 3) (result 3))) + (export (;1;) "roundtrip-flag2" (func (type 13))) + (type (;14;) (func (param "x" 5) (result 5))) + (export (;2;) "roundtrip-flag4" (func (type 14))) + (type (;15;) (func (param "x" 7) (result 7))) + (export (;3;) "roundtrip-flag8" (func (type 15))) + (type (;16;) (func (param "x" 9) (result 9))) + (export (;4;) "roundtrip-flag16" (func (type 16))) + (type (;17;) (func (param "x" 11) (result 11))) + (export (;5;) "roundtrip-flag32" (func (type 17))) ) ) (import "foo:flags/imports" (instance (;0;) (type 0))) diff --git a/crates/wit-component/tests/interfaces/flags.wit b/crates/wit-component/tests/interfaces/flags.wit index 9542f96e08..75f16cb355 100644 --- a/crates/wit-component/tests/interfaces/flags.wit +++ b/crates/wit-component/tests/interfaces/flags.wit @@ -82,73 +82,6 @@ interface imports { b31, } - flags flag64 { - b0, - b1, - b2, - b3, - b4, - b5, - b6, - b7, - b8, - b9, - b10, - b11, - b12, - b13, - b14, - b15, - b16, - b17, - b18, - b19, - b20, - b21, - b22, - b23, - b24, - b25, - b26, - b27, - b28, - b29, - b30, - b31, - b32, - b33, - b34, - b35, - b36, - b37, - b38, - b39, - b40, - b41, - b42, - b43, - b44, - b45, - b46, - b47, - b48, - b49, - b50, - b51, - b52, - b53, - b54, - b55, - b56, - b57, - b58, - b59, - b60, - b61, - b62, - b63, - } - roundtrip-flag1: func(x: flag1) -> flag1; roundtrip-flag2: func(x: flag2) -> flag2; @@ -160,8 +93,6 @@ interface imports { roundtrip-flag16: func(x: flag16) -> flag16; roundtrip-flag32: func(x: flag32) -> flag32; - - roundtrip-flag64: func(x: flag64) -> flag64; } world flags-world { diff --git a/crates/wit-component/tests/interfaces/flags.wit.print b/crates/wit-component/tests/interfaces/flags.wit.print index 9542f96e08..75f16cb355 100644 --- a/crates/wit-component/tests/interfaces/flags.wit.print +++ b/crates/wit-component/tests/interfaces/flags.wit.print @@ -82,73 +82,6 @@ interface imports { b31, } - flags flag64 { - b0, - b1, - b2, - b3, - b4, - b5, - b6, - b7, - b8, - b9, - b10, - b11, - b12, - b13, - b14, - b15, - b16, - b17, - b18, - b19, - b20, - b21, - b22, - b23, - b24, - b25, - b26, - b27, - b28, - b29, - b30, - b31, - b32, - b33, - b34, - b35, - b36, - b37, - b38, - b39, - b40, - b41, - b42, - b43, - b44, - b45, - b46, - b47, - b48, - b49, - b50, - b51, - b52, - b53, - b54, - b55, - b56, - b57, - b58, - b59, - b60, - b61, - b62, - b63, - } - roundtrip-flag1: func(x: flag1) -> flag1; roundtrip-flag2: func(x: flag2) -> flag2; @@ -160,8 +93,6 @@ interface imports { roundtrip-flag16: func(x: flag16) -> flag16; roundtrip-flag32: func(x: flag32) -> flag32; - - roundtrip-flag64: func(x: flag64) -> flag64; } world flags-world { diff --git a/tests/local/component-model/more-flags.wast b/tests/local/component-model/more-flags.wast new file mode 100644 index 0000000000..6d17e45095 --- /dev/null +++ b/tests/local/component-model/more-flags.wast @@ -0,0 +1,37 @@ +(component + (type (flags + "f1" + "f2" + "f3" + "f4" + "f5" + "f6" + "f7" + "f8" + "f9" + "f10" + "f11" + "f12" + "f13" + "f14" + "f15" + "f16" + "f17" + "f18" + "f19" + "f20" + "f21" + "f22" + "f23" + "f24" + "f25" + "f26" + "f27" + "f28" + "f29" + "f30" + "f31" + "f32" + "f33" + )) +) diff --git a/tests/local/component-model/types.wast b/tests/local/component-model/types.wast index 628ec825cf..88fbd2d376 100644 --- a/tests/local/component-model/types.wast +++ b/tests/local/component-model/types.wast @@ -323,3 +323,43 @@ )) ) ) + +(assert_invalid + (component + (type (flags + "f1" + "f2" + "f3" + "f4" + "f5" + "f6" + "f7" + "f8" + "f9" + "f10" + "f11" + "f12" + "f13" + "f14" + "f15" + "f16" + "f17" + "f18" + "f19" + "f20" + "f21" + "f22" + "f23" + "f24" + "f25" + "f26" + "f27" + "f28" + "f29" + "f30" + "f31" + "f32" + "f33" + )) + ) + "cannot have more than 32 flags") diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 61620033e9..66fdc1e49b 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -578,7 +578,8 @@ impl TestState { let mut features = WasmFeatures::all() & !WasmFeatures::SHARED_EVERYTHING_THREADS & !WasmFeatures::COMPONENT_MODEL - & !WasmFeatures::COMPONENT_MODEL_NESTED_NAMES; + & !WasmFeatures::COMPONENT_MODEL_NESTED_NAMES + & !WasmFeatures::COMPONENT_MODEL_MORE_FLAGS; for part in test.iter().filter_map(|t| t.to_str()) { match part { "testsuite" => { @@ -623,6 +624,9 @@ impl TestState { "import-extended.wast" => { features.insert(WasmFeatures::COMPONENT_MODEL_NESTED_NAMES); } + "more-flags.wast" => { + features.insert(WasmFeatures::COMPONENT_MODEL_MORE_FLAGS); + } _ => {} } } diff --git a/tests/snapshots/local/component-model/more-flags.wast/0.print b/tests/snapshots/local/component-model/more-flags.wast/0.print new file mode 100644 index 0000000000..5e23480ae2 --- /dev/null +++ b/tests/snapshots/local/component-model/more-flags.wast/0.print @@ -0,0 +1,3 @@ +(component + (type (;0;) (flags "f1" "f2" "f3" "f4" "f5" "f6" "f7" "f8" "f9" "f10" "f11" "f12" "f13" "f14" "f15" "f16" "f17" "f18" "f19" "f20" "f21" "f22" "f23" "f24" "f25" "f26" "f27" "f28" "f29" "f30" "f31" "f32" "f33")) +) From 85270cad630b5891b3c681ee8c08f410becca314 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 26 Jun 2024 12:34:12 -0500 Subject: [PATCH 30/58] Fix nightly warning (#1636) Cropped up in last night's nightly. --- crates/wasm-smith/src/core.rs | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index 096a1ef7eb..6dd32456cf 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -2730,17 +2730,6 @@ impl EntityType { } } -// A helper structure used when generating module/instance types to limit the -// amount of each kind of import created. -#[derive(Default, Clone, Copy, PartialEq)] -struct Entities { - globals: usize, - memories: usize, - tables: usize, - funcs: usize, - tags: usize, -} - /// A container for the kinds of instructions that wasm-smith is allowed to /// emit. /// From 02d291aa146fe1bf3357acbaf95f1e05e1807f13 Mon Sep 17 00:00:00 2001 From: Yuta Saito Date: Thu, 27 Jun 2024 02:40:35 +0900 Subject: [PATCH 31/58] wit-component: Allow `cabi_realloc` to be weakly defined (#1625) If a weak "default" visibility symbol is defined in a shared library, wasm-ld emits an import for the symbol for the "env" module for the case where the symbol is overridden by other modules. `cabi_realloc` is usually defined weakly by wit-bindgen'ed code, and if the code is compiled with `-fvisibility=default`, it emits an import for "env.cabi_realloc". The import leads re-exporting the symbol to the "env" module, and it conflicts with the special re-exporting logic for `cabi_realloc`. This commit fixes the duplicate re-exporting issue. --- crates/wit-component/src/linking.rs | 27 +++- .../link-weak-cabi-realloc/component.wat | 116 ++++++++++++++++++ .../component.wit.print | 5 + .../link-weak-cabi-realloc/lib-foo.wat | 14 +++ .../link-weak-cabi-realloc/lib-foo.wit | 9 ++ 5 files changed, 167 insertions(+), 4 deletions(-) create mode 100644 crates/wit-component/tests/components/link-weak-cabi-realloc/component.wat create mode 100644 crates/wit-component/tests/components/link-weak-cabi-realloc/component.wit.print create mode 100644 crates/wit-component/tests/components/link-weak-cabi-realloc/lib-foo.wat create mode 100644 crates/wit-component/tests/components/link-weak-cabi-realloc/lib-foo.wit diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs index 855fc0f3a6..a9d3ca95b8 100644 --- a/crates/wit-component/src/linking.rs +++ b/crates/wit-component/src/linking.rs @@ -1046,6 +1046,11 @@ fn find_dependencies( } } +struct EnvFunctionExports<'a> { + exports: Vec<(&'a str, &'a FunctionType, usize)>, + reexport_cabi_realloc: bool, +} + /// Analyze the specified metadata and generate a list of functions which should be re-exported as a /// `call.indirect`-based function by the main (AKA "env") module, including the offset of the library containing /// the original export. @@ -1053,7 +1058,7 @@ fn env_function_exports<'a>( metadata: &'a [Metadata<'a>], exporters: &'a IndexMap<&'a ExportKey, (&'a str, &Export)>, topo_sorted: &[usize], -) -> Result> { +) -> Result> { let function_exporters = exporters .iter() .filter_map(|(export, exporter)| { @@ -1103,7 +1108,12 @@ fn env_function_exports<'a>( seen.insert(index); } - Ok(result) + let reexport_cabi_realloc = exported.contains("cabi_realloc"); + + Ok(EnvFunctionExports { + exports: result, + reexport_cabi_realloc, + }) } /// Synthesize a module which contains trapping stub exports for the specified functions. @@ -1385,12 +1395,21 @@ impl Linker { let topo_sorted = topo_sort(metadata.len(), &dependencies)?; - let env_function_exports = env_function_exports(&metadata, &exporters, &topo_sorted)?; + let EnvFunctionExports { + exports: env_function_exports, + reexport_cabi_realloc, + } = env_function_exports(&metadata, &exporters, &topo_sorted)?; let (env_module, dl_openables, table_base) = make_env_module( &metadata, &env_function_exports, - cabi_realloc_exporter, + if reexport_cabi_realloc { + // If "env" module already reexports "cabi_realloc", we don't need to + // reexport it again. + None + } else { + cabi_realloc_exporter + }, self.stack_size.unwrap_or(DEFAULT_STACK_SIZE_BYTES), ); diff --git a/crates/wit-component/tests/components/link-weak-cabi-realloc/component.wat b/crates/wit-component/tests/components/link-weak-cabi-realloc/component.wat new file mode 100644 index 0000000000..123ccb84f3 --- /dev/null +++ b/crates/wit-component/tests/components/link-weak-cabi-realloc/component.wat @@ -0,0 +1,116 @@ +(component + (core module (;0;) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) + local.get 0 + local.get 1 + local.get 2 + local.get 3 + i32.const 1 + call_indirect (type 0) + ) + (table (;0;) 2 funcref) + (memory (;0;) 17) + (global (;0;) (mut i32) i32.const 1048576) + (global (;1;) i32 i32.const 1048592) + (global (;2;) i32 i32.const 1) + (global (;3;) (mut i32) i32.const 1048592) + (global (;4;) (mut i32) i32.const 1114112) + (export "__stack_pointer" (global 0)) + (export "foo:memory_base" (global 1)) + (export "foo:table_base" (global 2)) + (export "__heap_base" (global 3)) + (export "__heap_end" (global 4)) + (export "cabi_realloc" (func 0)) + (export "__indirect_function_table" (table 0)) + (export "memory" (memory 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core module (;1;) + (@dylink.0 + (mem-info (memory 0 4)) + ) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (type (;1;) (func (param i32))) + (type (;2;) (func (param i32 i32))) + (import "env" "cabi_realloc" (func $cabi_realloc.0 (;0;) (type 0))) + (func $cabi_realloc.1 (;1;) (type 0) (param i32 i32 i32 i32) (result i32) + i32.const -257976192 + ) + (func $foo (;2;) (type 2) (param i32 i32)) + (export "cabi_realloc" (func $cabi_realloc.1)) + (export "test:test/test#foo" (func $foo)) + ) + (core module (;2;) + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (type (;2;) (func (param i32 i32 i32 i32) (result i32))) + (import "env" "memory" (memory (;0;) 0)) + (import "env" "__indirect_function_table" (table (;0;) 0 funcref)) + (import "foo" "cabi_realloc" (func (;0;) (type 2))) + (func (;1;) (type 0)) + (start 1) + (elem (;0;) (i32.const 1) func) + (elem (;1;) (i32.const 1) func 0) + (data (;0;) (i32.const 1048576) "\00\00\00\00\00\00\10\00") + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) + ) + (core instance (;0;) (instantiate 0)) + (alias core export 0 "memory" (core memory (;0;))) + (alias core export 0 "cabi_realloc" (core func (;0;))) + (alias core export 0 "__heap_base" (core global (;0;))) + (alias core export 0 "__heap_end" (core global (;1;))) + (core instance (;1;) + (export "__heap_base" (global 0)) + (export "__heap_end" (global 1)) + ) + (core instance (;2;)) + (alias core export 0 "memory" (core memory (;1;))) + (alias core export 0 "__indirect_function_table" (core table (;0;))) + (alias core export 0 "__stack_pointer" (core global (;2;))) + (alias core export 0 "foo:memory_base" (core global (;3;))) + (alias core export 0 "foo:table_base" (core global (;4;))) + (alias core export 0 "cabi_realloc" (core func (;1;))) + (core instance (;3;) + (export "memory" (memory 1)) + (export "__indirect_function_table" (table 0)) + (export "__stack_pointer" (global 2)) + (export "__memory_base" (global 3)) + (export "__table_base" (global 4)) + (export "cabi_realloc" (func 1)) + ) + (core instance (;4;) (instantiate 1 + (with "GOT.mem" (instance 1)) + (with "GOT.func" (instance 2)) + (with "env" (instance 3)) + ) + ) + (alias core export 4 "cabi_realloc" (core func (;2;))) + (alias core export 4 "cabi_realloc" (core func (;3;))) + (core instance (;5;) (instantiate 2 + (with "env" (instance 0)) + (with "foo" (instance 4)) + ) + ) + (type (;0;) (func (param "x" string))) + (alias core export 4 "test:test/test#foo" (core func (;4;))) + (func (;0;) (type 0) (canon lift (core func 4) (memory 0) (realloc 2) string-encoding=utf8)) + (component (;0;) + (type (;0;) (func (param "x" string))) + (import "import-func-foo" (func (;0;) (type 0))) + (type (;1;) (func (param "x" string))) + (export (;1;) "foo" (func 0) (func (type 1))) + ) + (instance (;0;) (instantiate 0 + (with "import-func-foo" (func 0)) + ) + ) + (export (;1;) "test:test/test" (instance 0)) + (@producers + (processed-by "wit-component" "$CARGO_PKG_VERSION") + ) +) diff --git a/crates/wit-component/tests/components/link-weak-cabi-realloc/component.wit.print b/crates/wit-component/tests/components/link-weak-cabi-realloc/component.wit.print new file mode 100644 index 0000000000..ad829fd0b8 --- /dev/null +++ b/crates/wit-component/tests/components/link-weak-cabi-realloc/component.wit.print @@ -0,0 +1,5 @@ +package root:component; + +world root { + export test:test/test; +} diff --git a/crates/wit-component/tests/components/link-weak-cabi-realloc/lib-foo.wat b/crates/wit-component/tests/components/link-weak-cabi-realloc/lib-foo.wat new file mode 100644 index 0000000000..9367f39c5e --- /dev/null +++ b/crates/wit-component/tests/components/link-weak-cabi-realloc/lib-foo.wat @@ -0,0 +1,14 @@ +(module + (@dylink.0 + (mem-info (memory 0 4)) + ) + (type (func (param i32 i32 i32 i32) (result i32))) + (type (func (param i32))) + (import "env" "cabi_realloc" (func $cabi_realloc.0 (type 0))) + (func $cabi_realloc.1 (type 0) + i32.const 0xf09f9880 + ) + (func $foo (param i32 i32)) + (export "cabi_realloc" (func $cabi_realloc.1)) + (export "test:test/test#foo" (func $foo)) +) diff --git a/crates/wit-component/tests/components/link-weak-cabi-realloc/lib-foo.wit b/crates/wit-component/tests/components/link-weak-cabi-realloc/lib-foo.wit new file mode 100644 index 0000000000..4a088b7146 --- /dev/null +++ b/crates/wit-component/tests/components/link-weak-cabi-realloc/lib-foo.wit @@ -0,0 +1,9 @@ +package test:test; + +interface test { + foo: func(x: string); +} + +world lib-foo { + export test; +} From bee5a7c056514ab8d36bd964158e49099f61bfae Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 26 Jun 2024 16:05:41 -0500 Subject: [PATCH 32/58] Implement DWARF support in text-to-binary (#1632) * Implement DWARF support in text-to-binary This commit implements support to emit DWARF debugging information when the WebAssembly text format is converted to binary. Currently there is otherwise no means of translating a binary offset in a WebAssembly file back to the text format that it came from. This support is implemented with a few API knobs here and there as well as two new CLI flags for all the commands that support the text format as input: `-g` and `--generate-dwarf lines|full`. The original motivation of this commit is that I was curious to learn more about DWARF and it seemed like a neat little feature that wouldn't be too hard to maintain. The inspiration was bytecodealliance/wasmtime#8658 which this does not implement as-is (e.g. not custom DWARF, just wasm-text-DWARF). Otherwise though I've realized that this can be useful for development in a few situations: * Wasmtime's backtraces can now mention filenames/line numbers of the WebAssembly text format. * Validation errors can use `addr2line` to print a filename/line number of a WebAssembly text file. * Wasmtime doesn't have a native debugger nor does LLDB/GDB know about WebAssembly. Through Wasmtime's DWARF transformation this enables debugging WebAssembly text files. This commit implements knobs for "full" or "line" support in DWARF. The "line" support should be as complete as it can be (DWARF can only provide information for the code section). The "full" support is somewhat basic still but my hope is that it can be expanded in the future as needed. * Fix a compile warning --- Cargo.lock | 2 + Cargo.toml | 7 +- crates/wast/Cargo.toml | 10 + crates/wast/src/component.rs | 2 +- crates/wast/src/component/binary.rs | 17 +- crates/wast/src/component/component.rs | 3 +- crates/wast/src/core.rs | 1 + crates/wast/src/core/binary.rs | 207 +++++- crates/wast/src/core/binary/dwarf.rs | 610 ++++++++++++++++++ crates/wast/src/core/binary/dwarf_disabled.rs | 41 ++ crates/wast/src/core/expr.rs | 122 +++- crates/wast/src/core/memory.rs | 13 +- crates/wast/src/core/module.rs | 7 +- .../core/resolve/deinline_import_export.rs | 26 +- crates/wast/src/core/table.rs | 5 +- crates/wast/src/lib.rs | 5 + crates/wast/src/parser.rs | 20 + crates/wast/src/wat.rs | 5 +- crates/wat/Cargo.toml | 9 + crates/wat/src/lib.rs | 138 +++- src/addr2line.rs | 116 ++++ src/bin/wasm-tools/addr2line.rs | 97 +-- src/bin/wasm-tools/print.rs | 5 +- src/bin/wasm-tools/validate.rs | 78 ++- src/lib.rs | 59 +- tests/cli/dwarf-into-addr2line.wat | 13 + tests/cli/dwarf-into-addr2line.wat.stdout | 4 + tests/cli/dwarf-simple.wat | 11 + tests/cli/dwarf-simple.wat.stdout | 14 + tests/cli/dwarf-validate.wat | 8 + tests/cli/dwarf-validate.wat.stderr | 5 + 31 files changed, 1425 insertions(+), 235 deletions(-) create mode 100644 crates/wast/src/core/binary/dwarf.rs create mode 100644 crates/wast/src/core/binary/dwarf_disabled.rs create mode 100644 src/addr2line.rs create mode 100644 tests/cli/dwarf-into-addr2line.wat create mode 100644 tests/cli/dwarf-into-addr2line.wat.stdout create mode 100644 tests/cli/dwarf-simple.wat create mode 100644 tests/cli/dwarf-simple.wat.stdout create mode 100644 tests/cli/dwarf-validate.wat create mode 100644 tests/cli/dwarf-validate.wat.stderr diff --git a/Cargo.lock b/Cargo.lock index ee75653594..fb40676c1a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2036,9 +2036,11 @@ version = "211.0.1" dependencies = [ "anyhow", "bumpalo", + "gimli 0.29.0", "leb128", "libtest-mimic", "memchr", + "rand", "unicode-width", "wasm-encoder 0.211.1", "wasmparser 0.211.1", diff --git a/Cargo.toml b/Cargo.toml index d7d9e3d4f1..7f72e692a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -85,6 +85,7 @@ hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] ahash = { version = "0.8.11", default-features = false } termcolor = "1.2.0" indoc = "2.0.5" +gimli = "0.29.0" wasm-compose = { version = "0.211.1", path = "crates/wasm-compose" } wasm-encoder = { version = "0.211.1", path = "crates/wasm-encoder" } @@ -108,7 +109,7 @@ log = { workspace = true } clap = { workspace = true } clap_complete = { workspace = true, optional = true } tempfile = "3.2.0" -wat = { workspace = true } +wat = { workspace = true, features = ['dwarf'] } termcolor = { workspace = true } # Dependencies of `validate` @@ -156,7 +157,7 @@ wit-smith = { workspace = true, features = ["clap"], optional = true } # Dependencies of `addr2line` addr2line = { version = "0.22.0", optional = true } -gimli = { version = "0.29.0", optional = true } +gimli = { workspace = true, optional = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] is_executable = { version = "1.0.1", optional = true } @@ -200,7 +201,7 @@ default = [ ] # Each subcommand is gated behind a feature and lists the dependencies it needs -validate = ['dep:wasmparser', 'rayon'] +validate = ['dep:wasmparser', 'rayon', 'dep:addr2line', 'dep:gimli'] print = [] parse = [] smith = ['wasm-smith', 'arbitrary', 'dep:serde', 'dep:serde_derive', 'dep:serde_json'] diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index 3c7fe6b3ed..2eaed8e554 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -13,6 +13,9 @@ Customizable Rust parsers for the WebAssembly Text formats WAT and WAST """ rust-version.workspace = true +[package.metadata.docs.rs] +all-features = true + [lints] workspace = true @@ -22,12 +25,14 @@ unicode-width = "0.1.9" memchr = "2.4.1" wasm-encoder = { workspace = true } bumpalo = "3.14.0" +gimli = { workspace = true, optional = true } [dev-dependencies] anyhow = { workspace = true } libtest-mimic = { workspace = true } wasmparser = { path = "../wasmparser" } wat = { path = "../wat" } +rand = { workspace = true } [features] default = ['wasm-module'] @@ -40,6 +45,11 @@ default = ['wasm-module'] # This feature is turned on by default. wasm-module = [] +# Off-by-default feature to support emitting DWARF debugging information in +# parsed binaries pointing back to source locations in the original `*.wat` +# source. +dwarf = ["dep:gimli"] + [[test]] name = "parse-fail" harness = false diff --git a/crates/wast/src/component.rs b/crates/wast/src/component.rs index 899baaa356..427bf2d407 100644 --- a/crates/wast/src/component.rs +++ b/crates/wast/src/component.rs @@ -1,7 +1,7 @@ //! Types and support for parsing the component model text format. mod alias; -mod binary; +pub(crate) mod binary; mod component; mod custom; mod expand; diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index a55626e7a4..c2f3e0e242 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -1,5 +1,6 @@ use crate::component::*; use crate::core; +use crate::core::EncodeOptions; use crate::token::{Id, Index, NameAnnotation, Span}; use wasm_encoder::{ CanonicalFunctionSection, ComponentAliasSection, ComponentDefinedTypeEncoder, @@ -9,10 +10,10 @@ use wasm_encoder::{ NestedComponentSection, RawSection, SectionId, }; -pub fn encode(component: &Component<'_>) -> Vec { +pub fn encode(component: &Component<'_>, options: &EncodeOptions) -> Vec { match &component.kind { ComponentKind::Text(fields) => { - encode_fields(&component.id, &component.name, fields).finish() + encode_fields(&component.id, &component.name, fields, options).finish() } ComponentKind::Binary(bytes) => bytes.iter().flat_map(|b| b.iter().copied()).collect(), } @@ -23,15 +24,16 @@ fn encode_fields( component_id: &Option>, component_name: &Option>, fields: &[ComponentField<'_>], + options: &EncodeOptions, ) -> wasm_encoder::Component { let mut e = Encoder::default(); for field in fields { match field { - ComponentField::CoreModule(m) => e.encode_core_module(m), + ComponentField::CoreModule(m) => e.encode_core_module(m, options), ComponentField::CoreInstance(i) => e.encode_core_instance(i), ComponentField::CoreType(t) => e.encode_core_type(t), - ComponentField::Component(c) => e.encode_component(c), + ComponentField::Component(c) => e.encode_component(c, options), ComponentField::Instance(i) => e.encode_instance(i), ComponentField::Alias(a) => e.encode_alias(a), ComponentField::Type(t) => e.encode_type(t), @@ -191,7 +193,7 @@ impl<'a> Encoder<'a> { }) } - fn encode_core_module(&mut self, module: &CoreModule<'a>) { + fn encode_core_module(&mut self, module: &CoreModule<'a>, options: &EncodeOptions) { // Flush any in-progress section before encoding the module self.flush(None); @@ -202,7 +204,7 @@ impl<'a> Encoder<'a> { CoreModuleKind::Import { .. } => unreachable!("should be expanded already"), CoreModuleKind::Inline { fields } => { // TODO: replace this with a wasm-encoder based encoding (should return `wasm_encoder::Module`) - let data = crate::core::binary::encode(&module.id, &module.name, fields); + let data = crate::core::binary::encode(&module.id, &module.name, fields, options); self.component.section(&RawSection { id: ComponentSectionId::CoreModule.into(), data: &data, @@ -238,7 +240,7 @@ impl<'a> Encoder<'a> { self.flush(Some(self.core_types.id())); } - fn encode_component(&mut self, component: &NestedComponent<'a>) { + fn encode_component(&mut self, component: &NestedComponent<'a>, options: &EncodeOptions) { self.component_names .push(get_name(&component.id, &component.name)); // Flush any in-progress section before encoding the component @@ -252,6 +254,7 @@ impl<'a> Encoder<'a> { &component.id, &component.name, fields, + options, ))); } } diff --git a/crates/wast/src/component/component.rs b/crates/wast/src/component/component.rs index cc9171b505..92814db0e7 100644 --- a/crates/wast/src/component/component.rs +++ b/crates/wast/src/component/component.rs @@ -86,8 +86,7 @@ impl<'a> Component<'a> { /// This function can return an error for name resolution errors and other /// expansion-related errors. pub fn encode(&mut self) -> std::result::Result, crate::Error> { - self.resolve()?; - Ok(crate::component::binary::encode(self)) + crate::core::EncodeOptions::default().encode_component(self) } pub(crate) fn validate(&self, parser: Parser<'_>) -> Result<()> { diff --git a/crates/wast/src/core.rs b/crates/wast/src/core.rs index 785a081853..8b75aba932 100644 --- a/crates/wast/src/core.rs +++ b/crates/wast/src/core.rs @@ -12,6 +12,7 @@ mod table; mod tag; mod types; mod wast; +pub use self::binary::{EncodeOptions, GenerateDwarf}; pub use self::custom::*; pub use self::export::*; pub use self::expr::*; diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs index 8899c81d04..8ce1b40619 100644 --- a/crates/wast/src/core/binary.rs +++ b/crates/wast/src/core/binary.rs @@ -1,11 +1,105 @@ +use crate::component::Component; use crate::core::*; use crate::encode::Encode; use crate::token::*; +use crate::Wat; +use std::marker; +#[cfg(feature = "dwarf")] +use std::path::Path; + +/// Options that can be specified when encoding a component or a module to +/// customize what the final binary looks like. +/// +/// Methods such as [`Module::encode`], [`Wat::encode`], and +/// [`Component::encode`] will use the default options. +#[derive(Default)] +pub struct EncodeOptions<'a> { + #[cfg(feature = "dwarf")] + dwarf_info: Option<(&'a Path, &'a str, GenerateDwarf)>, + + _marker: marker::PhantomData<&'a str>, +} + +#[cfg(feature = "dwarf")] +mod dwarf; + +#[cfg(not(feature = "dwarf"))] +mod dwarf_disabled; +#[cfg(not(feature = "dwarf"))] +use self::dwarf_disabled as dwarf; + +/// Configuration of how DWARF debugging information may be generated. +#[derive(Copy, Clone, Debug)] +#[non_exhaustive] +pub enum GenerateDwarf { + /// Only generate line tables to map binary offsets back to source + /// locations. + Lines, + + /// Generate full debugging information for both line numbers and + /// variables/locals/operands. + Full, +} + +impl<'a> EncodeOptions<'a> { + /// Creates a new set of default encoding options. + pub fn new() -> EncodeOptions<'a> { + EncodeOptions::default() + } + + /// Enables emission of DWARF debugging information in the final binary. + /// + /// This method will use the `file` specified as the source file for the + /// `*.wat` file whose `contents` must also be supplied here. These are + /// used to calculate filenames/line numbers and are referenced from the + /// generated DWARF. + #[cfg(feature = "dwarf")] + pub fn dwarf(&mut self, file: &'a Path, contents: &'a str, style: GenerateDwarf) -> &mut Self { + self.dwarf_info = Some((file, contents, style)); + self + } + + /// Encodes the given [`Module`] with these options. + /// + /// For more information see [`Module::encode`]. + pub fn encode_module( + &self, + module: &mut Module<'_>, + ) -> std::result::Result, crate::Error> { + module.resolve()?; + Ok(match &module.kind { + ModuleKind::Text(fields) => encode(&module.id, &module.name, fields, self), + ModuleKind::Binary(blobs) => blobs.iter().flat_map(|b| b.iter().cloned()).collect(), + }) + } + + /// Encodes the given [`Component`] with these options. + /// + /// For more information see [`Component::encode`]. + pub fn encode_component( + &self, + component: &mut Component<'_>, + ) -> std::result::Result, crate::Error> { + component.resolve()?; + Ok(crate::component::binary::encode(component, self)) + } + + /// Encodes the given [`Wat`] with these options. + /// + /// For more information see [`Wat::encode`]. + pub fn encode_wat(&self, wat: &mut Wat<'_>) -> std::result::Result, crate::Error> { + match wat { + Wat::Module(m) => self.encode_module(m), + Wat::Component(c) => self.encode_component(c), + } + } +} -pub fn encode( +pub(crate) fn encode( module_id: &Option>, module_name: &Option>, fields: &[ModuleField<'_>], + opts: &EncodeOptions, ) -> Vec { use CustomPlace::*; use CustomPlaceAnchor::*; @@ -69,14 +163,27 @@ pub fn encode( if needs_data_count(&funcs) { e.section(12, &data.len()); } - e.code_section(&funcs, &imports); - e.section_list(11, Data, &data); + // Prepare to and emit the code section. This is where DWARF may optionally + // be emitted depending on configuration settings. Note that `code_section` + // will internally emit the branch hints section if necessary. let names = find_names(module_id, module_name, fields); + let num_import_funcs = imports + .iter() + .filter(|i| matches!(i.item.kind, ItemKind::Func(..))) + .count() as u32; + let mut dwarf = dwarf::Dwarf::new(num_import_funcs, opts, &names, &types); + e.code_section(&funcs, num_import_funcs, dwarf.as_mut()); + + e.section_list(11, Data, &data); + if !names.is_empty() { e.section(0, &("name", names)); } e.custom_sections(AfterLast); + if let Some(dwarf) = &mut dwarf { + dwarf.emit(&mut e); + } return e.wasm; @@ -109,11 +216,21 @@ impl Encoder<'_> { fn custom_sections(&mut self, place: CustomPlace) { for entry in self.customs.iter() { if entry.place() == place { - self.section(0, &(entry.name(), entry)); + let mut data = Vec::new(); + entry.encode(&mut data); + self.custom_section(entry.name(), &data); } } } + fn custom_section(&mut self, name: &str, data: &[u8]) { + self.tmp.truncate(0); + name.encode(&mut self.tmp); + self.tmp.extend_from_slice(data); + self.wasm.push(0); + self.tmp.encode(&mut self.wasm); + } + fn section_list(&mut self, id: u8, anchor: CustomPlaceAnchor, list: &[impl Encode]) { self.custom_sections(CustomPlace::Before(anchor)); if !list.is_empty() { @@ -130,7 +247,17 @@ impl Encoder<'_> { /// each instruction and we save its offset. If needed, we use this /// information to build the branch hint section and insert it before the /// code section. - fn code_section<'a>(&'a mut self, list: &[&'a Func<'_>], imports: &[&Import<'_>]) { + /// + /// The `list` provided is the list of functions that are emitted into the + /// code section. The `func_index` provided is the initial index of defined + /// functions, so it's the count of imported functions. The `dwarf` field is + /// optionally used to track debugging information. + fn code_section<'a>( + &'a mut self, + list: &[&'a Func<'_>], + mut func_index: u32, + mut dwarf: Option<&mut dwarf::Dwarf>, + ) { self.custom_sections(CustomPlace::Before(CustomPlaceAnchor::Code)); if !list.is_empty() { @@ -138,12 +265,8 @@ impl Encoder<'_> { let mut code_section = Vec::new(); list.len().encode(&mut code_section); - let mut func_index = imports - .iter() - .filter(|i| matches!(i.item.kind, ItemKind::Func(..))) - .count() as u32; for func in list.iter() { - let hints = func.encode(&mut code_section); + let hints = func.encode(&mut code_section, dwarf.as_deref_mut()); if !hints.is_empty() { branch_hints.push(FunctionBranchHints { func_index, hints }); } @@ -159,6 +282,10 @@ impl Encoder<'_> { // Finally, insert the Code section from the tmp buffer self.wasm.push(10); code_section.encode(&mut self.wasm); + + if let Some(dwarf) = &mut dwarf { + dwarf.set_code_section_size(code_section.len()); + } } self.custom_sections(CustomPlace::After(CustomPlaceAnchor::Code)); } @@ -496,7 +623,7 @@ impl Encode for Table<'_> { e.push(0x40); e.push(0x00); ty.encode(e); - init_expr.encode(e, 0); + init_expr.encode(e, None); } _ => panic!("TableKind should be normal during encoding"), } @@ -519,7 +646,7 @@ impl Encode for Global<'_> { self.ty.encode(e); match &self.kind { GlobalKind::Inline(expr) => { - let _hints = expr.encode(e, 0); + let _hints = expr.encode(e, None); } _ => panic!("GlobalKind should be inline during encoding"), } @@ -557,7 +684,7 @@ impl Encode for Elem<'_> { ElemPayload::Indices(_), ) => { e.push(0x00); - offset.encode(e, 0); + offset.encode(e, None); } (ElemKind::Passive, ElemPayload::Indices(_)) => { e.push(0x01); // flags @@ -566,7 +693,7 @@ impl Encode for Elem<'_> { (ElemKind::Active { table, offset }, ElemPayload::Indices(_)) => { e.push(0x02); // flags table.encode(e); - offset.encode(e, 0); + offset.encode(e, None); e.push(0x00); // extern_kind } (ElemKind::Declared, ElemPayload::Indices(_)) => { @@ -592,7 +719,7 @@ impl Encode for Elem<'_> { }, ) => { e.push(0x04); - offset.encode(e, 0); + offset.encode(e, None); } (ElemKind::Passive, ElemPayload::Exprs { ty, .. }) => { e.push(0x05); @@ -601,7 +728,7 @@ impl Encode for Elem<'_> { (ElemKind::Active { table, offset }, ElemPayload::Exprs { ty, .. }) => { e.push(0x06); table.encode(e); - offset.encode(e, 0); + offset.encode(e, None); ty.encode(e); } (ElemKind::Declared, ElemPayload::Exprs { ty, .. }) => { @@ -621,7 +748,7 @@ impl Encode for ElemPayload<'_> { ElemPayload::Exprs { exprs, ty: _ } => { exprs.len().encode(e); for expr in exprs { - expr.encode(e, 0); + expr.encode(e, None); } } } @@ -637,12 +764,12 @@ impl Encode for Data<'_> { offset, } => { e.push(0x00); - offset.encode(e, 0); + offset.encode(e, None); } DataKind::Active { memory, offset } => { e.push(0x02); memory.encode(e); - offset.encode(e, 0); + offset.encode(e, None); } } self.data.iter().map(|l| l.len()).sum::().encode(e); @@ -655,21 +782,36 @@ impl Encode for Data<'_> { impl Func<'_> { /// Encodes the function into `e` while returning all branch hints with /// known relative offsets after encoding. - fn encode(&self, e: &mut Vec) -> Vec { + /// + /// The `dwarf` field is optional and used to track debugging information + /// for each instruction. + fn encode(&self, e: &mut Vec, mut dwarf: Option<&mut dwarf::Dwarf>) -> Vec { assert!(self.exports.names.is_empty()); let (expr, locals) = match &self.kind { FuncKind::Inline { expression, locals } => (expression, locals), _ => panic!("should only have inline functions in emission"), }; + if let Some(dwarf) = &mut dwarf { + let index = match self.ty.index.as_ref().unwrap() { + Index::Num(n, _) => *n, + _ => unreachable!(), + }; + dwarf.start_func(self.span, index, locals); + } + // Encode the function into a temporary vector because functions are // prefixed with their length. The temporary vector, when encoded, // encodes its length first then the body. let mut tmp = Vec::new(); locals.encode(&mut tmp); - let branch_hints = expr.encode(&mut tmp, 0); + let branch_hints = expr.encode(&mut tmp, dwarf.as_deref_mut()); tmp.encode(e); + if let Some(dwarf) = &mut dwarf { + dwarf.end_func(tmp.len(), e.len()); + } + branch_hints } } @@ -690,20 +832,35 @@ impl Encode for Box<[Local<'_>]> { } } -// Encode the expression and store the offset from the beginning -// for each instruction. impl Expression<'_> { - fn encode(&self, e: &mut Vec, relative_start: usize) -> Vec { + /// Encodes this expression into `e` and optionally tracks debugging + /// information for each instruction in `dwarf`. + /// + /// Returns all branch hints, if any, found while parsing this function. + fn encode(&self, e: &mut Vec, mut dwarf: Option<&mut dwarf::Dwarf>) -> Vec { let mut hints = Vec::with_capacity(self.branch_hints.len()); let mut next_hint = self.branch_hints.iter().peekable(); for (i, instr) in self.instrs.iter().enumerate() { + // Branch hints are stored in order of increasing `instr_index` so + // check to see if the next branch hint matches this instruction's + // index. if let Some(hint) = next_hint.next_if(|h| h.instr_index == i) { hints.push(BranchHint { - branch_func_offset: u32::try_from(e.len() - relative_start).unwrap(), + branch_func_offset: u32::try_from(e.len()).unwrap(), branch_hint_value: hint.value, }); } + + // If DWARF is enabled then track this instruction's binary offset + // and source location. + if let Some(dwarf) = &mut dwarf { + if let Some(span) = self.instr_spans.as_ref().map(|s| s[i]) { + dwarf.instr(e.len(), span); + } + } + + // Finally emit the instruction and move to the next. instr.encode(e); } e.push(0x0b); diff --git a/crates/wast/src/core/binary/dwarf.rs b/crates/wast/src/core/binary/dwarf.rs new file mode 100644 index 0000000000..ae9c430629 --- /dev/null +++ b/crates/wast/src/core/binary/dwarf.rs @@ -0,0 +1,610 @@ +//! Implementation of emitting DWARF debugging information for `*.wat` files. +//! +//! This is intended to be relatively simple but the goal is to enable emission +//! of DWARF sections which point back to the original `*.wat` file itself. This +//! enables debuggers like LLDB to debug `*.wat` files without necessarily +//! built-in knowledge of WebAssembly itself. +//! +//! Overall I was curious on weekend and decided to implement this. It's an +//! off-by-default crate feature and an off-by-default runtime feature of this +//! crate. Hopefully doesn't carry too much complexity with it while still being +//! easy/fun to play around with. + +use crate::core::binary::{EncodeOptions, Encoder, GenerateDwarf, Names, RecOrType}; +use crate::core::{Local, TypeDef, ValType}; +use crate::token::Span; +use gimli::write::{ + self, Address, AttributeValue, DwarfUnit, Expression, FileId, LineProgram, LineString, + Sections, UnitEntryId, Writer, +}; +use gimli::{Encoding, Format, LineEncoding, LittleEndian}; +use std::cmp::Ordering; +use std::collections::HashMap; +use std::path::Path; + +pub struct Dwarf<'a> { + // Metadata configured at `Dwarf` creation time + func_names: HashMap, + func_imports: u32, + contents: &'a str, + file_id: FileId, + style: GenerateDwarf, + types: &'a [RecOrType<'a>], + + dwarf: DwarfUnit, + + // Next function index when `start_func` is called + next_func: u32, + + // Current `DW_TAG_subprogram` that's being built as part of `start_func`. + cur_subprogram: Option, + cur_subprogram_instrs: usize, + + // Code-section-relative offset of the start of every function. Filled in + // as part of `end_func`. + sym_offsets: Vec, + + // Metadata tracking what the line/column information was at the specified + // last offset. + line: u64, + column: u64, + last_offset: usize, + + i32_ty: Option, + i64_ty: Option, + f32_ty: Option, + f64_ty: Option, +} + +impl<'a> Dwarf<'a> { + /// Creates a new `Dwarf` from the specified configuration. + /// + /// * `func_imports` - the number of imported functions in this module, or + /// the index of the first defined function. + /// * `opts` - encoding options, namely where whether DWARF is to be emitted + /// is configured. + /// * `names` - the `name` custom section for this module, used to name + /// functions in DWARF. + pub fn new( + func_imports: u32, + opts: &EncodeOptions<'a>, + names: &Names<'a>, + types: &'a [RecOrType<'a>], + ) -> Option> { + // This is a load-bearing `?` which notably short-circuits all DWARF + // machinery entirely if this was not enabled at runtime. + let (file, contents, style) = opts.dwarf_info?; + let file = file.to_str()?; + + // Configure some initial `gimli::write` context. + let encoding = Encoding { + address_size: 4, + format: Format::Dwarf32, + version: 5, + }; + let mut dwarf = DwarfUnit::new(encoding); + let (comp_dir, comp_file) = match ( + Path::new(file).parent().and_then(|s| s.to_str()), + Path::new(file).file_name().and_then(|s| s.to_str()), + ) { + (Some(parent), Some(file_name)) if !parent.is_empty() => (parent, file_name), + _ => (".", file), + }; + let comp_dir_ref = dwarf.strings.add(comp_dir); + let comp_file_ref = dwarf.strings.add(comp_file); + dwarf.unit.line_program = LineProgram::new( + encoding, + LineEncoding::default(), + LineString::StringRef(comp_dir_ref), + LineString::StringRef(comp_file_ref), + None, + ); + let dir_id = dwarf.unit.line_program.default_directory(); + let file_id = + dwarf + .unit + .line_program + .add_file(LineString::StringRef(comp_file_ref), dir_id, None); + + // Configure a single compilation unit which encompasses the entire code + // section. The code section isn't fully known at this point so only a + // "low pc" is emitted here. + let root = dwarf.unit.root(); + let cu = dwarf.unit.get_mut(root); + cu.set( + gimli::DW_AT_producer, + AttributeValue::String(format!("wast {}", env!("CARGO_PKG_VERSION")).into_bytes()), + ); + cu.set( + gimli::DW_AT_language, + // Technically this should be something like wasm or wat but that + // doesn't exist so fill in something here. + AttributeValue::Language(gimli::DW_LANG_C), + ); + cu.set(gimli::DW_AT_name, AttributeValue::StringRef(comp_file_ref)); + cu.set( + gimli::DW_AT_comp_dir, + AttributeValue::StringRef(comp_dir_ref), + ); + cu.set(gimli::DW_AT_low_pc, AttributeValue::Data4(0)); + + // Build a lookup table for defined function index to its name. + let mut func_names = HashMap::new(); + for (idx, name) in names.funcs.iter() { + func_names.insert(*idx, *name); + } + + // Offsets pointing to newlines are considered internally as the 0th + // column of the next line, so handle the case that the contents start + // with a newline. + let (line, column) = if contents.starts_with("\n") { + (2, 0) + } else { + (1, 1) + }; + Some(Dwarf { + dwarf, + style, + next_func: func_imports, + func_imports, + sym_offsets: Vec::new(), + contents, + line, + column, + last_offset: 0, + file_id, + cur_subprogram: None, + cur_subprogram_instrs: 0, + func_names, + i32_ty: None, + i64_ty: None, + f32_ty: None, + f64_ty: None, + types, + }) + } + + /// Start emitting a new function defined at `span`. + /// + /// This will start a new line program for this function and additionally + /// configure a new `DW_TAG_subprogram` for this new function. + pub fn start_func(&mut self, span: Span, ty: u32, locals: &[Local<'_>]) { + self.change_linecol(span); + let addr = Address::Symbol { + symbol: (self.next_func - self.func_imports) as usize, + addend: 0, + }; + self.dwarf.unit.line_program.begin_sequence(Some(addr)); + + let root = self.dwarf.unit.root(); + let subprogram = self.dwarf.unit.add(root, gimli::DW_TAG_subprogram); + let entry = self.dwarf.unit.get_mut(subprogram); + let fallback = format!("wasm-function[{}]", self.next_func); + let func_name = self + .func_names + .get(&self.next_func) + .copied() + .unwrap_or(&fallback); + entry.set(gimli::DW_AT_name, AttributeValue::String(func_name.into())); + entry.set( + gimli::DW_AT_decl_file, + AttributeValue::FileIndex(Some(self.file_id)), + ); + entry.set(gimli::DW_AT_decl_line, AttributeValue::Udata(self.line)); + entry.set(gimli::DW_AT_decl_column, AttributeValue::Udata(self.column)); + entry.set(gimli::DW_AT_external, AttributeValue::FlagPresent); + entry.set(gimli::DW_AT_low_pc, AttributeValue::Address(addr)); + + if let GenerateDwarf::Full = self.style { + self.add_func_params_and_locals(subprogram, ty, locals); + } + + self.cur_subprogram = Some(subprogram); + self.cur_subprogram_instrs = 0; + self.next_func += 1; + } + + /// Adds `DW_TAG_formal_parameter` and `DW_TAG_variable` for all locals + /// (which are both params and function-defined locals). + /// + /// This is pretty simple in that the expression for the location of + /// these variables is constant, it's just "it's the local", and it spans + /// the entire function. + fn add_func_params_and_locals( + &mut self, + subprogram: UnitEntryId, + ty: u32, + locals: &[Local<'_>], + ) { + // Iterate through `self.types` which is what was encoded into the + // module and find the function type which gives access to the + // parameters which gives access to their types. + let ty = self + .types + .iter() + .flat_map(|t| match t { + RecOrType::Type(t) => std::slice::from_ref(*t), + RecOrType::Rec(r) => &r.types, + }) + .nth(ty as usize); + let ty = match ty.map(|t| &t.def) { + Some(TypeDef::Func(ty)) => ty, + _ => return, + }; + + let mut local_idx = 0; + for (_, _, ty) in ty.params.iter() { + self.local(local_idx, subprogram, gimli::DW_TAG_formal_parameter, ty); + local_idx += 1; + } + + for local in locals { + self.local(local_idx, subprogram, gimli::DW_TAG_variable, &local.ty); + local_idx += 1; + } + } + + /// Attempts to define a local variable within `subprogram` with the `ty` + /// given. + /// + /// This does nothing if `ty` can't be represented in DWARF. + fn local(&mut self, local: u32, subprogram: UnitEntryId, tag: gimli::DwTag, ty: &ValType<'_>) { + let ty = match self.val_type_to_dwarf_type(ty) { + Some(ty) => ty, + None => return, + }; + + let param = self.dwarf.unit.add(subprogram, tag); + let entry = self.dwarf.unit.get_mut(param); + entry.set( + gimli::DW_AT_name, + AttributeValue::String(format!("local{local}").into()), + ); + + let mut loc = Expression::new(); + loc.op_wasm_local(local); + loc.op(gimli::DW_OP_stack_value); + entry.set(gimli::DW_AT_location, AttributeValue::Exprloc(loc)); + entry.set(gimli::DW_AT_type, AttributeValue::UnitRef(ty)); + } + + fn val_type_to_dwarf_type(&mut self, ty: &ValType<'_>) -> Option { + match ty { + ValType::I32 => Some(self.i32_ty()), + ValType::I64 => Some(self.i64_ty()), + ValType::F32 => Some(self.f32_ty()), + ValType::F64 => Some(self.f64_ty()), + // TODO: make a C union of sorts or something like that to + // represent v128 as an array-of-lanes or u128 or something like + // that. + ValType::V128 => None, + // Not much that can be done about reference types without actually + // knowing what the engine does, this probably needs an addition to + // DWARF itself to represent this. + ValType::Ref(_) => None, + } + } + + /// Emit an instruction which starts at `offset` and is defined at `span`. + /// + /// Note that `offset` is code-section-relative. + pub fn instr(&mut self, offset: usize, span: Span) { + self.change_linecol(span); + let offset = u64::try_from(offset).unwrap(); + + let mut changed = false; + let row = self.dwarf.unit.line_program.row(); + set(&mut row.address_offset, offset, &mut changed); + set(&mut row.line, self.line, &mut changed); + set(&mut row.column, self.column, &mut changed); + set(&mut row.file, self.file_id, &mut changed); + set(&mut row.is_statement, true, &mut changed); + set( + &mut row.prologue_end, + self.cur_subprogram_instrs == 0, + &mut changed, + ); + + if changed { + self.dwarf.unit.line_program.generate_row(); + } + self.cur_subprogram_instrs += 1; + + fn set(slot: &mut T, val: T, changed: &mut bool) { + if *slot != val { + *slot = val; + *changed = true; + } + } + } + + /// Change `self.line` and `self.column` to be appropriate for the offset + /// in `span`. + /// + /// This will incrementally move from `self.last_offset` to `span.offset()` + /// and update line/column information as we go. It's assumed that this is + /// more efficient than a precalculate-all-the-positions-for-each-byte + /// approach since that would require a great deal of memory to store a + /// line/col for all bytes in the input string. It's also assumed that most + /// `span` adjustments are minor as it's between instructions in a function + /// which are frequently close together. Whether or not this assumption + /// pans out is yet to be seen. + fn change_linecol(&mut self, span: Span) { + let offset = span.offset(); + + loop { + match self.last_offset.cmp(&offset) { + Ordering::Less => { + let haystack = self.contents[self.last_offset + 1..].as_bytes(); + let next_newline = match memchr::memchr_iter(b'\n', haystack).next() { + Some(pos) => pos + self.last_offset + 1, + None => break, + }; + if next_newline > offset { + break; + } else { + self.line += 1; + self.column = 0; + self.last_offset = next_newline; + } + } + Ordering::Greater => { + let haystack = self.contents[..self.last_offset].as_bytes(); + match memchr::memchr_iter(b'\n', haystack).next_back() { + Some(prev_newline) => { + if self.column == 0 { + self.line -= 1; + } + self.column = 0; + self.last_offset = prev_newline; + } + None => { + self.line = 1; + self.column = 1; + self.last_offset = 0; + } + } + } + Ordering::Equal => break, + } + } + + match self.last_offset.cmp(&offset) { + Ordering::Less => { + self.column += (offset - self.last_offset) as u64; + } + Ordering::Greater => { + self.column -= (self.last_offset - offset) as u64; + } + Ordering::Equal => {} + } + self.last_offset = offset; + } + + /// Completes emission of the latest function. + /// + /// The latest function took `func_size` bytes to encode and the current end + /// of the code section, after the function was appended, is + /// `code_section_end`. + pub fn end_func(&mut self, func_size: usize, code_section_end: usize) { + // Add a final row corresponding to the final `end` instruction in the + // function to ensure there's something for all bytecodes. + let row = self.dwarf.unit.line_program.row(); + row.address_offset = (func_size - 1) as u64; + self.dwarf.unit.line_program.generate_row(); + + // This function's symbol is relative to the start of the function + // itself. Functions are encoded as a leb-size-of-function then the + // function itself, so to account for the size of the + // leb-size-of-function we calculate the function start as the current + // end of the code section minus the size of the function's bytes. + self.sym_offsets.push(code_section_end - func_size); + + // The line program is relative to the start address, so only the + // function's size is used here. + self.dwarf + .unit + .line_program + .end_sequence(u64::try_from(func_size).unwrap()); + + // The high PC value here is relative to `DW_AT_low_pc`, so it's the + // size of the function. + let entry = self.dwarf.unit.get_mut(self.cur_subprogram.take().unwrap()); + entry.set( + gimli::DW_AT_high_pc, + AttributeValue::Data4(func_size as u32), + ); + } + + pub fn set_code_section_size(&mut self, size: usize) { + let root = self.dwarf.unit.root(); + let entry = self.dwarf.unit.get_mut(root); + entry.set(gimli::DW_AT_high_pc, AttributeValue::Data4(size as u32)); + } + + pub fn emit(&mut self, dst: &mut Encoder<'_>) { + let mut sections = Sections::new(DwarfWriter { + sym_offsets: &self.sym_offsets, + bytes: Vec::new(), + }); + self.dwarf.write(&mut sections).unwrap(); + + sections + .for_each(|id, writer| { + if !writer.bytes.is_empty() { + dst.custom_section(id.name(), &writer.bytes); + } + Ok::<_, std::convert::Infallible>(()) + }) + .unwrap(); + } + + fn i32_ty(&mut self) -> UnitEntryId { + if self.i32_ty.is_none() { + self.i32_ty = Some(self.mk_primitive("i32", 4, gimli::DW_ATE_signed)); + } + self.i32_ty.unwrap() + } + + fn i64_ty(&mut self) -> UnitEntryId { + if self.i64_ty.is_none() { + self.i64_ty = Some(self.mk_primitive("i64", 8, gimli::DW_ATE_signed)); + } + self.i64_ty.unwrap() + } + + fn f32_ty(&mut self) -> UnitEntryId { + if self.f32_ty.is_none() { + self.f32_ty = Some(self.mk_primitive("f32", 4, gimli::DW_ATE_float)); + } + self.f32_ty.unwrap() + } + + fn f64_ty(&mut self) -> UnitEntryId { + if self.f64_ty.is_none() { + self.f64_ty = Some(self.mk_primitive("f64", 8, gimli::DW_ATE_float)); + } + self.f64_ty.unwrap() + } + + fn mk_primitive(&mut self, name: &str, byte_size: u8, encoding: gimli::DwAte) -> UnitEntryId { + let name = self.dwarf.strings.add(name); + let root = self.dwarf.unit.root(); + let ty = self.dwarf.unit.add(root, gimli::DW_TAG_base_type); + let entry = self.dwarf.unit.get_mut(ty); + entry.set(gimli::DW_AT_name, AttributeValue::StringRef(name)); + entry.set(gimli::DW_AT_byte_size, AttributeValue::Data1(byte_size)); + entry.set(gimli::DW_AT_encoding, AttributeValue::Encoding(encoding)); + ty + } +} + +#[derive(Clone)] +struct DwarfWriter<'a> { + sym_offsets: &'a [usize], + bytes: Vec, +} + +impl Writer for DwarfWriter<'_> { + type Endian = LittleEndian; + + fn endian(&self) -> Self::Endian { + LittleEndian + } + fn len(&self) -> usize { + self.bytes.len() + } + fn write(&mut self, bytes: &[u8]) -> write::Result<()> { + self.bytes.extend_from_slice(bytes); + Ok(()) + } + fn write_at(&mut self, offset: usize, bytes: &[u8]) -> write::Result<()> { + self.bytes[offset..][..bytes.len()].copy_from_slice(bytes); + Ok(()) + } + fn write_address(&mut self, address: Address, size: u8) -> write::Result<()> { + match address { + Address::Constant(val) => self.write_udata(val, size), + Address::Symbol { symbol, addend } => { + assert_eq!(addend, 0); + let offset = self.sym_offsets[symbol]; + self.write_udata(offset as u64, size) + } + } + } +} + +#[cfg(test)] +mod tests { + use super::{Dwarf, EncodeOptions, GenerateDwarf}; + use crate::token::Span; + use rand::rngs::SmallRng; + use rand::{Rng, SeedableRng}; + + fn linecol_test(contents: &str) { + let mut dwarf = Dwarf::new( + 0, + EncodeOptions::default().dwarf("foo.wat".as_ref(), contents, GenerateDwarf::Lines), + &Default::default(), + &[], + ) + .unwrap(); + + // Print some debugging information in case someone's debugging this + // test + let mut offset = 0; + for (i, line) in contents.lines().enumerate() { + println!( + "line {:2} is at {:2} .. {:2}", + i + 1, + offset, + offset + line.len() + ); + offset += line.len() + 1; + } + println!(""); + + // Precalculate (line, col) for all characters, assumed to all be one + // byte here. + let mut precalculated_linecols = Vec::new(); + let mut line = 1; + let mut col = 1; + for c in contents.chars() { + if c == '\n' { + line += 1; + col = 0; + } + precalculated_linecols.push((line, col)); + col += 1; + } + + // Traverse randomly throughout this string and assert that the + // incremental update matches the precalculated position. + let mut rand = SmallRng::seed_from_u64(102); + for _ in 0..1000 { + let pos = rand.gen_range(0..contents.len()); + dwarf.change_linecol(Span::from_offset(pos)); + let (line, col) = precalculated_linecols[pos]; + + assert_eq!(dwarf.line, line, "line mismatch"); + assert_eq!(dwarf.column, col, "column mismatch"); + } + } + + #[test] + fn linecol_simple() { + linecol_test( + "a + + b + c (; ... ;) + d + e + + + f + + fg", + ); + } + + #[test] + fn linecol_empty() { + linecol_test("x"); + } + + #[test] + fn linecol_start_newline() { + linecol_test("\nx ab\nyyy \ncc"); + } + + #[test] + fn linecol_lots_of_newlines() { + linecol_test("\n\n\n\n"); + } + + #[test] + fn linecol_interspersed() { + linecol_test("\na\nb\nc\n"); + } +} diff --git a/crates/wast/src/core/binary/dwarf_disabled.rs b/crates/wast/src/core/binary/dwarf_disabled.rs new file mode 100644 index 0000000000..214b9fb5ae --- /dev/null +++ b/crates/wast/src/core/binary/dwarf_disabled.rs @@ -0,0 +1,41 @@ +//! Dummy version of dwarf emission that does nothing when the compile-time +//! feature is disabled. + +use crate::core::binary::{EncodeOptions, Encoder, Names, RecOrType}; +use crate::core::Local; +use crate::token::Span; + +pub struct Dwarf<'a> { + uninhabited: &'a std::convert::Infallible, +} + +impl<'a> Dwarf<'a> { + pub fn new( + _func_imports: u32, + _opts: &EncodeOptions<'a>, + _names: &Names<'a>, + _types: &'a [RecOrType<'a>], + ) -> Option> { + None + } + + pub fn start_func(&mut self, _span: Span, _ty: u32, _locals: &[Local<'_>]) { + match *self.uninhabited {} + } + + pub fn instr(&mut self, _offset: usize, _span: Span) { + match *self.uninhabited {} + } + + pub fn end_func(&mut self, _: usize, _: usize) { + match *self.uninhabited {} + } + + pub fn set_code_section_size(&mut self, _size: usize) { + match *self.uninhabited {} + } + + pub fn emit(&mut self, _dst: &mut Encoder<'_>) { + match *self.uninhabited {} + } +} diff --git a/crates/wast/src/core/expr.rs b/crates/wast/src/core/expr.rs index df585c22c1..9104a8ea7f 100644 --- a/crates/wast/src/core/expr.rs +++ b/crates/wast/src/core/expr.rs @@ -14,8 +14,22 @@ use std::mem; #[derive(Debug)] #[allow(missing_docs)] pub struct Expression<'a> { + /// Instructions in this expression. pub instrs: Box<[Instruction<'a>]>, - pub branch_hints: Vec, + + /// Branch hints, if any, found while parsing instructions. + pub branch_hints: Box<[BranchHint]>, + + /// Optionally parsed spans of all instructions in `instrs`. + /// + /// This value is `None` as it's disabled by default. This can be enabled + /// through the + /// [`ParseBuffer::track_instr_spans`](crate::parser::ParseBuffer::track_instr_spans) + /// function. + /// + /// This is not tracked by default due to the memory overhead and limited + /// use of this field. + pub instr_spans: Option>, } /// A `@metadata.code.branch_hint` in the code, associated with a If or BrIf @@ -33,16 +47,26 @@ pub struct BranchHint { impl<'a> Parse<'a> for Expression<'a> { fn parse(parser: Parser<'a>) -> Result { - let mut exprs = ExpressionParser::default(); + let mut exprs = ExpressionParser::new(parser); exprs.parse(parser)?; Ok(Expression { - instrs: exprs.instrs.into(), - branch_hints: exprs.branch_hints, + instrs: exprs.raw_instrs.into(), + branch_hints: exprs.branch_hints.into(), + instr_spans: exprs.spans.map(|s| s.into()), }) } } impl<'a> Expression<'a> { + /// Creates an expression from the single `instr` specified. + pub fn one(instr: Instruction<'a>) -> Expression<'a> { + Expression { + instrs: [instr].into(), + branch_hints: Box::new([]), + instr_spans: None, + } + } + /// Parse an expression formed from a single folded instruction. /// /// Attempts to parse an expression formed from a single folded instruction. @@ -59,11 +83,12 @@ impl<'a> Expression<'a> { /// operation, so [`crate::Error`] is typically fatal and propagated all the /// way back to the top parse call site. pub fn parse_folded_instruction(parser: Parser<'a>) -> Result { - let mut exprs = ExpressionParser::default(); + let mut exprs = ExpressionParser::new(parser); exprs.parse_folded_instruction(parser)?; Ok(Expression { - instrs: exprs.instrs.into(), - branch_hints: exprs.branch_hints, + instrs: exprs.raw_instrs.into(), + branch_hints: exprs.branch_hints.into(), + instr_spans: exprs.spans.map(|s| s.into()), }) } } @@ -74,11 +99,13 @@ impl<'a> Expression<'a> { /// call-thread-stack recursive function. Since we're parsing user input that /// runs the risk of blowing the call stack, so we want to be sure to use a heap /// stack structure wherever possible. -#[derive(Default)] struct ExpressionParser<'a> { /// The flat list of instructions that we've parsed so far, and will /// eventually become the final `Expression`. - instrs: Vec>, + /// + /// Appended to with `push_instr` to ensure that this is the same length of + /// `spans` if `spans` is used. + raw_instrs: Vec>, /// Descriptor of all our nested s-expr blocks. This only happens when /// instructions themselves are nested. @@ -88,19 +115,23 @@ struct ExpressionParser<'a> { /// Will be used later to collect the offsets in the final binary. /// <(index of branch instructions, BranchHintAnnotation)> branch_hints: Vec, + + /// Storage for all span information in `raw_instrs`. Optionally disabled to + /// reduce memory consumption of parsing expressions. + spans: Option>, } enum Paren { None, Left, - Right, + Right(Span), } /// A "kind" of nested block that we can be parsing inside of. enum Level<'a> { /// This is a normal `block` or `loop` or similar, where the instruction /// payload here is pushed when the block is exited. - EndWith(Instruction<'a>), + EndWith(Instruction<'a>, Option), /// This is a pretty special variant which means that we're parsing an `if` /// statement, and the state of the `if` parsing is tracked internally in @@ -122,7 +153,7 @@ enum If<'a> { /// clause, if any, of the `if` instruction. /// /// This parse ends when `(then ...)` is encountered. - Clause(Instruction<'a>), + Clause(Instruction<'a>, Span), /// Currently parsing the `then` block, and afterwards a closing paren is /// required or an `(else ...)` expression. Then, @@ -131,6 +162,19 @@ enum If<'a> { } impl<'a> ExpressionParser<'a> { + fn new(parser: Parser<'a>) -> ExpressionParser { + ExpressionParser { + raw_instrs: Vec::new(), + stack: Vec::new(), + branch_hints: Vec::new(), + spans: if parser.track_instr_spans() { + Some(Vec::new()) + } else { + None + }, + } + } + fn parse(&mut self, parser: Parser<'a>) -> Result<()> { // Here we parse instructions in a loop, and we do not recursively // invoke this parse function to avoid blowing the stack on @@ -153,7 +197,10 @@ impl<'a> ExpressionParser<'a> { match self.paren(parser)? { // No parenthesis seen? Then we just parse the next instruction // and move on. - Paren::None => self.instrs.push(parser.parse()?), + Paren::None => { + let span = parser.cur_span(); + self.push_instr(parser.parse()?, span); + } // If we see a left-parenthesis then things are a little // special. We handle block-like instructions specially @@ -178,6 +225,7 @@ impl<'a> ExpressionParser<'a> { continue; } + let span = parser.cur_span(); match parser.parse()? { // If block/loop show up then we just need to be sure to // push an `end` instruction whenever the `)` token is @@ -185,29 +233,30 @@ impl<'a> ExpressionParser<'a> { i @ Instruction::Block(_) | i @ Instruction::Loop(_) | i @ Instruction::TryTable(_) => { - self.instrs.push(i); - self.stack.push(Level::EndWith(Instruction::End(None))); + self.push_instr(i, span); + self.stack + .push(Level::EndWith(Instruction::End(None), None)); } // Parsing an `if` instruction is super tricky, so we // push an `If` scope and we let all our scope-based // parsing handle the remaining items. i @ Instruction::If(_) => { - self.stack.push(Level::If(If::Clause(i))); + self.stack.push(Level::If(If::Clause(i, span))); } // Anything else means that we're parsing a nested form // such as `(i32.add ...)` which means that the // instruction we parsed will be coming at the end. - other => self.stack.push(Level::EndWith(other)), + other => self.stack.push(Level::EndWith(other, Some(span))), } } // If we registered a `)` token as being seen, then we're // guaranteed there's an item in the `stack` stack for us to // pop. We peel that off and take a look at what it says to do. - Paren::Right => match self.stack.pop().unwrap() { - Level::EndWith(i) => self.instrs.push(i), + Paren::Right(span) => match self.stack.pop().unwrap() { + Level::EndWith(i, s) => self.push_instr(i, s.unwrap_or(span)), Level::IfArm => {} Level::BranchHint => {} @@ -215,11 +264,11 @@ impl<'a> ExpressionParser<'a> { // block, then that's an error because there weren't enough // items in the `if` statement. Otherwise we're just careful // to terminate with an `end` instruction. - Level::If(If::Clause(_)) => { + Level::If(If::Clause(..)) => { return Err(parser.error("previous `if` had no `then`")); } Level::If(_) => { - self.instrs.push(Instruction::End(None)); + self.push_instr(Instruction::End(None), span); } }, } @@ -232,14 +281,15 @@ impl<'a> ExpressionParser<'a> { while !done { match self.paren(parser)? { Paren::Left => { - self.stack.push(Level::EndWith(parser.parse()?)); + let span = parser.cur_span(); + self.stack.push(Level::EndWith(parser.parse()?, Some(span))); } - Paren::Right => { - let top_instr = match self.stack.pop().unwrap() { - Level::EndWith(i) => i, + Paren::Right(span) => { + let (top_instr, span) = match self.stack.pop().unwrap() { + Level::EndWith(i, s) => (i, s.unwrap_or(span)), _ => panic!("unknown level type"), }; - self.instrs.push(top_instr); + self.push_instr(top_instr, span); if self.stack.is_empty() { done = true; } @@ -259,7 +309,7 @@ impl<'a> ExpressionParser<'a> { Some(rest) => (Paren::Left, rest), None if self.stack.is_empty() => (Paren::None, cursor), None => match cursor.rparen()? { - Some(rest) => (Paren::Right, rest), + Some(rest) => (Paren::Right(cursor.cur_span()), rest), None => (Paren::None, cursor), }, }) @@ -292,14 +342,15 @@ impl<'a> ExpressionParser<'a> { // folded instruction unless it starts with `then`, in which case // this transitions to the `Then` state and a new level has been // reached. - If::Clause(if_instr) => { + If::Clause(if_instr, if_instr_span) => { if !parser.peek::()? { return Ok(false); } parser.parse::()?; let instr = mem::replace(if_instr, Instruction::End(None)); - self.instrs.push(instr); + let span = *if_instr_span; *i = If::Then; + self.push_instr(instr, span); self.stack.push(Level::IfArm); Ok(true) } @@ -307,9 +358,9 @@ impl<'a> ExpressionParser<'a> { // Previously we were parsing the `(then ...)` clause so this next // `(` must be followed by `else`. If::Then => { - parser.parse::()?; - self.instrs.push(Instruction::Else(None)); + let span = parser.parse::()?.0; *i = If::Else; + self.push_instr(Instruction::Else(None), span); self.stack.push(Level::IfArm); Ok(true) } @@ -332,11 +383,18 @@ impl<'a> ExpressionParser<'a> { }; self.branch_hints.push(BranchHint { - instr_index: self.instrs.len(), + instr_index: self.raw_instrs.len(), value, }); Ok(()) } + + fn push_instr(&mut self, instr: Instruction<'a>, span: Span) { + self.raw_instrs.push(instr); + if let Some(spans) = &mut self.spans { + spans.push(span); + } + } } // TODO: document this obscenity diff --git a/crates/wast/src/core/memory.rs b/crates/wast/src/core/memory.rs index 2cc04480ff..7efabed60c 100644 --- a/crates/wast/src/core/memory.rs +++ b/crates/wast/src/core/memory.rs @@ -166,10 +166,7 @@ impl<'a> Parse<'a> for Data<'a> { // single-instruction expression. let insn = parser.parse()?; if parser.is_empty() { - return Ok(Expression { - instrs: [insn].into(), - branch_hints: Vec::new(), - }); + return Ok(Expression::one(insn)); } // This is support for what is currently invalid syntax @@ -183,13 +180,11 @@ impl<'a> Parse<'a> for Data<'a> { // (data (offset ...)) // // but alas - let expr: Expression = parser.parse()?; + let mut expr: Expression = parser.parse()?; let mut instrs = Vec::from(expr.instrs); instrs.push(insn); - Ok(Expression { - instrs: instrs.into(), - branch_hints: Vec::new(), - }) + expr.instrs = instrs.into(); + Ok(expr) } })?; DataKind::Active { memory, offset } diff --git a/crates/wast/src/core/module.rs b/crates/wast/src/core/module.rs index f74ce6b619..bba4a8dd95 100644 --- a/crates/wast/src/core/module.rs +++ b/crates/wast/src/core/module.rs @@ -1,3 +1,4 @@ +use crate::core::binary::EncodeOptions; use crate::core::*; use crate::parser::{Parse, Parser, Result}; use crate::token::{Id, Index, NameAnnotation, Span}; @@ -85,11 +86,7 @@ impl<'a> Module<'a> { /// This function can return an error for name resolution errors and other /// expansion-related errors. pub fn encode(&mut self) -> std::result::Result, crate::Error> { - self.resolve()?; - Ok(match &self.kind { - ModuleKind::Text(fields) => crate::core::binary::encode(&self.id, &self.name, fields), - ModuleKind::Binary(blobs) => blobs.iter().flat_map(|b| b.iter().cloned()).collect(), - }) + EncodeOptions::default().encode_module(self) } pub(crate) fn validate(&self, parser: Parser<'_>) -> Result<()> { diff --git a/crates/wast/src/core/resolve/deinline_import_export.rs b/crates/wast/src/core/resolve/deinline_import_export.rs index 1946bfd037..8dbb5f6a17 100644 --- a/crates/wast/src/core/resolve/deinline_import_export.rs +++ b/crates/wast/src/core/resolve/deinline_import_export.rs @@ -71,14 +71,11 @@ pub fn run(fields: &mut Vec) { name: None, kind: DataKind::Active { memory: Index::Id(id), - offset: Expression { - instrs: Box::new([if is64 { - Instruction::I64Const(0) - } else { - Instruction::I32Const(0) - }]), - branch_hints: Vec::new(), - }, + offset: Expression::one(if is64 { + Instruction::I64Const(0) + } else { + Instruction::I32Const(0) + }), }, data, })); @@ -140,14 +137,11 @@ pub fn run(fields: &mut Vec) { name: None, kind: ElemKind::Active { table: Index::Id(id), - offset: Expression { - instrs: Box::new([if is64 { - Instruction::I64Const(0) - } else { - Instruction::I32Const(0) - }]), - branch_hints: Vec::new(), - }, + offset: Expression::one(if is64 { + Instruction::I64Const(0) + } else { + Instruction::I32Const(0) + }), }, payload, })); diff --git a/crates/wast/src/core/table.rs b/crates/wast/src/core/table.rs index 9390ccba06..3d48903394 100644 --- a/crates/wast/src/core/table.rs +++ b/crates/wast/src/core/table.rs @@ -264,10 +264,7 @@ impl<'a> ElemPayload<'a> { match &mut ret { ElemPayload::Indices(list) => list.push(func), ElemPayload::Exprs { exprs, .. } => { - let expr = Expression { - instrs: [Instruction::RefFunc(func)].into(), - branch_hints: Vec::new(), - }; + let expr = Expression::one(Instruction::RefFunc(func)); exprs.push(expr); } } diff --git a/crates/wast/src/lib.rs b/crates/wast/src/lib.rs index 7419d2ad9c..9fbb5c6431 100644 --- a/crates/wast/src/lib.rs +++ b/crates/wast/src/lib.rs @@ -43,10 +43,15 @@ //! don't need this (for example you're parsing your own s-expression format) //! then this feature can be disabled. //! +//! This crate also has an off-by-default `dwarf` feature which enables using +//! [`core::EncodeOptions::dwarf`] to embed DWARF debugging information in generated +//! binaries. +//! //! [`Parse`]: parser::Parse //! [`LexError`]: lexer::LexError #![deny(missing_docs, rustdoc::broken_intra_doc_links)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] /// A macro to create a custom keyword parser. /// diff --git a/crates/wast/src/parser.rs b/crates/wast/src/parser.rs index 2bd9e4665e..305abe9eaf 100644 --- a/crates/wast/src/parser.rs +++ b/crates/wast/src/parser.rs @@ -304,6 +304,7 @@ pub struct ParseBuffer<'a> { lexer: Lexer<'a>, cur: Cell, known_annotations: RefCell>, + track_instr_spans: bool, depth: Cell, strings: Bump, } @@ -385,9 +386,23 @@ impl ParseBuffer<'_> { }), known_annotations: Default::default(), strings: Default::default(), + track_instr_spans: false, }) } + /// Indicates whether the [`Expression::instr_spans`] field will be filled + /// in. + /// + /// This is useful when enabling DWARF debugging information via + /// [`EncodeOptions::dwarf`], for example. + /// + /// [`Expression::instr_spans`]: crate::core::Expression::instr_spans + /// [`EncodeOptions::dwarf`]: crate::core::EncodeOptions::dwarf + pub fn track_instr_spans(&mut self, track: bool) -> &mut Self { + self.track_instr_spans = track; + self + } + fn parser(&self) -> Parser<'_> { Parser { buf: self } } @@ -973,6 +988,11 @@ impl<'a> Parser<'a> { } } } + + #[cfg(feature = "wasm-module")] + pub(crate) fn track_instr_spans(&self) -> bool { + self.buf.track_instr_spans + } } impl<'a> Cursor<'a> { diff --git a/crates/wast/src/wat.rs b/crates/wast/src/wat.rs index d4982264fa..3673c849f1 100644 --- a/crates/wast/src/wat.rs +++ b/crates/wast/src/wat.rs @@ -27,10 +27,7 @@ impl Wat<'_> { /// Encodes this `Wat` to binary form. This calls either [`Module::encode`] /// or [`Component::encode`]. pub fn encode(&mut self) -> std::result::Result, crate::Error> { - match self { - Wat::Module(m) => m.encode(), - Wat::Component(c) => c.encode(), - } + crate::core::EncodeOptions::default().encode_wat(self) } /// Returns the defining span of this file. diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml index b062f04a0e..fa8901c3de 100644 --- a/crates/wat/Cargo.toml +++ b/crates/wat/Cargo.toml @@ -13,8 +13,17 @@ Rust parser for the WebAssembly Text format, WAT """ rust-version.workspace = true +[package.metadata.docs.rs] +all-features = true + [lints] workspace = true [dependencies] wast = { workspace = true } + +[features] +# Off-by-default feature to support emitting DWARF debugging information in +# parsed binaries pointing back to source locations in the original `*.wat` +# source. +dwarf = ['wast/dwarf'] diff --git a/crates/wat/src/lib.rs b/crates/wat/src/lib.rs index 9404ad5e0e..d08d6ccebf 100644 --- a/crates/wat/src/lib.rs +++ b/crates/wat/src/lib.rs @@ -66,14 +66,19 @@ //! [wat]: http://webassembly.github.io/spec/core/text/index.html #![deny(missing_docs)] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] use std::borrow::Cow; use std::fmt; use std::path::{Path, PathBuf}; use std::str; +use wast::core::EncodeOptions; use wast::lexer::{Lexer, TokenKind}; use wast::parser::{self, ParseBuffer}; +#[doc(inline)] +pub use wast::core::GenerateDwarf; + /// Parses a file on disk as a [WebAssembly Text format][wat] file, or a binary /// WebAssembly file /// @@ -97,23 +102,7 @@ use wast::parser::{self, ParseBuffer}; /// /// [wat]: http://webassembly.github.io/spec/core/text/index.html pub fn parse_file(file: impl AsRef) -> Result> { - _parse_file(file.as_ref()) -} - -fn _parse_file(file: &Path) -> Result> { - let contents = std::fs::read(file).map_err(|err| Error { - kind: Box::new(ErrorKind::Io { - err, - file: Some(file.to_owned()), - }), - })?; - match parse_bytes(&contents) { - Ok(bytes) => Ok(bytes.into_owned()), - Err(mut e) => { - e.set_path(file); - Err(e) - } - } + Parser::new().parse_file(file) } /// Parses in-memory bytes as either the [WebAssembly Text format][wat], or a @@ -156,18 +145,7 @@ fn _parse_file(file: &Path) -> Result> { /// /// [wat]: http://webassembly.github.io/spec/core/text/index.html pub fn parse_bytes(bytes: &[u8]) -> Result> { - if bytes.starts_with(b"\0asm") { - return Ok(bytes.into()); - } - match str::from_utf8(bytes) { - Ok(s) => _parse_str(s).map(|s| s.into()), - Err(_) => Err(Error { - kind: Box::new(ErrorKind::Custom { - msg: "input bytes aren't valid utf-8".to_string(), - file: None, - }), - }), - } + Parser::new().parse_bytes(None, bytes) } /// Parses an in-memory string as the [WebAssembly Text format][wat], returning @@ -213,13 +191,100 @@ pub fn parse_bytes(bytes: &[u8]) -> Result> { /// /// [wat]: http://webassembly.github.io/spec/core/text/index.html pub fn parse_str(wat: impl AsRef) -> Result> { - _parse_str(wat.as_ref()) + Parser::default().parse_str(None, wat) } -fn _parse_str(wat: &str) -> Result> { - let buf = ParseBuffer::new(wat).map_err(|e| Error::cvt(e, wat))?; - let mut ast = parser::parse::(&buf).map_err(|e| Error::cvt(e, wat))?; - ast.encode().map_err(|e| Error::cvt(e, wat)) +/// Parser configuration for transforming bytes into WebAssembly binaries. +#[derive(Default)] +pub struct Parser { + #[cfg(feature = "dwarf")] + generate_dwarf: Option, + _private: (), +} + +impl Parser { + /// Creates a new parser with th default settings. + pub fn new() -> Parser { + Parser::default() + } + + /// Indicates that DWARF debugging information should be generated and + /// emitted by default. + /// + /// Note that DWARF debugging information is only emitted for textual-based + /// modules. For example if a WebAssembly binary is parsed via + /// [`Parser::parse_bytes`] this won't insert new DWARF information in such + /// a binary. Additionally if the text format used the `(module binary ...)` + /// form then no DWARF information will be emitted. + #[cfg(feature = "dwarf")] + pub fn generate_dwarf(&mut self, generate: GenerateDwarf) -> &mut Self { + self.generate_dwarf = Some(generate); + self + } + + /// Equivalent of [`parse_file`] but uses this parser's settings. + pub fn parse_file(&self, path: impl AsRef) -> Result> { + self._parse_file(path.as_ref()) + } + + fn _parse_file(&self, file: &Path) -> Result> { + let contents = std::fs::read(file).map_err(|err| Error { + kind: Box::new(ErrorKind::Io { + err, + file: Some(file.to_owned()), + }), + })?; + match self.parse_bytes(Some(file), &contents) { + Ok(bytes) => Ok(bytes.into_owned()), + Err(mut e) => { + e.set_path(file); + Err(e) + } + } + } + + /// Equivalent of [`parse_bytes`] but uses this parser's settings. + /// + /// The `path` argument is an optional path to use when error messages are + /// generated. + pub fn parse_bytes<'a>(&self, path: Option<&Path>, bytes: &'a [u8]) -> Result> { + if bytes.starts_with(b"\0asm") { + return Ok(bytes.into()); + } + match str::from_utf8(bytes) { + Ok(s) => self._parse_str(path, s).map(|s| s.into()), + Err(_) => Err(Error { + kind: Box::new(ErrorKind::Custom { + msg: "input bytes aren't valid utf-8".to_string(), + file: path.map(|p| p.to_owned()), + }), + }), + } + } + + /// Equivalent of [`parse_str`] but uses this parser's settings. + /// + /// The `path` argument is an optional path to use when error messages are + /// generated. + pub fn parse_str(&self, path: Option<&Path>, wat: impl AsRef) -> Result> { + self._parse_str(path, wat.as_ref()) + } + + fn _parse_str(&self, path: Option<&Path>, wat: &str) -> Result> { + let mut _buf = ParseBuffer::new(wat).map_err(|e| Error::cvt(e, wat, path))?; + #[cfg(feature = "dwarf")] + _buf.track_instr_spans(self.generate_dwarf.is_some()); + let mut ast = parser::parse::(&_buf).map_err(|e| Error::cvt(e, wat, path))?; + + let mut _opts = EncodeOptions::default(); + #[cfg(feature = "dwarf")] + if let Some(style) = self.generate_dwarf { + _opts.dwarf(path.unwrap_or(".wat".as_ref()), wat, style); + } + _opts + .encode_wat(&mut ast) + .map_err(|e| Error::cvt(e, wat, path)) + } } /// Result of [`Detect::from_bytes`] to indicate what some input bytes look @@ -320,8 +385,11 @@ enum ErrorKind { } impl Error { - fn cvt>(e: E, contents: &str) -> Error { + fn cvt>(e: E, contents: &str, path: Option<&Path>) -> Error { let mut err = e.into(); + if let Some(path) = path { + err.set_path(path); + } err.set_text(contents); Error { kind: Box::new(ErrorKind::Wast(err)), diff --git a/src/addr2line.rs b/src/addr2line.rs new file mode 100644 index 0000000000..f81986ee66 --- /dev/null +++ b/src/addr2line.rs @@ -0,0 +1,116 @@ +//! Shared support for `addr2line` and `validate` to parse DWARF sections. + +use addr2line::Context; +use anyhow::{bail, Context as _, Result}; +use gimli::EndianSlice; +use std::collections::HashMap; +use std::ops::Range; +use wasmparser::{Encoding, Parser, Payload}; + +pub struct Addr2lineModules<'a> { + modules: Vec>, +} + +struct Module<'a> { + range: Range, + code_start: Option, + custom_sections: HashMap<&'a str, &'a [u8]>, + context: Option>>, +} + +impl<'a> Addr2lineModules<'a> { + pub fn parse(wasm: &'a [u8]) -> Result { + let mut modules = Vec::new(); + let mut cur_module = None; + for payload in Parser::new(0).parse_all(wasm) { + match payload? { + Payload::Version { + encoding: Encoding::Module, + range, + .. + } => { + assert!(cur_module.is_none()); + cur_module = Some(Module { + range: range.start as u64..0, + code_start: None, + custom_sections: HashMap::new(), + context: None, + }); + } + + Payload::CustomSection(s) => { + if let Some(cur) = &mut cur_module { + cur.custom_sections.insert(s.name(), s.data()); + } + } + Payload::CodeSectionStart { range, .. } => { + assert!(cur_module.is_some()); + cur_module.as_mut().unwrap().code_start = Some(range.start as u64); + } + + Payload::End(offset) => { + if let Some(mut module) = cur_module.take() { + module.range.end = offset as u64; + modules.push(module); + } + } + _ => {} + } + } + Ok(Addr2lineModules { modules }) + } + + pub fn context( + &mut self, + addr: u64, + code_section_relative: bool, + ) -> Result>, u64)>> { + let module = if code_section_relative { + if self.modules.len() == 1 { + &mut self.modules[0] + } else { + bail!("cannot use `--code-section-relative` with more than one module") + } + } else { + match self + .modules + .iter_mut() + .find(|module| module.range.start <= addr && addr <= module.range.end) + { + Some(module) => module, + None => return Ok(None), + } + }; + + let dwarf = gimli::Dwarf::load(|id| -> Result<_> { + let data = module + .custom_sections + .get(id.name()) + .copied() + .unwrap_or(&[]); + Ok(EndianSlice::new(data, gimli::LittleEndian)) + })?; + if module.context.is_none() { + module.context = Some( + Context::from_dwarf(dwarf) + .context("failed to create addr2line dwarf mapping context")?, + ); + } + let context = module.context.as_mut().unwrap(); + + // Addresses in DWARF are relative to the start of the text section, so + // factor that in here. + let text_relative_addr = if code_section_relative { + addr + } else { + match module.code_start { + Some(start) => addr + .checked_sub(start) + .context("address is before the beginning of the text section")?, + None => bail!("no code section found in module"), + } + }; + + Ok(Some((context, text_relative_addr))) + } +} diff --git a/src/bin/wasm-tools/addr2line.rs b/src/bin/wasm-tools/addr2line.rs index ea4c60917c..989929da09 100644 --- a/src/bin/wasm-tools/addr2line.rs +++ b/src/bin/wasm-tools/addr2line.rs @@ -1,11 +1,8 @@ -use addr2line::{Context, LookupResult}; -use anyhow::{anyhow, bail, Context as _, Result}; -use gimli::EndianSlice; -use std::collections::HashMap; +use addr2line::LookupResult; +use anyhow::{bail, Context as _, Result}; use std::io::Write; -use std::ops::Range; use std::u64; -use wasmparser::{Encoding, Parser, Payload}; +use wasm_tools::addr2line::Addr2lineModules; /// Translate a WebAssembly address to a filename and line number using DWARF /// debugging information. @@ -41,12 +38,6 @@ pub struct Opts { code_section_relative: bool, } -struct Module<'a> { - range: Range, - code_start: Option, - custom_sections: HashMap<&'a str, &'a [u8]>, -} - impl Opts { pub fn general_opts(&self) -> &wasm_tools::GeneralOpts { self.io.general_opts() @@ -55,60 +46,24 @@ impl Opts { pub fn run(&self) -> Result<()> { let wasm = self.io.parse_input_wasm()?; - let modules = self - .parse_custom_sections(&wasm) + let mut modules = Addr2lineModules::parse(&wasm) .context("failed to parse input and read custom sections")?; let mut output = self.io.output_writer()?; for addr in self.addresses.iter() { - self.addr2line(&addr, &modules, &mut output) + self.addr2line(&addr, &mut modules, &mut output) .with_context(|| format!("failed to find frames for `{addr}`"))?; } Ok(()) } - fn parse_custom_sections<'a>(&self, wasm: &'a [u8]) -> Result>> { - let mut ret = Vec::new(); - let mut cur_module = None; - for payload in Parser::new(0).parse_all(wasm) { - match payload? { - Payload::Version { - encoding: Encoding::Module, - range, - .. - } => { - assert!(cur_module.is_none()); - cur_module = Some(Module { - range: range.start as u64..0, - code_start: None, - custom_sections: HashMap::new(), - }); - } - - Payload::CustomSection(s) => { - if let Some(cur) = &mut cur_module { - cur.custom_sections.insert(s.name(), s.data()); - } - } - Payload::CodeSectionStart { range, .. } => { - assert!(cur_module.is_some()); - cur_module.as_mut().unwrap().code_start = Some(range.start as u64); - } - - Payload::End(offset) => { - if let Some(mut module) = cur_module.take() { - module.range.end = offset as u64; - ret.push(module); - } - } - _ => {} - } - } - Ok(ret) - } - - fn addr2line(&self, addr: &str, modules: &[Module<'_>], out: &mut dyn Write) -> Result<()> { + fn addr2line( + &self, + addr: &str, + modules: &mut Addr2lineModules<'_>, + out: &mut dyn Write, + ) -> Result<()> { // Support either `0x` or `@` prefixes for hex addresses since 0x is // standard and @ is used by wasmprinter (and web browsers I think?) let addr = if let Some(hex) = addr.strip_prefix("0x").or_else(|| addr.strip_prefix("@")) { @@ -117,33 +72,9 @@ impl Opts { addr.parse()? }; - let module = modules - .iter() - .find(|module| module.range.start <= addr && addr <= module.range.end) - .ok_or_else(|| anyhow!("no module found which contains this address"))?; - - let dwarf = gimli::Dwarf::load(|id| -> Result<_> { - let data = module - .custom_sections - .get(id.name()) - .copied() - .unwrap_or(&[]); - Ok(EndianSlice::new(data, gimli::LittleEndian)) - })?; - let cx = Context::from_dwarf(dwarf) - .context("failed to create addr2line dwarf mapping context")?; - - // Addresses in DWARF are relative to the start of the text section, so - // factor that in here. - let text_relative_addr = if self.code_section_relative { - addr - } else { - match module.code_start { - Some(start) => addr - .checked_sub(start) - .context("address is before the beginning of the text section")?, - None => bail!("no code section found in module"), - } + let (cx, text_relative_addr) = match modules.context(addr, self.code_section_relative)? { + Some(pair) => pair, + None => bail!("no module found which contains this address"), }; let mut frames = match cx.find_frames(text_relative_addr) { diff --git a/src/bin/wasm-tools/print.rs b/src/bin/wasm-tools/print.rs index aa29c525c6..b783b04e85 100644 --- a/src/bin/wasm-tools/print.rs +++ b/src/bin/wasm-tools/print.rs @@ -12,8 +12,9 @@ pub struct Opts { #[clap(short, long)] print_offsets: bool, - /// Indicates that the "skeleton" of a module should be printed where items - /// such as function bodies, data segments, and element segments are + /// Indicates that the "skeleton" of a module should be printed. + /// + /// Items such as function bodies, data segments, and element segments are /// replaced with "..." instead of printing their actual contents. #[clap(long)] skeleton: bool, diff --git a/src/bin/wasm-tools/validate.rs b/src/bin/wasm-tools/validate.rs index 2fdfbdb6a8..c0268ccb0d 100644 --- a/src/bin/wasm-tools/validate.rs +++ b/src/bin/wasm-tools/validate.rs @@ -1,8 +1,13 @@ +use addr2line::LookupResult; use anyhow::{anyhow, Context, Result}; use rayon::prelude::*; +use std::fmt::Write; use std::mem; use std::time::Instant; -use wasmparser::{FuncValidatorAllocations, Parser, ValidPayload, Validator, WasmFeatures}; +use wasm_tools::addr2line::Addr2lineModules; +use wasmparser::{ + BinaryReaderError, FuncValidatorAllocations, Parser, ValidPayload, Validator, WasmFeatures, +}; /// Validate a WebAssembly binary /// @@ -49,6 +54,31 @@ impl Opts { } pub fn run(&self) -> Result<()> { + let wasm = self.io.parse_input_wasm()?; + + // If validation fails then try to attach extra information to the + // error based on DWARF information in the input wasm binary. If + // DWARF information isn't present or if the DWARF failed to get parsed + // then ignore the error and carry on. + let error = match self.validate(&wasm) { + Ok(()) => return Ok(()), + Err(e) => e, + }; + let offset = match error.downcast_ref::() { + Some(err) => err.offset(), + None => return Err(error.into()), + }; + match self.annotate_error_with_file_and_line(&wasm, offset) { + Ok(Some(msg)) => Err(error.context(msg)), + Ok(None) => Err(error.into()), + Err(e) => { + log::warn!("failed to parse DWARF information: {e:?}"); + Err(error.into()) + } + } + } + + fn validate(&self, wasm: &[u8]) -> Result<()> { // Note that here we're copying the contents of // `Validator::validate_all`, but the end is followed up with a parallel // iteration over the functions to validate instead of a synchronous @@ -61,7 +91,6 @@ impl Opts { // validated later. let mut validator = Validator::new_with_features(self.features.unwrap_or_default()); let mut functions_to_validate = Vec::new(); - let wasm = self.io.parse_input_wasm()?; let start = Instant::now(); for payload in Parser::new(0).parse_all(&wasm) { @@ -92,6 +121,51 @@ impl Opts { log::info!("functions validated in {:?}", start.elapsed()); Ok(()) } + + fn annotate_error_with_file_and_line( + &self, + wasm: &[u8], + offset: usize, + ) -> Result> { + let mut modules = Addr2lineModules::parse(wasm)?; + let code_section_relative = false; + let (context, text_rel) = match modules.context(offset as u64, code_section_relative)? { + Some(pair) => pair, + None => return Ok(None), + }; + + let mut frames = match context.find_frames(text_rel) { + LookupResult::Output(result) => result?, + LookupResult::Load { .. } => return Ok(None), + }; + let frame = match frames.next()? { + Some(frame) => frame, + None => return Ok(None), + }; + + let mut out = String::new(); + if let Some(loc) = &frame.location { + if let Some(file) = loc.file { + write!(out, "{file}")?; + } + if let Some(line) = loc.line { + write!(out, ":{line}")?; + } + if let Some(column) = loc.column { + write!(out, ":{column}")?; + } + write!(out, " ")?; + } + if let Some(func) = &frame.function { + write!(out, "function `{}` failed to validate", func.demangle()?)?; + } + + if out.is_empty() { + Ok(None) + } else { + Ok(Some(out)) + } + } } fn parse_features(arg: &str) -> Result { diff --git a/src/lib.rs b/src/lib.rs index 6a2eb1f6d2..b9536eff81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,8 +5,12 @@ use std::fs::File; use std::io::IsTerminal; use std::io::{BufWriter, Read, Write}; use std::path::{Path, PathBuf}; +use std::str::FromStr; use termcolor::{Ansi, ColorChoice, NoColor, StandardStream, WriteColor}; +#[cfg(any(feature = "addr2line", feature = "validate"))] +pub mod addr2line; + #[derive(clap::Parser)] pub struct GeneralOpts { /// Use verbose output (-v info, -vv debug, -vvv trace). @@ -60,13 +64,61 @@ pub struct InputArg { /// processed. Note that for most subcommands this input can either be a /// binary `*.wasm` file or a textual format `*.wat` file. input: Option, + + /// Optionally generate DWARF debugging information from WebAssembly text + /// files. + /// + /// When the input to this command is a WebAssembly text file, such as + /// `*.wat`, then this option will instruct the text parser to insert DWARF + /// debugging information to map binary locations back to the original + /// source locations in the input `*.wat` file. This option has no effect if + /// the `INPUT` argument is already a WebAssembly binary or if the text + /// format uses `(module binary ...)`. + #[clap( + long, + value_name = "lines|full", + conflicts_with = "generate_full_dwarf" + )] + generate_dwarf: Option, + + /// Shorthand for `--generate-dwarf full` + #[clap(short, conflicts_with = "generate_dwarf")] + generate_full_dwarf: bool, +} + +#[derive(Copy, Clone)] +enum GenerateDwarf { + Lines, + Full, +} + +impl FromStr for GenerateDwarf { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "lines" => Ok(GenerateDwarf::Lines), + "full" => Ok(GenerateDwarf::Full), + other => bail!("unknown `--generate-dwarf` setting: {other}"), + } + } } impl InputArg { pub fn parse_wasm(&self) -> Result> { + let mut parser = wat::Parser::new(); + match (self.generate_full_dwarf, self.generate_dwarf) { + (false, Some(GenerateDwarf::Lines)) => { + parser.generate_dwarf(wat::GenerateDwarf::Lines); + } + (true, _) | (false, Some(GenerateDwarf::Full)) => { + parser.generate_dwarf(wat::GenerateDwarf::Full); + } + (false, None) => {} + } if let Some(path) = &self.input { if path != Path::new("-") { - let bytes = wat::parse_file(path)?; + let bytes = parser.parse_file(path)?; return Ok(bytes); } } @@ -74,10 +126,7 @@ impl InputArg { std::io::stdin() .read_to_end(&mut stdin) .context("failed to read ")?; - let bytes = wat::parse_bytes(&stdin).map_err(|mut e| { - e.set_path(""); - e - })?; + let bytes = parser.parse_bytes(Some("".as_ref()), &stdin)?; Ok(bytes.into_owned()) } } diff --git a/tests/cli/dwarf-into-addr2line.wat b/tests/cli/dwarf-into-addr2line.wat new file mode 100644 index 0000000000..b31f1effe9 --- /dev/null +++ b/tests/cli/dwarf-into-addr2line.wat @@ -0,0 +1,13 @@ +;; RUN: addr2line --generate-dwarf lines % 0x18 0x1a 0x1e 0x20 + +(module + (func $"dwarf(name)" +(;@18;) i32.const 0 +(;@1a;) drop + ) + + (func $another-function +(;@1e;) i32.const 0 +(;@20;) drop + ) +) diff --git a/tests/cli/dwarf-into-addr2line.wat.stdout b/tests/cli/dwarf-into-addr2line.wat.stdout new file mode 100644 index 0000000000..f22d09ef14 --- /dev/null +++ b/tests/cli/dwarf-into-addr2line.wat.stdout @@ -0,0 +1,4 @@ +0x18: dwarf(name) tests/cli/dwarf-into-addr2line.wat:5:10 +0x1a: dwarf(name) tests/cli/dwarf-into-addr2line.wat:6:10 +0x1e: another-function tests/cli/dwarf-into-addr2line.wat:10:10 +0x20: another-function tests/cli/dwarf-into-addr2line.wat:11:10 diff --git a/tests/cli/dwarf-simple.wat b/tests/cli/dwarf-simple.wat new file mode 100644 index 0000000000..7739ff37a6 --- /dev/null +++ b/tests/cli/dwarf-simple.wat @@ -0,0 +1,11 @@ +;; RUN: print --generate-dwarf lines % + +(module + (func) + (func $b) + (func $"c d") + (func + i32.const 0 + drop + ) +) diff --git a/tests/cli/dwarf-simple.wat.stdout b/tests/cli/dwarf-simple.wat.stdout new file mode 100644 index 0000000000..f5e39467f4 --- /dev/null +++ b/tests/cli/dwarf-simple.wat.stdout @@ -0,0 +1,14 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0)) + (func $b (;1;) (type 0)) + (func $"c d" (;2;) (type 0)) + (func (;3;) (type 0) + i32.const 0 + drop + ) + (@custom ".debug_abbrev" (after code) "\01\11\01%\08\13\0f\03\0e\1b\0e\11\06\12\06\10\17\00\00\02.\00\03\08:\0f;\0f9\0f?\19\11\01\12\06\00\00\00") + (@custom ".debug_str" (after code) "tests/cli\00dwarf-simple.wat\00") + (@custom ".debug_line" (after code) "k\00\00\00\05\00\04\00*\00\00\00\01\01\01\fb\0e\0d\00\01\01\01\01\00\00\00\01\00\00\01\01\01\0e\01\00\00\00\00\02\01\0e\02\0f\02\0a\00\00\00\00\0a\00\00\00\00\00\05\02\02\00\00\00 \02\01\00\01\01\00\05\02\05\00\00\00 \02\01\00\01\01\00\05\02\08\00\00\00 \02\01\00\01\01\00\05\02\0b\00\00\00\0a\05\05'/ \02\01\00\01\01") + (@custom ".debug_info" (after code) "\84\00\00\00\05\00\01\04\00\00\00\00\01wast 211.0.1\00\02\0a\00\00\00\00\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\02wasm-function[0]\00\01\04\04\02\00\00\00\02\00\00\00\02b\00\01\05\04\05\00\00\00\02\00\00\00\02c d\00\01\06\04\08\00\00\00\02\00\00\00\02wasm-function[3]\00\01\07\04\0b\00\00\00\05\00\00\00\00") +) diff --git a/tests/cli/dwarf-validate.wat b/tests/cli/dwarf-validate.wat new file mode 100644 index 0000000000..163f519e4c --- /dev/null +++ b/tests/cli/dwarf-validate.wat @@ -0,0 +1,8 @@ +;; FAIL: validate -g % + +(module + (memory 1) + (func $user-name + i32.load + ) +) diff --git a/tests/cli/dwarf-validate.wat.stderr b/tests/cli/dwarf-validate.wat.stderr new file mode 100644 index 0000000000..0c3960fdea --- /dev/null +++ b/tests/cli/dwarf-validate.wat.stderr @@ -0,0 +1,5 @@ +error: tests/cli/dwarf-validate.wat:6:5 function `user-name` failed to validate + +Caused by: + 0: func 0 failed to validate + 1: type mismatch: expected i32 but nothing on stack (at offset 0x1c) From ffd630c9cc1374ded79ff0eada0d0d66e8775508 Mon Sep 17 00:00:00 2001 From: Keith Winstein Date: Thu, 27 Jun 2024 05:33:40 -0700 Subject: [PATCH 33/58] wasmparser/validator & wasmprinter operators.rs: update comments (#1642) --- crates/wasmparser/src/validator/operators.rs | 10 +++++----- crates/wasmprinter/src/operator.rs | 1 - 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs index e13994d3a9..7245fe53bd 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -966,7 +966,7 @@ where Ok(()) } - /// Checks the validity of a common conversion operator. + /// Checks the validity of a common float conversion operator. fn check_fconversion_op(&mut self, into: ValType, from: ValType) -> Result<()> { debug_assert!(matches!(into, ValType::F32 | ValType::F64)); self.check_floats_enabled()?; @@ -1044,14 +1044,14 @@ where self.check_v128_binary_op() } - /// Checks a [`V128`] binary operator. + /// Checks a [`V128`] unary operator. fn check_v128_unary_op(&mut self) -> Result<()> { self.pop_operand(Some(ValType::V128))?; self.push_operand(ValType::V128)?; Ok(()) } - /// Checks a [`V128`] binary operator. + /// Checks a [`V128`] unary float operator. fn check_v128_funary_op(&mut self) -> Result<()> { self.check_floats_enabled()?; self.check_v128_unary_op() @@ -1066,14 +1066,14 @@ where Ok(()) } - /// Checks a [`V128`] relaxed ternary operator. + /// Checks a [`V128`] test operator. fn check_v128_bitmask_op(&mut self) -> Result<()> { self.pop_operand(Some(ValType::V128))?; self.push_operand(ValType::I32)?; Ok(()) } - /// Checks a [`V128`] relaxed ternary operator. + /// Checks a [`V128`] shift operator. fn check_v128_shift_op(&mut self) -> Result<()> { self.pop_operand(Some(ValType::I32))?; self.pop_operand(Some(ValType::V128))?; diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs index 8aac9020a3..2111e80dfe 100644 --- a/crates/wasmprinter/src/operator.rs +++ b/crates/wasmprinter/src/operator.rs @@ -405,7 +405,6 @@ macro_rules! define_visit { // // * Print the name of the insruction as defined in this macro // * Print any payload, as necessary - // * Return the `OpKind`, as defined by this macro ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident )*) => ($( fn $visit(&mut self $( , $($arg: $argty),* )?) -> Self::Output { define_visit!(before_op self $op); From fbf48fc8143ffbb4e5d39a5e8f088e601dac4e97 Mon Sep 17 00:00:00 2001 From: Nick Fitzgerald Date: Thu, 27 Jun 2024 09:37:54 -0700 Subject: [PATCH 34/58] fuzzing: Don't try to run custom-page-sizes modules through our Wasmtime (#1644) The Wasmtime version with support for that proposal is not released yet. --- fuzz/src/no_traps.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/fuzz/src/no_traps.rs b/fuzz/src/no_traps.rs index 2b180b353d..b039c5ac5e 100644 --- a/fuzz/src/no_traps.rs +++ b/fuzz/src/no_traps.rs @@ -26,9 +26,8 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { })?; validate_module(&wasm_bytes); - // Tail calls aren't implemented in wasmtime, so don't try to run them - // there. - if config.tail_call_enabled { + // Don't try to run these modules until we update to Wasmtime >=23. + if config.custom_page_sizes_enabled { return Ok(()); } @@ -38,6 +37,7 @@ pub fn run(u: &mut Unstructured<'_>) -> Result<()> { let mut eng_conf = wasmtime::Config::new(); eng_conf.wasm_memory64(true); eng_conf.wasm_multi_memory(true); + eng_conf.wasm_tail_call(true); eng_conf.consume_fuel(true); let engine = Engine::new(&eng_conf).unwrap(); let module = match Module::from_binary(&engine, &wasm_bytes) { From 8c43f9463046eca2447d3c785dae053594ca6958 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Jun 2024 13:31:36 -0500 Subject: [PATCH 35/58] Update some dependencies and major versions (#1643) Just a bit of routine maintenance. --- Cargo.lock | 552 ++++++++++++++-------------- Cargo.toml | 6 +- crates/fuzz-stats/src/dummy.rs | 14 +- crates/wasm-mutate-stats/Cargo.toml | 4 +- 4 files changed, 292 insertions(+), 284 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index fb40676c1a..5b5035567a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4,17 +4,18 @@ version = 3 [[package]] name = "addr2line" -version = "0.22.0" +version = "0.23.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" +checksum = "7b9d03130b08257bc8110b0df827d8b137fdf67a95e2459eaace2e13fecf1d72" dependencies = [ "cpp_demangle", "fallible-iterator", - "gimli 0.29.0", + "gimli 0.30.0", "memmap2", - "object 0.35.0", + "object", "rustc-demangle", "smallvec", + "typed-arena", ] [[package]] @@ -52,47 +53,48 @@ checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" [[package]] name = "anstream" -version = "0.6.13" +version = "0.6.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", + "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" [[package]] name = "anstyle-parse" -version = "0.2.3" +version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" -version = "1.0.2" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" dependencies = [ "windows-sys", ] [[package]] name = "anstyle-wincon" -version = "3.0.2" +version = "3.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" dependencies = [ "anstyle", "windows-sys", @@ -100,9 +102,9 @@ dependencies = [ [[package]] name = "anyhow" -version = "1.0.82" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f538837af36e6f6a9be0faa67f9a314f8119e4e4b5867c6ab40ed60360142519" +checksum = "b3d1d046238990b9cf5bcde22a3fb3584ee5cf65fb2765f454ed428c7a0063da" [[package]] name = "arbitrary" @@ -127,9 +129,9 @@ checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" [[package]] name = "autocfg" -version = "1.2.0" +version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1fdabc7756949593fe60f30ec81974b613357de856987752631dea1e3394c80" +checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "beef" @@ -137,20 +139,11 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3a8241f3ebb85c056b509d4327ad0358fbbba6ffb340bf388f26350aeda225b1" -[[package]] -name = "bincode" -version = "1.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" -dependencies = [ - "serde", -] - [[package]] name = "bitflags" -version = "2.5.0" +version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "bitmaps" @@ -194,9 +187,9 @@ checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" [[package]] name = "cc" -version = "1.0.95" +version = "1.0.101" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d32a725bc159af97c3e629873bb9f88fb8cf8a4867175f76dc987815ea07c83b" +checksum = "ac367972e516d45567c7eafc73d24e1c193dcf200a8d94e9db7b3d38b349572d" dependencies = [ "jobserver", "libc", @@ -238,9 +231,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.4" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" +checksum = "5db83dced34638ad474f39f250d7fea9598bdd239eaced1bdf45d597da0f433f" dependencies = [ "clap_builder", "clap_derive", @@ -248,9 +241,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.2" +version = "4.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4" +checksum = "f7e204572485eb3fbf28f871612191521df159bc3e15a9f5064c66dba3a8c05f" dependencies = [ "anstream", "anstyle", @@ -260,36 +253,42 @@ dependencies = [ [[package]] name = "clap_complete" -version = "4.5.2" +version = "4.5.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd79504325bf38b10165b02e89b4347300f855f273c4cb30c4a3209e6583275e" +checksum = "fbca90c87c2a04da41e95d1856e8bcd22f159bdbfa147314d2ce5218057b0e58" dependencies = [ "clap", ] [[package]] name = "clap_derive" -version = "4.5.4" +version = "4.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "528131438037fd55894f62d6e9f068b8f45ac57ffa77517819645d10aed04f64" +checksum = "c780290ccf4fb26629baa7a1081e68ced113f1d3ec302fa5948f1c381ebf06c6" dependencies = [ "heck 0.5.0", "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] name = "clap_lex" -version = "0.7.0" +version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "cobs" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" [[package]] name = "colorchoice" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" [[package]] name = "component" @@ -317,18 +316,18 @@ dependencies = [ [[package]] name = "cranelift-bforest" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79b27922a6879b5b5361d0a084cb0b1941bf109a98540addcb932da13b68bed4" +checksum = "0b6b33d7e757a887989eb18b35712b2a67d96171ec3149d1bfb657b29b7b367c" dependencies = [ "cranelift-entity", ] [[package]] name = "cranelift-codegen" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "304c455b28bf56372729acb356afbb55d622f2b0f2f7837aa5e57c138acaac4d" +checksum = "b9acf15cb22be42d07c3b57d7856329cb228b7315d385346149df2566ad5e4aa" dependencies = [ "bumpalo", "cranelift-bforest", @@ -338,42 +337,43 @@ dependencies = [ "cranelift-entity", "cranelift-isle", "gimli 0.28.1", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "log", "regalloc2", + "rustc-hash", "smallvec", "target-lexicon", ] [[package]] name = "cranelift-codegen-meta" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1653c56b99591d07f67c5ca7f9f25888948af3f4b97186bff838d687d666f613" +checksum = "e934d301392b73b3f8b0540391fb82465a0f179a3cee7c726482ac4727efcc97" dependencies = [ "cranelift-codegen-shared", ] [[package]] name = "cranelift-codegen-shared" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f5b6a9cf6b6eb820ee3f973a0db313c05dc12d370f37b4fe9630286e1672573f" +checksum = "8afb2a2566b3d54b854dfb288b3b187f6d3d17d6f762c92898207eba302931da" [[package]] name = "cranelift-control" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d9d06e6bf30075fb6bed9e034ec046475093392eea1aff90eb5c44c4a033d19a" +checksum = "0100f33b704cdacd01ad66ff41f8c5030d57cbff078e2a4e49ab1822591299fa" dependencies = [ "arbitrary", ] [[package]] name = "cranelift-entity" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29be04f931b73cdb9694874a295027471817f26f26d2f0ebe5454153176b6e3a" +checksum = "a8cfdc315e5d18997093e040a8d234bea1ac1e118a716d3e30f40d449e78207b" dependencies = [ "serde", "serde_derive", @@ -381,9 +381,9 @@ dependencies = [ [[package]] name = "cranelift-frontend" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a07fd7393041d7faa2f37426f5dc7fc04003b70988810e8c063beefeff1cd8f9" +checksum = "0f74b84f16af2e982b0c0c72233503d9d55cbfe3865dbe807ca28dc6642a28b5" dependencies = [ "cranelift-codegen", "log", @@ -393,15 +393,15 @@ dependencies = [ [[package]] name = "cranelift-isle" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f341d7938caa6dff8149dac05bb2b53fc680323826b83b4cf175ab9f5139a3c9" +checksum = "adf306d3dde705fb94bd48082f01d38c4ededc74293a4c007805f610bf08bc6e" [[package]] name = "cranelift-native" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "82af6066e6448d26eeabb7aa26a43f7ff79f8217b06bade4ee6ef230aecc8880" +checksum = "1ea0ebdef7aff4a79bcbc8b6495f31315f16b3bf311152f472eaa8d679352581" dependencies = [ "cranelift-codegen", "libc", @@ -410,9 +410,9 @@ dependencies = [ [[package]] name = "cranelift-wasm" -version = "0.107.0" +version = "0.109.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2766fab7284a914a7f17f90ebe865c86453225fb8637ac31f123f5028fee69cd" +checksum = "d549108a1942065cdbac3bb96c2952afa0e1b9a3beff4b08c4308ac72257576d" dependencies = [ "cranelift-codegen", "cranelift-entity", @@ -420,15 +420,15 @@ dependencies = [ "itertools 0.12.1", "log", "smallvec", - "wasmparser 0.202.0", + "wasmparser 0.209.1", "wasmtime-types", ] [[package]] name = "crc32fast" -version = "1.4.0" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3855a8a784b474f333699ef2bbca9db2c4a1f6d9088a90a2d25b1eb53111eaa" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -488,9 +488,9 @@ dependencies = [ [[package]] name = "crossbeam-utils" -version = "0.8.19" +version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "248e3bacc7dc6baa3b21e405ee045c3047101a49145e7e9eca583ab4c2ca5345" +checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "crunchy" @@ -506,18 +506,18 @@ checksum = "67e77553c4162a157adbf834ebae5b415acbecbeafc7a74b0e886657506a7611" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] name = "derive_more" -version = "0.99.17" +version = "0.99.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +checksum = "5f33878137e4dafd7fa914ad4e259e18a4e8e532b9617a2d0150262bf53abfce" dependencies = [ "proc-macro2", "quote", - "syn 1.0.109", + "syn", ] [[package]] @@ -546,9 +546,15 @@ dependencies = [ [[package]] name = "either" -version = "1.11.0" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "embedded-io" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" [[package]] name = "encoding_rs" @@ -590,9 +596,9 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.8" +version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys", @@ -600,12 +606,9 @@ dependencies = [ [[package]] name = "escape8259" -version = "0.5.2" +version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba4f4911e3666fcd7826997b4745c8224295a6f3072f1418c3067b97a67557ee" -dependencies = [ - "rustversion", -] +checksum = "5692dd7b5a1978a5aeb0ce83b7655c58ca8efdcb79d21036ea249da95afec2c6" [[package]] name = "fallible-iterator" @@ -615,9 +618,9 @@ checksum = "2acce4a10f12dc2fb14a218589d4f1f62ef011b2d0cc4b3cb1bba8e94da14649" [[package]] name = "fastrand" -version = "2.0.2" +version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "fixedbitset" @@ -636,9 +639,9 @@ dependencies = [ [[package]] name = "flate2" -version = "1.0.28" +version = "1.0.30" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "46303f565772937ffe1d394a4fac6f411c6013172fadde9dcdb1e147a086940e" +checksum = "5f54427cfd1c7829e2a139fcefea601bf088ebca651d2bf53ebc600eac295dae" dependencies = [ "crc32fast", "miniz_oxide", @@ -664,9 +667,9 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.2.14" +version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", @@ -686,9 +689,9 @@ dependencies = [ [[package]] name = "gimli" -version = "0.29.0" +version = "0.30.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "e2e1d97fbe9722ba9bbd0c97051c2956e726562b61f86a25a4360398a40edfc9" dependencies = [ "fallible-iterator", "indexmap 2.2.6", @@ -728,9 +731,9 @@ dependencies = [ [[package]] name = "hashbrown" -version = "0.14.3" +version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" dependencies = [ "ahash", "serde", @@ -797,7 +800,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" dependencies = [ "equivalent", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "serde", ] @@ -809,9 +812,9 @@ checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "instant" -version = "0.1.12" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" dependencies = [ "cfg-if", ] @@ -836,6 +839,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itertools" version = "0.10.5" @@ -854,6 +863,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -871,9 +889,9 @@ dependencies = [ [[package]] name = "lazy_static" -version = "1.4.0" +version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "leb128" @@ -883,9 +901,9 @@ checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" [[package]] name = "libc" -version = "0.2.153" +version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "libfuzzer-sys" @@ -898,11 +916,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "libm" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ec2a862134d2a7d32d7983ddcdd1c4923530833c9f2ea1a44fc5fa473989058" + [[package]] name = "libtest-mimic" -version = "0.7.2" +version = "0.7.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fefdf21230d6143476a28adbee3d930e2b68a3d56443c777cae3fe9340eebff9" +checksum = "cc0bda45ed5b3a2904262c1bb91e526127aa70e7ef3758aba2ef93cf896b9b58" dependencies = [ "clap", "escape8259", @@ -912,9 +936,9 @@ dependencies = [ [[package]] name = "linux-raw-sys" -version = "0.4.13" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" +checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" @@ -943,7 +967,7 @@ dependencies = [ "proc-macro2", "quote", "regex-syntax", - "syn 2.0.60", + "syn", ] [[package]] @@ -966,9 +990,9 @@ dependencies = [ [[package]] name = "memchr" -version = "2.7.2" +version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memfd" @@ -990,27 +1014,27 @@ dependencies = [ [[package]] name = "memoffset" -version = "0.9.0" +version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "miniz_oxide" -version = "0.7.2" +version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7" +checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "num-traits" -version = "0.2.18" +version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da0df0e5185db44f69b44f26786fe401b6c293d1907744beaa7fa62b2e5a517a" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] @@ -1027,23 +1051,14 @@ dependencies = [ [[package]] name = "object" -version = "0.33.0" +version = "0.36.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d8dd6c0cdf9429bce006e1362bfce61fa1bfd8c898a643ed8d2b471934701d3d" +checksum = "576dfe1fc8f9df304abb159d767a29d0476f7750fbf8aa7ad07816004a207434" dependencies = [ "crc32fast", - "hashbrown 0.14.3", - "indexmap 2.2.6", - "memchr", -] - -[[package]] -name = "object" -version = "0.35.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8ec7ab813848ba4522158d5517a6093db1ded27575b070f4177b8d12b41db5e" -dependencies = [ "flate2", + "hashbrown 0.14.5", + "indexmap 2.2.6", "memchr", "ruzstd", ] @@ -1062,20 +1077,31 @@ checksum = "0ab1bc2a289d34bd04a330323ac98a1b4bc82c9d9fcb1e66b63caa84da26b575" [[package]] name = "paste" -version = "1.0.14" +version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "petgraph" -version = "0.6.4" +version = "0.6.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1d3afd2628e69da2be385eb6f2fd57c8ac7977ceeff6dc166ff1657b0e386a9" +checksum = "b4c5cc86750666a3ed20bdaf5ca2a0344f9c67674cae0515bec2da16fbaa47db" dependencies = [ "fixedbitset", "indexmap 2.2.6", ] +[[package]] +name = "postcard" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a55c51ee6c0db07e68448e336cf8ea4131a620edefebf9893e759b2d793420f8" +dependencies = [ + "cobs", + "embedded-io", + "serde", +] + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -1094,9 +1120,9 @@ dependencies = [ [[package]] name = "proc-macro2" -version = "1.0.81" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d1597b0c024618f09a9c3b8655b7e430397a36d23fdafec26d6965e9eec3eba" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] @@ -1193,9 +1219,9 @@ dependencies = [ [[package]] name = "regex" -version = "1.10.4" +version = "1.10.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" +checksum = "b91213439dad192326a0d7c6ee3955910425f441d7038e0d6933b0aec5c4517f" dependencies = [ "aho-corasick", "memchr", @@ -1205,9 +1231,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.6" +version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea" +checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", @@ -1216,15 +1242,15 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.3" +version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" +checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "rustc-demangle" -version = "0.1.23" +version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" @@ -1245,12 +1271,6 @@ dependencies = [ "windows-sys", ] -[[package]] -name = "rustversion" -version = "1.0.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "80af6f9131f277a45a3fba6ce8e2258037bb0477a67e610d3c1fe046ab31de47" - [[package]] name = "ruzstd" version = "0.6.0" @@ -1264,9 +1284,9 @@ dependencies = [ [[package]] name = "ryu" -version = "1.0.17" +version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e86697c916019a8588c99b5fac3cead74ec0b4b819707a682fd4d23fa0ce1ba1" +checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" @@ -1279,35 +1299,35 @@ dependencies = [ [[package]] name = "semver" -version = "1.0.22" +version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "92d43fe69e652f3df9bdc2b85b2854a0825b86e4fb76bc44d945137d053639ca" +checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" [[package]] name = "serde" -version = "1.0.198" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9846a40c979031340571da2545a4e5b7c4163bdae79b301d5f86d03979451fcc" +checksum = "7253ab4de971e72fb7be983802300c30b5a7f0c2e56fab8abfc6a214307c0094" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.198" +version = "1.0.203" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e88edab869b01783ba905e7d0153f9fc1a6505a96e4ad3018011eedb838566d9" +checksum = "500cbc0ebeb6f46627f50f3f5811ccf6bf00643be300b4c3eabc0ef55dc5b5ba" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] name = "serde_json" -version = "1.0.116" +version = "1.0.118" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3e17db7126d17feb94eb3fad46bf1a96b034e8aacbc2e775fe81505f8b0b2813" +checksum = "d947f6b3163d8857ea16c4fa0dd4840d52f3041039a85decd46867eb1abef2e4" dependencies = [ "itoa", "ryu", @@ -1348,12 +1368,15 @@ name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" +dependencies = [ + "serde", +] [[package]] name = "spdx" -version = "0.10.4" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ef1a0fa1e39ac22972c8db23ff89aea700ab96aa87114e1fb55937a631a0c9" +checksum = "47317bbaf63785b53861e1ae2d11b80d6b624211d42cb20efcd210ee6f8a14bc" dependencies = [ "smallvec", ] @@ -1390,20 +1413,9 @@ checksum = "7c68d531d83ec6c531150584c42a4290911964d5f0d79132b193b67252a23b71" [[package]] name = "syn" -version = "1.0.109" +version = "2.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.60" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "909518bc7b1c9b779f1bbf07f2929d35af9f0f37e47c6e9ef7f9dddc1e1821f3" +checksum = "901fa70d88b9d6c98022e23b4136f9f3e54e4662c3bc1bd1d84a42a9a0f0c1e9" dependencies = [ "proc-macro2", "quote", @@ -1439,22 +1451,22 @@ dependencies = [ [[package]] name = "thiserror" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f0126ad08bff79f29fc3ae6a55cc72352056dfff61e3ff8bb7129476d44b23aa" +checksum = "c546c80d6be4bc6a00c0f01730c08df82eaa7a7a61f11d656526506112cc1709" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" -version = "1.0.59" +version = "1.0.61" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d1cd413b5d558b4c5bf3680e324a6fa5014e7b7c067a51e69dbdf47eb7148b66" +checksum = "46c3384250002a6d5af4d114f2845d37b57521033f30d5c3f46c4d70e1197533" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] @@ -1486,6 +1498,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "typed-arena" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6af6ae20167a9ece4bcb41af5b80f8a1f1df981f6391189ce00fd257af04126a" + [[package]] name = "typenum" version = "1.17.0" @@ -1500,9 +1518,9 @@ checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-width" -version = "0.1.11" +version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" +checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" @@ -1518,9 +1536,9 @@ checksum = "673aac59facbab8a9007c7f6108d11f63b603f7cabff99fabf650fea5c32b861" [[package]] name = "utf8parse" -version = "0.2.1" +version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "version_check" @@ -1569,9 +1587,9 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.202.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bfd106365a7f5f7aa3c1916a98cbb3ad477f5ff96ddb130285a91c6e7429e67a" +checksum = "7b4a05336882dae732ce6bd48b7e11fe597293cb72c13da4f35d7d5f8d53b2a7" dependencies = [ "leb128", ] @@ -1627,7 +1645,7 @@ dependencies = [ "arbitrary", "clap", "env_logger", - "itertools 0.12.1", + "itertools 0.13.0", "log", "num_cpus", "rand", @@ -1686,7 +1704,7 @@ dependencies = [ "cpp_demangle", "diff", "env_logger", - "gimli 0.29.0", + "gimli 0.30.0", "is_executable", "libtest-mimic", "log", @@ -1765,13 +1783,16 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.202.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d6998515d3cf3f8b980ef7c11b29a9b1017d4cf86b99ae93b546992df9931413" +checksum = "07035cc9a9b41e62d3bb3a3815a66ab87c993c06fe1cf6b2a3f2a18499d937db" dependencies = [ + "ahash", "bitflags", + "hashbrown 0.14.5", "indexmap 2.2.6", "semver", + "serde", ] [[package]] @@ -1783,7 +1804,7 @@ dependencies = [ "bitflags", "criterion", "env_logger", - "hashbrown 0.14.3", + "hashbrown 0.14.5", "indexmap 2.2.6", "log", "once_cell", @@ -1797,12 +1818,12 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.202.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ab1cc9508685eef9502e787f4d4123745f5651a1e29aec047645d3cac1e2da7a" +checksum = "ceca8ae6eaa8c7c87b33c25c53bdf299f8c2a764aee1179402ff7652ef3a6859" dependencies = [ "anyhow", - "wasmparser 0.202.0", + "wasmparser 0.209.1", ] [[package]] @@ -1821,75 +1842,83 @@ dependencies = [ [[package]] name = "wasmtime" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a5990663c28d81015ddbb02a068ac1bf396a4ea296eba7125b2dfc7c00cb52e" +checksum = "786d8b5e7a4d54917c5ebe555b9667337e5f93383f49bddaaeec2eba68093b45" dependencies = [ "anyhow", - "bincode", "bumpalo", + "cc", "cfg-if", "encoding_rs", - "gimli 0.28.1", + "hashbrown 0.14.5", "indexmap 2.2.6", "libc", + "libm", "log", - "object 0.33.0", + "mach2", + "memfd", + "memoffset", + "object", "once_cell", "paste", + "postcard", + "psm", "rustix", "semver", "serde", "serde_derive", - "serde_json", + "smallvec", + "sptr", "target-lexicon", - "wasmparser 0.202.0", + "wasmparser 0.209.1", + "wasmtime-asm-macros", "wasmtime-component-macro", "wasmtime-component-util", "wasmtime-cranelift", "wasmtime-environ", "wasmtime-jit-icache-coherence", - "wasmtime-runtime", "wasmtime-slab", + "wasmtime-versioned-export-macros", "wasmtime-winch", "windows-sys", ] [[package]] name = "wasmtime-asm-macros" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "625ee94c72004f3ea0228989c9506596e469517d7d0ed66f7300d1067bdf1ca9" +checksum = "d697d99c341d4a9ffb72f3af7a02124d233eeb59aee010f36d88e97cca553d5e" dependencies = [ "cfg-if", ] [[package]] name = "wasmtime-component-macro" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64f84414a25ee3a624c8b77550f3fe7b5d8145bd3405ca58886ee6900abb6dc2" +checksum = "b29b462b068e73b5b27fae092a27f47e5937cabf6b26be2779c978698a52feca" dependencies = [ "anyhow", "proc-macro2", "quote", - "syn 2.0.60", + "syn", "wasmtime-component-util", "wasmtime-wit-bindgen", - "wit-parser 0.202.0", + "wit-parser 0.209.1", ] [[package]] name = "wasmtime-component-util" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78580bdb4e04c7da3bf98088559ca1d29382668536e4d5c7f2f966d79c390307" +checksum = "f9d2912c53d9054984b380dfbd7579f9c3681b2a73b903a56bd71a1c4f175f1e" [[package]] name = "wasmtime-cranelift" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b60df0ee08c6a536c765f69e9e8205273435b66d02dd401e938769a2622a6c1a" +checksum = "a3975deafea000457ba84355c7c0fce0372937204f77026510b7b454f28a3a65" dependencies = [ "anyhow", "cfg-if", @@ -1901,118 +1930,91 @@ dependencies = [ "cranelift-wasm", "gimli 0.28.1", "log", - "object 0.33.0", + "object", "target-lexicon", "thiserror", - "wasmparser 0.202.0", + "wasmparser 0.209.1", "wasmtime-environ", "wasmtime-versioned-export-macros", ] [[package]] name = "wasmtime-environ" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64ffc1613db69ee47c96738861534f9a405e422a5aa00224fbf5d410b03fb445" +checksum = "f444e900e848b884d8a8a2949b6f5b92af642a3e663ff8fbe78731143a55be61" dependencies = [ "anyhow", - "bincode", "cranelift-entity", "gimli 0.28.1", "indexmap 2.2.6", "log", - "object 0.33.0", + "object", + "postcard", "serde", "serde_derive", "target-lexicon", - "thiserror", - "wasm-encoder 0.202.0", - "wasmparser 0.202.0", - "wasmprinter 0.202.0", + "wasm-encoder 0.209.1", + "wasmparser 0.209.1", + "wasmprinter 0.209.1", "wasmtime-component-util", "wasmtime-types", ] [[package]] name = "wasmtime-jit-icache-coherence" -version = "20.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a9f93a3289057b26dc75eb84d6e60d7694f7d169c7c09597495de6e016a13ff" -dependencies = [ - "cfg-if", - "libc", - "windows-sys", -] - -[[package]] -name = "wasmtime-runtime" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6332a2b0af4224c3ea57c857ad39acd2780ccc2b0c99ba1baa01864d90d7c94" +checksum = "5afe2f0499542f9a4bcfa1b55bfdda803b6ade4e7c93c6b99e0f39dba44b0a91" dependencies = [ "anyhow", - "cc", "cfg-if", - "encoding_rs", - "indexmap 2.2.6", "libc", - "log", - "mach2", - "memfd", - "memoffset", - "paste", - "psm", - "rustix", - "sptr", - "wasmtime-asm-macros", - "wasmtime-environ", - "wasmtime-slab", - "wasmtime-versioned-export-macros", "windows-sys", ] [[package]] name = "wasmtime-slab" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b3655075824a374c536a2b2cc9283bb765fcdf3d58b58587862c48571ad81ef" +checksum = "0a7de1f2bec5bbb35d532e61c85c049dc84ae671df60492f90b954ecf21169e7" [[package]] name = "wasmtime-types" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf64a242b0b9257604181ca28b28a5fcaa4c9ea1d396f76d1d2d1c5b40eef" +checksum = "412463e9000e14cf6856be48628d2213c20c153e29ffc22b036980c892ea6964" dependencies = [ "cranelift-entity", "serde", "serde_derive", - "thiserror", - "wasmparser 0.202.0", + "smallvec", + "wasmparser 0.209.1", ] [[package]] name = "wasmtime-versioned-export-macros" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8561d9e2920db2a175213d557d71c2ac7695831ab472bbfafb9060cd1034684f" +checksum = "de5a9bc4f44ceeb168e9e8e3be4e0b4beb9095b468479663a9e24c667e36826f" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] [[package]] name = "wasmtime-winch" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06b573d14ac846a0fb8c541d8fca6a64acf9a1d176176982472274ab1d2fa5d" +checksum = "ed4db238a0241df2d15f79ad17b3a37a27f2ea6cb885894d81b42ae107544466" dependencies = [ "anyhow", "cranelift-codegen", "gimli 0.28.1", - "object 0.33.0", + "object", "target-lexicon", - "wasmparser 0.202.0", + "wasmparser 0.209.1", "wasmtime-cranelift", "wasmtime-environ", "winch-codegen", @@ -2020,14 +2022,14 @@ dependencies = [ [[package]] name = "wasmtime-wit-bindgen" -version = "20.0.0" +version = "22.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "595bc7bb3b0ff4aa00fab718c323ea552c3034d77abc821a35112552f2ea487a" +checksum = "70dc077306b38288262e5ba01d4b21532a6987416cdc0aedf04bb06c22a68fdc" dependencies = [ "anyhow", "heck 0.4.1", "indexmap 2.2.6", - "wit-parser 0.202.0", + "wit-parser 0.209.1", ] [[package]] @@ -2036,7 +2038,7 @@ version = "211.0.1" dependencies = [ "anyhow", "bumpalo", - "gimli 0.29.0", + "gimli 0.30.0", "leb128", "libtest-mimic", "memchr", @@ -2087,9 +2089,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "winch-codegen" -version = "0.18.0" +version = "0.20.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb23450977f9d4a23c02439cf6899340b2d68887b19465c5682740d9cc37d52e" +checksum = "85c6915884e731b2db0d8cf08cb64474cb69221a161675fd3c135f91febc3daa" dependencies = [ "anyhow", "cranelift-codegen", @@ -2097,7 +2099,7 @@ dependencies = [ "regalloc2", "smallvec", "target-lexicon", - "wasmparser 0.202.0", + "wasmparser 0.209.1", "wasmtime-cranelift", "wasmtime-environ", ] @@ -2220,9 +2222,9 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.202.0" +version = "0.209.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "744237b488352f4f27bca05a10acb79474415951c450e52ebd0da784c1df2bcc" +checksum = "3e79b9e3c0b6bb589dec46317e645851e0db2734c44e2be5e251b03ff4a51269" dependencies = [ "anyhow", "id-arena", @@ -2233,7 +2235,7 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.202.0", + "wasmparser 0.209.1", ] [[package]] @@ -2290,20 +2292,20 @@ checksum = "09041cd90cf85f7f8b2df60c646f853b7f535ce68f85244eb6731cf89fa498ec" [[package]] name = "zerocopy" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "74d4d3961e53fa4c9a25a8637fc2bfaf2595b3d3ae34875568a5cf64787716be" +checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087" dependencies = [ "zerocopy-derive", ] [[package]] name = "zerocopy-derive" -version = "0.7.32" +version = "0.7.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ce1b18ccd8e73a9321186f97e46f9f04b778851177567b1975109d26a08d2a6" +checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b" dependencies = [ "proc-macro2", "quote", - "syn 2.0.60", + "syn", ] diff --git a/Cargo.toml b/Cargo.toml index 7f72e692a9..27a220998d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,7 +74,7 @@ rayon = "1.3" serde = { version = "1.0.166", default-features = false, features = ['alloc'] } serde_derive = "1.0.166" serde_json = { version = "1" } -wasmtime = { version = "20.0.0", default-features = false, features = ['cranelift', 'component-model', 'runtime', 'gc'] } +wasmtime = { version = "22.0.0", default-features = false, features = ['cranelift', 'component-model', 'runtime', 'gc'] } url = "2.0.0" pretty_assertions = "1.3.0" semver = { version = "1.0.0", default-features = false } @@ -85,7 +85,7 @@ hashbrown = { version = "0.14.3", default-features = false, features = ['ahash'] ahash = { version = "0.8.11", default-features = false } termcolor = "1.2.0" indoc = "2.0.5" -gimli = "0.29.0" +gimli = "0.30.0" wasm-compose = { version = "0.211.1", path = "crates/wasm-compose" } wasm-encoder = { version = "0.211.1", path = "crates/wasm-encoder" } @@ -156,7 +156,7 @@ wasm-metadata = { workspace = true, features = ["clap"], optional = true } wit-smith = { workspace = true, features = ["clap"], optional = true } # Dependencies of `addr2line` -addr2line = { version = "0.22.0", optional = true } +addr2line = { version = "0.23.0", optional = true } gimli = { workspace = true, optional = true } [target.'cfg(not(target_family = "wasm"))'.dependencies] diff --git a/crates/fuzz-stats/src/dummy.rs b/crates/fuzz-stats/src/dummy.rs index 7075ef56f9..757ce44733 100644 --- a/crates/fuzz-stats/src/dummy.rs +++ b/crates/fuzz-stats/src/dummy.rs @@ -47,10 +47,16 @@ pub fn dummy_value(val_ty: ValType) -> Val { pub fn dummy_ref(ty: &RefType) -> Ref { assert!(ty.is_nullable()); match ty.heap_type() { - HeapType::Extern => Ref::Extern(None), - HeapType::NoFunc | HeapType::Func => Ref::Func(None), - HeapType::Any | HeapType::I31 | HeapType::None => Ref::Any(None), - HeapType::Concrete(_) => unimplemented!(), + HeapType::Extern | HeapType::NoExtern => Ref::Extern(None), + HeapType::NoFunc | HeapType::Func | HeapType::ConcreteFunc(_) => Ref::Func(None), + HeapType::Any + | HeapType::I31 + | HeapType::None + | HeapType::Eq + | HeapType::Array + | HeapType::Struct + | HeapType::ConcreteArray(_) + | HeapType::ConcreteStruct(_) => Ref::Any(None), } } diff --git a/crates/wasm-mutate-stats/Cargo.toml b/crates/wasm-mutate-stats/Cargo.toml index 04db6f0984..cc6e380106 100644 --- a/crates/wasm-mutate-stats/Cargo.toml +++ b/crates/wasm-mutate-stats/Cargo.toml @@ -14,10 +14,10 @@ num_cpus = { workspace = true } rand = { workspace = true } wasm-mutate = { workspace = true } wasmprinter = { workspace = true } -wasmparser = { workspace = true } +wasmparser = { workspace = true, features = ['validate'] } wasmtime = { workspace = true } env_logger = { workspace = true } -itertools = "0.12.0" +itertools = "0.13.0" clap = { workspace = true } log = { workspace = true } From 3f4960adab1ef083bffbb0aecb578a1e45a01f49 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Jun 2024 13:36:46 -0500 Subject: [PATCH 36/58] Fix a panic when validating `return_call` after `end` (#1641) This commit fixes a mistake that was introduced in #1587 which was first released as 1.210.0 as part of `wasm-tools`. In #1587 control flow was restructured in the validator to expose an out-of-bounds access of `self.control` when a function has instructions after the final `end` instruction. The fix in this commit is to apply the same logic as `check_return` which is to explicitly check for the length of the `control` stack and return an error. This bug comes from how instructions-after-`end` are detected in the validator. Notably this erroneous condition is checked when the functions reaches EOF, not when the control stack is emptied. This is to avoid checking at all instructions that the control stack has a length greater than one and to instead defer that check to only instructions that need it. This susprising behavior, though, ended up leading to this bug. --- crates/wasmparser/src/validator/operators.rs | 3 +++ tests/local/tail-call-after-end.wast | 8 ++++++++ tests/snapshots/local/tail-call-after-end.wast.json | 12 ++++++++++++ 3 files changed, 23 insertions(+) create mode 100644 tests/local/tail-call-after-end.wast create mode 100644 tests/snapshots/local/tail-call-after-end.wast.json diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs index 7245fe53bd..52dceebb02 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -904,6 +904,9 @@ where /// Check that the given type has the same result types as the current /// function's results. fn check_func_type_same_results(&self, callee_ty: &FuncType) -> Result<()> { + if self.control.is_empty() { + return Err(self.err_beyond_end(self.offset)); + } let caller_rets = self.results(self.control[0].block_type)?; if callee_ty.results().len() != caller_rets.len() || !caller_rets diff --git a/tests/local/tail-call-after-end.wast b/tests/local/tail-call-after-end.wast new file mode 100644 index 0000000000..0a7827aed0 --- /dev/null +++ b/tests/local/tail-call-after-end.wast @@ -0,0 +1,8 @@ +(assert_invalid + (module + (func + end + return_call 0 + ) + ) + "operators remaining after end of function") diff --git a/tests/snapshots/local/tail-call-after-end.wast.json b/tests/snapshots/local/tail-call-after-end.wast.json new file mode 100644 index 0000000000..fac043db26 --- /dev/null +++ b/tests/snapshots/local/tail-call-after-end.wast.json @@ -0,0 +1,12 @@ +{ + "source_filename": "tests/local/tail-call-after-end.wast", + "commands": [ + { + "type": "assert_invalid", + "line": 2, + "filename": "tail-call-after-end.0.wasm", + "text": "operators remaining after end of function", + "module_type": "binary" + } + ] +} \ No newline at end of file From 771b2bddd84634736ba659efd11a8812c26aee45 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 27 Jun 2024 13:37:32 -0500 Subject: [PATCH 37/58] Fix newline separators between invalid `end` instructions (#1640) This commit fixes a bug from a prior refactoring where when there were too many `end` instructions in a function, which is invalid, then printing would confusingly "fuse" instructions together by not printing a separator. --- crates/wasmprinter/src/operator.rs | 2 +- tests/cli/print-no-panic-double-end.wat.stdout | 4 +++- tests/cli/print-with-too-many-ends.wat | 5 +++++ tests/cli/print-with-too-many-ends.wat.stdout | 9 +++++++++ 4 files changed, 18 insertions(+), 2 deletions(-) create mode 100644 tests/cli/print-with-too-many-ends.wat create mode 100644 tests/cli/print-with-too-many-ends.wat.stdout diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs index 2111e80dfe..fa7e2ca456 100644 --- a/crates/wasmprinter/src/operator.rs +++ b/crates/wasmprinter/src/operator.rs @@ -71,8 +71,8 @@ impl<'printer, 'state, 'a, 'b> PrintOperator<'printer, 'state, 'a, 'b> { fn block_end(&mut self) -> Result<()> { if self.printer.nesting > self.nesting_start { self.printer.nesting -= 1; - self.separator()?; } + self.separator()?; Ok(()) } diff --git a/tests/cli/print-no-panic-double-end.wat.stdout b/tests/cli/print-no-panic-double-end.wat.stdout index 828cd76866..3d0ed6beb9 100644 --- a/tests/cli/print-no-panic-double-end.wat.stdout +++ b/tests/cli/print-no-panic-double-end.wat.stdout @@ -1,4 +1,6 @@ (module (type (;0;) (func)) - (func (;0;) (type 0)end) + (func (;0;) (type 0) + end + ) ) diff --git a/tests/cli/print-with-too-many-ends.wat b/tests/cli/print-with-too-many-ends.wat new file mode 100644 index 0000000000..f498a40ef9 --- /dev/null +++ b/tests/cli/print-with-too-many-ends.wat @@ -0,0 +1,5 @@ +;; RUN: print % + +(module + (func end i32.const 0 drop end) +) diff --git a/tests/cli/print-with-too-many-ends.wat.stdout b/tests/cli/print-with-too-many-ends.wat.stdout new file mode 100644 index 0000000000..b06719885d --- /dev/null +++ b/tests/cli/print-with-too-many-ends.wat.stdout @@ -0,0 +1,9 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + end + i32.const 0 + drop + end + ) +) From 1cf71f9a9bbb4706e1e5d21ad2a12586a3911199 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 20:14:12 +0000 Subject: [PATCH 38/58] Release wasm-tools 1.212.0 (#1645) * Release wasm-tools 1.212.0 [automatically-tag-and-release-this-commit] * Remove dwarf test The output changes between versions so not going to be able to easily update --------- Co-authored-by: Auto Release Process Co-authored-by: Alex Crichton --- Cargo.lock | 114 +++++++++++++++--------------- Cargo.toml | 32 ++++----- crates/wast/Cargo.toml | 2 +- crates/wat/Cargo.toml | 2 +- tests/cli/dwarf-simple.wat | 11 --- tests/cli/dwarf-simple.wat.stdout | 14 ---- 6 files changed, 75 insertions(+), 100 deletions(-) delete mode 100644 tests/cli/dwarf-simple.wat delete mode 100644 tests/cli/dwarf-simple.wat.stdout diff --git a/Cargo.lock b/Cargo.lock index 5b5035567a..7fa6f3e0f7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,7 +294,7 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" name = "component" version = "0.0.0" dependencies = [ - "wasmprinter 0.211.1", + "wasmprinter 0.212.0", "wat", "wit-bindgen-rt", ] @@ -1564,7 +1564,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-compose" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "glob", @@ -1578,9 +1578,9 @@ dependencies = [ "serde_derive", "serde_yaml", "smallvec", - "wasm-encoder 0.211.1", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasm-encoder 0.212.0", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wat", "wit-component", ] @@ -1596,17 +1596,17 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "leb128", "tempfile", - "wasmparser 0.211.1", + "wasmparser 0.212.0", ] [[package]] name = "wasm-metadata" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "clap", @@ -1615,14 +1615,14 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.211.1", - "wasmparser 0.211.1", + "wasm-encoder 0.212.0", + "wasmparser 0.212.0", "wat", ] [[package]] name = "wasm-mutate" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "clap", @@ -1631,9 +1631,9 @@ dependencies = [ "log", "rand", "thiserror", - "wasm-encoder 0.211.1", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasm-encoder 0.212.0", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wat", ] @@ -1650,14 +1650,14 @@ dependencies = [ "num_cpus", "rand", "wasm-mutate", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wasmtime", ] [[package]] name = "wasm-shrink" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "blake3", @@ -1666,14 +1666,14 @@ dependencies = [ "log", "rand", "wasm-mutate", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wat", ] [[package]] name = "wasm-smith" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "arbitrary", @@ -1686,15 +1686,15 @@ dependencies = [ "rand", "serde", "serde_derive", - "wasm-encoder 0.211.1", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasm-encoder 0.212.0", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wat", ] [[package]] name = "wasm-tools" -version = "1.211.1" +version = "1.212.0" dependencies = [ "addr2line", "anyhow", @@ -1718,18 +1718,18 @@ dependencies = [ "tempfile", "termcolor", "wasm-compose", - "wasm-encoder 0.211.1", + "wasm-encoder 0.212.0", "wasm-metadata", "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wast", "wat", "wit-component", "wit-encoder", - "wit-parser 0.211.1", + "wit-parser 0.212.0", "wit-smith", ] @@ -1741,8 +1741,8 @@ dependencies = [ "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wast", "wat", ] @@ -1757,28 +1757,28 @@ dependencies = [ "libfuzzer-sys", "log", "tempfile", - "wasm-encoder 0.211.1", + "wasm-encoder 0.212.0", "wasm-mutate", "wasm-smith", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wasmtime", "wast", "wat", "wit-component", - "wit-parser 0.211.1", + "wit-parser 0.212.0", "wit-smith", ] [[package]] name = "wasm-wave" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "indexmap 2.2.6", "logos", "thiserror", - "wit-parser 0.211.1", + "wit-parser 0.212.0", ] [[package]] @@ -1797,7 +1797,7 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.211.1" +version = "0.212.0" dependencies = [ "ahash", "anyhow", @@ -1811,7 +1811,7 @@ dependencies = [ "rayon", "semver", "serde", - "wasm-encoder 0.211.1", + "wasm-encoder 0.212.0", "wast", "wat", ] @@ -1828,14 +1828,14 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "diff", "rayon", "tempfile", "termcolor", - "wasmparser 0.211.1", + "wasmparser 0.212.0", "wast", "wat", ] @@ -2034,7 +2034,7 @@ dependencies = [ [[package]] name = "wast" -version = "211.0.1" +version = "212.0.0" dependencies = [ "anyhow", "bumpalo", @@ -2044,14 +2044,14 @@ dependencies = [ "memchr", "rand", "unicode-width", - "wasm-encoder 0.211.1", - "wasmparser 0.211.1", + "wasm-encoder 0.212.0", + "wasmparser 0.212.0", "wat", ] [[package]] name = "wat" -version = "1.211.1" +version = "1.212.0" dependencies = [ "wast", ] @@ -2188,7 +2188,7 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "bitflags", @@ -2201,19 +2201,19 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.211.1", + "wasm-encoder 0.212.0", "wasm-metadata", - "wasmparser 0.211.1", - "wasmprinter 0.211.1", + "wasmparser 0.212.0", + "wasmprinter 0.212.0", "wasmtime", "wast", "wat", - "wit-parser 0.211.1", + "wit-parser 0.212.0", ] [[package]] name = "wit-encoder" -version = "0.211.1" +version = "0.212.0" dependencies = [ "indoc", "pretty_assertions", @@ -2240,7 +2240,7 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.211.1" +version = "0.212.0" dependencies = [ "anyhow", "env_logger", @@ -2254,9 +2254,9 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.211.1", + "wasmparser 0.212.0", "wat", - "wit-parser 0.211.1", + "wit-parser 0.212.0", ] [[package]] @@ -2267,13 +2267,13 @@ dependencies = [ "env_logger", "libfuzzer-sys", "log", - "wasmprinter 0.211.1", - "wit-parser 0.211.1", + "wasmprinter 0.212.0", + "wit-parser 0.212.0", ] [[package]] name = "wit-smith" -version = "0.211.1" +version = "0.212.0" dependencies = [ "arbitrary", "clap", @@ -2281,7 +2281,7 @@ dependencies = [ "log", "semver", "wit-component", - "wit-parser 0.211.1", + "wit-parser 0.212.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index 27a220998d..b9ba2fe4c3 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-tools" -version = "1.211.1" +version = "1.212.0" authors = ["The Wasmtime Project Developers"] edition.workspace = true description = "CLI tools for interoperating with WebAssembly files" @@ -52,7 +52,7 @@ all = "allow" [workspace.package] edition = '2021' -version = "0.211.1" +version = "0.212.0" # Current policy for wasm-tools is the same as Wasmtime which is that this # number can be no larger than the current stable release of Rust minus 2. rust-version = "1.76.0" @@ -87,20 +87,20 @@ termcolor = "1.2.0" indoc = "2.0.5" gimli = "0.30.0" -wasm-compose = { version = "0.211.1", path = "crates/wasm-compose" } -wasm-encoder = { version = "0.211.1", path = "crates/wasm-encoder" } -wasm-metadata = { version = "0.211.1", path = "crates/wasm-metadata" } -wasm-mutate = { version = "0.211.1", path = "crates/wasm-mutate" } -wasm-shrink = { version = "0.211.1", path = "crates/wasm-shrink" } -wasm-smith = { version = "0.211.1", path = "crates/wasm-smith" } -wasmparser = { version = "0.211.1", path = "crates/wasmparser", default-features = false, features = ['std'] } -wasmprinter = { version = "0.211.1", path = "crates/wasmprinter" } -wast = { version = "211.0.1", path = "crates/wast" } -wat = { version = "1.211.1", path = "crates/wat" } -wit-component = { version = "0.211.1", path = "crates/wit-component" } -wit-encoder = { version = "0.211.1", path = "crates/wit-encoder" } -wit-parser = { version = "0.211.1", path = "crates/wit-parser" } -wit-smith = { version = "0.211.1", path = "crates/wit-smith" } +wasm-compose = { version = "0.212.0", path = "crates/wasm-compose" } +wasm-encoder = { version = "0.212.0", path = "crates/wasm-encoder" } +wasm-metadata = { version = "0.212.0", path = "crates/wasm-metadata" } +wasm-mutate = { version = "0.212.0", path = "crates/wasm-mutate" } +wasm-shrink = { version = "0.212.0", path = "crates/wasm-shrink" } +wasm-smith = { version = "0.212.0", path = "crates/wasm-smith" } +wasmparser = { version = "0.212.0", path = "crates/wasmparser", default-features = false, features = ['std'] } +wasmprinter = { version = "0.212.0", path = "crates/wasmprinter" } +wast = { version = "212.0.0", path = "crates/wast" } +wat = { version = "1.212.0", path = "crates/wat" } +wit-component = { version = "0.212.0", path = "crates/wit-component" } +wit-encoder = { version = "0.212.0", path = "crates/wit-encoder" } +wit-parser = { version = "0.212.0", path = "crates/wit-parser" } +wit-smith = { version = "0.212.0", path = "crates/wit-smith" } [dependencies] anyhow = { workspace = true } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index 2eaed8e554..ce098c47be 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wast" -version = "211.0.1" +version = "212.0.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml index fa8901c3de..489b4c6071 100644 --- a/crates/wat/Cargo.toml +++ b/crates/wat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wat" -version = "1.211.1" +version = "1.212.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" diff --git a/tests/cli/dwarf-simple.wat b/tests/cli/dwarf-simple.wat deleted file mode 100644 index 7739ff37a6..0000000000 --- a/tests/cli/dwarf-simple.wat +++ /dev/null @@ -1,11 +0,0 @@ -;; RUN: print --generate-dwarf lines % - -(module - (func) - (func $b) - (func $"c d") - (func - i32.const 0 - drop - ) -) diff --git a/tests/cli/dwarf-simple.wat.stdout b/tests/cli/dwarf-simple.wat.stdout deleted file mode 100644 index f5e39467f4..0000000000 --- a/tests/cli/dwarf-simple.wat.stdout +++ /dev/null @@ -1,14 +0,0 @@ -(module - (type (;0;) (func)) - (func (;0;) (type 0)) - (func $b (;1;) (type 0)) - (func $"c d" (;2;) (type 0)) - (func (;3;) (type 0) - i32.const 0 - drop - ) - (@custom ".debug_abbrev" (after code) "\01\11\01%\08\13\0f\03\0e\1b\0e\11\06\12\06\10\17\00\00\02.\00\03\08:\0f;\0f9\0f?\19\11\01\12\06\00\00\00") - (@custom ".debug_str" (after code) "tests/cli\00dwarf-simple.wat\00") - (@custom ".debug_line" (after code) "k\00\00\00\05\00\04\00*\00\00\00\01\01\01\fb\0e\0d\00\01\01\01\01\00\00\00\01\00\00\01\01\01\0e\01\00\00\00\00\02\01\0e\02\0f\02\0a\00\00\00\00\0a\00\00\00\00\00\05\02\02\00\00\00 \02\01\00\01\01\00\05\02\05\00\00\00 \02\01\00\01\01\00\05\02\08\00\00\00 \02\01\00\01\01\00\05\02\0b\00\00\00\0a\05\05'/ \02\01\00\01\01") - (@custom ".debug_info" (after code) "\84\00\00\00\05\00\01\04\00\00\00\00\01wast 211.0.1\00\02\0a\00\00\00\00\00\00\00\00\00\00\00\10\00\00\00\00\00\00\00\02wasm-function[0]\00\01\04\04\02\00\00\00\02\00\00\00\02b\00\01\05\04\05\00\00\00\02\00\00\00\02c d\00\01\06\04\08\00\00\00\02\00\00\00\02wasm-function[3]\00\01\07\04\0b\00\00\00\05\00\00\00\00") -) From 3ca2d7c67c42a0c2acd37a5d891665ded713bf61 Mon Sep 17 00:00:00 2001 From: Trevor Elliott Date: Wed, 3 Jul 2024 09:23:54 -0700 Subject: [PATCH 39/58] Switch from centos 7 to almalinux 8 for binary-compatible-builds (#1651) --- ci/docker/x86_64-linux/Dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ci/docker/x86_64-linux/Dockerfile b/ci/docker/x86_64-linux/Dockerfile index 89ddc2b282..7b4ac6b2e3 100644 --- a/ci/docker/x86_64-linux/Dockerfile +++ b/ci/docker/x86_64-linux/Dockerfile @@ -1,5 +1,5 @@ -FROM centos:7 +FROM almalinux:8 -RUN yum install -y git gcc +RUN dnf install -y git gcc ENV PATH=$PATH:/rust/bin From 6fc1601a4a565f282d9ba013d4857eec09200e10 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Wed, 3 Jul 2024 09:47:04 -0700 Subject: [PATCH 40/58] threads: add `shared` composite types (#1646) * threads: add `shared` composite types This continues the work of #1600 to expand the surface area of a WebAssembly module that can be marked `shared`. This is for support of the shared-everything-threads [proposal]. The key change is to convert `CompositeType` in each of the crates from an `enum` to a `struct` in order to add a `shared` boolean field. The original variants (`Func`, `Array`, `Struct`) are moved to a separate `enum` and fill an `inner` field. Propagating this throughout the code base is the bulk of this PR, with occasional, small refactors to avoid larger match patterns. [proposal]: https://github.com/WebAssembly/shared-everything-threads * Add `wast` parsing of shared composite types This finishes the work of the previous commit, allowing us to express shared composite types in the text format: `(type $t (shared (...)))`. This also finishes up the heap type testing by allowing the tests to express concrete heap types (e.g., `ref $t`). * Fix some un-formatted files * Add shared check to rec groups For now, this explicitly checks that both subtypes are shared. But it isn't yet clear whether an shared type might be able to match an unshared one. Once that is resolved, this might need to be updated. * Add @tlively `struct` and `array` tests These proposed spec tests are slightly modified from @tlively's originals: - they use this crate's choice of error message - they avoid the shorthand form of a single-field struct, ~(struct )` because this crate does not yet know how to parse that - they avoid the shorthand form for `(elem)` for the same reason All of these minor issues can be resolved later. --- crates/wasm-encoder/src/core/types.rs | 43 ++-- crates/wasm-encoder/src/reencode.rs | 18 +- crates/wasm-smith/src/component.rs | 22 +- crates/wasm-smith/src/component/encode.rs | 7 +- crates/wasm-smith/src/core.rs | 237 ++++++++++-------- crates/wasm-smith/src/core/code_builder.rs | 38 +-- crates/wasm-smith/src/core/encode.rs | 26 +- crates/wasm-smith/tests/exports.rs | 6 +- crates/wasmparser/src/readers/core/types.rs | 75 +++--- .../src/readers/core/types/matches.rs | 22 +- crates/wasmparser/src/validator/component.rs | 26 +- crates/wasmparser/src/validator/core.rs | 67 +++-- crates/wasmparser/src/validator/func.rs | 5 +- crates/wasmparser/src/validator/operators.rs | 14 +- crates/wasmparser/src/validator/types.rs | 86 +++---- crates/wasmprinter/src/lib.rs | 32 ++- crates/wast/src/component/binary.rs | 29 ++- crates/wast/src/component/expand.rs | 8 +- crates/wast/src/core/binary.rs | 17 +- crates/wast/src/core/binary/dwarf.rs | 6 +- crates/wast/src/core/resolve/names.rs | 18 +- crates/wast/src/core/resolve/types.rs | 17 +- crates/wast/src/core/types.rs | 40 ++- tests/cli/dump-branch-hints.wat.stdout | 2 +- tests/cli/dump-llvm-object.wat.stdout | 12 +- tests/cli/dump/alias.wat.stdout | 2 +- tests/cli/dump/alias2.wat.stdout | 4 +- tests/cli/dump/blockty.wat.stdout | 10 +- tests/cli/dump/bundled.wat.stdout | 10 +- .../dump/component-expand-bundle.wat.stdout | 4 +- tests/cli/dump/import-modules.wat.stdout | 4 +- tests/cli/dump/module-types.wat.stdout | 2 +- tests/cli/dump/names.wat.stdout | 2 +- tests/cli/dump/rec-group.wat.stdout | 12 +- tests/cli/dump/select.wat.stdout | 2 +- tests/cli/dump/simple.wat.stdout | 4 +- .../shared-everything-threads/array.wast | 115 +++++++++ .../global-heap-types.wast | 95 ++++++- .../shared-everything-threads/struct.wast | 92 +++++++ .../shared-everything-threads/array.wast.json | 60 +++++ .../array.wast/0.print | 10 + .../array.wast/6.print | 3 + .../array.wast/7.print | 3 + .../array.wast/8.print | 97 +++++++ .../global-heap-types.wast.json | 174 +++++++++---- .../global-heap-types.wast/48.print | 9 + .../global-heap-types.wast/52.print | 9 + .../global-heap-types.wast/56.print | 9 + .../struct.wast.json | 60 +++++ .../struct.wast/0.print | 10 + .../struct.wast/6.print | 3 + .../struct.wast/7.print | 3 + .../struct.wast/8.print | 37 +++ 53 files changed, 1281 insertions(+), 437 deletions(-) create mode 100644 tests/local/shared-everything-threads/array.wast create mode 100644 tests/local/shared-everything-threads/struct.wast create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast.json create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/0.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/6.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/7.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/8.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/48.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/52.print create mode 100644 tests/snapshots/local/shared-everything-threads/global-heap-types.wast/56.print create mode 100644 tests/snapshots/local/shared-everything-threads/struct.wast.json create mode 100644 tests/snapshots/local/shared-everything-threads/struct.wast/0.print create mode 100644 tests/snapshots/local/shared-everything-threads/struct.wast/6.print create mode 100644 tests/snapshots/local/shared-everything-threads/struct.wast/7.print create mode 100644 tests/snapshots/local/shared-everything-threads/struct.wast/8.print diff --git a/crates/wasm-encoder/src/core/types.rs b/crates/wasm-encoder/src/core/types.rs index de6705f913..23859d06e9 100644 --- a/crates/wasm-encoder/src/core/types.rs +++ b/crates/wasm-encoder/src/core/types.rs @@ -25,33 +25,46 @@ impl Encode for SubType { /// Represents a composite type in a WebAssembly module. #[derive(Debug, Clone)] -pub enum CompositeType { - /// The type is for a function. - Func(FuncType), - /// The type is for an array. - Array(ArrayType), - /// The type is for a struct. - Struct(StructType), +pub struct CompositeType { + /// The type defined inside the composite type. + pub inner: CompositeInnerType, + /// Whether the type is shared. This is part of the + /// shared-everything-threads proposal. + pub shared: bool, } impl Encode for CompositeType { fn encode(&self, sink: &mut Vec) { - match self { - CompositeType::Func(ty) => TypeSection::encode_function( + if self.shared { + sink.push(0x65); + } + match &self.inner { + CompositeInnerType::Func(ty) => TypeSection::encode_function( sink, ty.params().iter().copied(), ty.results().iter().copied(), ), - CompositeType::Array(ArrayType(ty)) => { + CompositeInnerType::Array(ArrayType(ty)) => { TypeSection::encode_array(sink, &ty.element_type, ty.mutable) } - CompositeType::Struct(ty) => { + CompositeInnerType::Struct(ty) => { TypeSection::encode_struct(sink, ty.fields.iter().cloned()) } } } } +/// A [`CompositeType`] can contain one of these types. +#[derive(Debug, Clone)] +pub enum CompositeInnerType { + /// The type is for a function. + Func(FuncType), + /// The type is for an array. + Array(ArrayType), + /// The type is for a struct. + Struct(StructType), +} + /// Represents a type of a function in a WebAssembly module. #[derive(Debug, Clone, Eq, PartialEq, Hash)] pub struct FuncType { @@ -635,10 +648,9 @@ impl Section for TypeSection { #[cfg(test)] mod tests { - use wasmparser::WasmFeatures; - use super::*; use crate::Module; + use wasmparser::WasmFeatures; #[test] fn func_types_dont_require_wasm_gc() { @@ -646,7 +658,10 @@ mod tests { types.subtype(&SubType { is_final: true, supertype_idx: None, - composite_type: CompositeType::Func(FuncType::new([], [])), + composite_type: CompositeType { + inner: CompositeInnerType::Func(FuncType::new([], [])), + shared: false, + }, }); let mut module = Module::new(); diff --git a/crates/wasm-encoder/src/reencode.rs b/crates/wasm-encoder/src/reencode.rs index 8624b81911..5c6a67116f 100644 --- a/crates/wasm-encoder/src/reencode.rs +++ b/crates/wasm-encoder/src/reencode.rs @@ -1001,16 +1001,20 @@ pub mod utils { reencoder: &mut T, composite_ty: wasmparser::CompositeType, ) -> Result> { - Ok(match composite_ty { - wasmparser::CompositeType::Func(f) => { - crate::CompositeType::Func(reencoder.func_type(f)?) + let inner = match composite_ty.inner { + wasmparser::CompositeInnerType::Func(f) => { + crate::CompositeInnerType::Func(reencoder.func_type(f)?) } - wasmparser::CompositeType::Array(a) => { - crate::CompositeType::Array(reencoder.array_type(a)?) + wasmparser::CompositeInnerType::Array(a) => { + crate::CompositeInnerType::Array(reencoder.array_type(a)?) } - wasmparser::CompositeType::Struct(s) => { - crate::CompositeType::Struct(reencoder.struct_type(s)?) + wasmparser::CompositeInnerType::Struct(s) => { + crate::CompositeInnerType::Struct(reencoder.struct_type(s)?) } + }; + Ok(crate::CompositeType { + inner, + shared: composite_ty.shared, }) } diff --git a/crates/wasm-smith/src/component.rs b/crates/wasm-smith/src/component.rs index 35bf93231b..9428a5f5a3 100644 --- a/crates/wasm-smith/src/component.rs +++ b/crates/wasm-smith/src/component.rs @@ -714,9 +714,12 @@ impl ComponentBuilder { }); let ty_idx = u32::try_from(types.len()).unwrap(); types.push(realloc_ty.clone()); - defs.push(ModuleTypeDef::TypeDef(crate::core::CompositeType::Func( - realloc_ty.clone(), - ))); + defs.push(ModuleTypeDef::TypeDef( + crate::core::CompositeType::new_func( + realloc_ty.clone(), + false, // TODO: handle shared + ), + )); defs.push(ModuleTypeDef::Export( "canonical_abi_realloc".into(), crate::core::EntityType::Func(ty_idx, realloc_ty), @@ -737,9 +740,12 @@ impl ComponentBuilder { }); let ty_idx = u32::try_from(types.len()).unwrap(); types.push(free_ty.clone()); - defs.push(ModuleTypeDef::TypeDef(crate::core::CompositeType::Func( - free_ty.clone(), - ))); + defs.push(ModuleTypeDef::TypeDef( + crate::core::CompositeType::new_func( + free_ty.clone(), + false, // TODO: handle shared + ), + )); defs.push(ModuleTypeDef::Export( "canonical_abi_free".into(), crate::core::EntityType::Func(ty_idx, free_ty), @@ -832,7 +838,9 @@ impl ComponentBuilder { 0, )?; types.push(ty.clone()); - defs.push(ModuleTypeDef::TypeDef(crate::core::CompositeType::Func(ty))); + defs.push(ModuleTypeDef::TypeDef( + crate::core::CompositeType::new_func(ty, false), + )); // TODO: handle shared } // Alias diff --git a/crates/wasm-smith/src/component/encode.rs b/crates/wasm-smith/src/component/encode.rs index 7963ecfae5..3d53f2bfb5 100644 --- a/crates/wasm-smith/src/component/encode.rs +++ b/crates/wasm-smith/src/component/encode.rs @@ -1,5 +1,7 @@ use std::borrow::Cow; +use crate::core::{CompositeInnerType, CompositeType}; + use super::*; use wasm_encoder::{ComponentExportKind, ComponentOuterAliasKind, ExportKind}; @@ -124,7 +126,10 @@ impl CoreType { let mut enc_mod_ty = wasm_encoder::ModuleType::new(); for def in &mod_ty.defs { match def { - ModuleTypeDef::TypeDef(crate::core::CompositeType::Func(func_ty)) => { + ModuleTypeDef::TypeDef(CompositeType { + inner: CompositeInnerType::Func(func_ty), + .. + }) => { enc_mod_ty.ty().function( func_ty.params.iter().copied(), func_ty.results.iter().copied(), diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index 6dd32456cf..43acc72b11 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -265,33 +265,62 @@ impl SubType { } #[derive(Clone, Debug, PartialEq, Eq, Hash)] -pub(crate) enum CompositeType { - Array(ArrayType), - Func(Rc), - Struct(StructType), +pub(crate) struct CompositeType { + pub inner: CompositeInnerType, + pub shared: bool, } impl CompositeType { - fn unwrap_struct(&self) -> &StructType { - match self { - CompositeType::Struct(s) => s, - _ => panic!("not a struct"), + pub(crate) fn new_func(func: Rc, shared: bool) -> Self { + Self { + inner: CompositeInnerType::Func(func), + shared, } } fn unwrap_func(&self) -> &Rc { - match self { - CompositeType::Func(f) => f, + match &self.inner { + CompositeInnerType::Func(f) => f, _ => panic!("not a func"), } } fn unwrap_array(&self) -> &ArrayType { - match self { - CompositeType::Array(a) => a, + match &self.inner { + CompositeInnerType::Array(a) => a, _ => panic!("not an array"), } } + + fn unwrap_struct(&self) -> &StructType { + match &self.inner { + CompositeInnerType::Struct(s) => s, + _ => panic!("not a struct"), + } + } +} + +impl From<&CompositeType> for wasm_encoder::CompositeType { + fn from(ty: &CompositeType) -> Self { + let inner = match &ty.inner { + CompositeInnerType::Array(a) => wasm_encoder::CompositeInnerType::Array(a.clone()), + CompositeInnerType::Func(f) => wasm_encoder::CompositeInnerType::Func( + wasm_encoder::FuncType::new(f.params.iter().cloned(), f.results.iter().cloned()), + ), + CompositeInnerType::Struct(s) => wasm_encoder::CompositeInnerType::Struct(s.clone()), + }; + wasm_encoder::CompositeType { + shared: ty.shared, + inner, + } + } +} + +#[derive(Clone, Debug, PartialEq, Eq, Hash)] +pub(crate) enum CompositeInnerType { + Array(ArrayType), + Func(Rc), + Struct(StructType), } /// A function signature. @@ -450,6 +479,7 @@ impl Module { fn heap_type_is_sub_type(&self, a: HeapType, b: HeapType) -> bool { use AbstractHeapType::*; + use CompositeInnerType as CT; use HeapType as HT; match (a, b) { (a, b) if a == b => true, @@ -477,39 +507,27 @@ impl Module { } (HT::Concrete(a), HT::Abstract { shared, ty }) => { - if shared { - // TODO: handle shared - todo!("check shared-ness of concrete type"); + let a_ty = &self.ty(a).composite_type; + if a_ty.shared == shared { + return false; } match ty { - Eq | Any => matches!( - self.ty(a).composite_type, - CompositeType::Array(_) | CompositeType::Struct(_) - ), - Struct => { - matches!(self.ty(a).composite_type, CompositeType::Struct(_)) - } - Array => { - matches!(self.ty(a).composite_type, CompositeType::Array(_)) - } - Func => { - matches!(self.ty(a).composite_type, CompositeType::Func(_)) - } + Eq | Any => matches!(a_ty.inner, CT::Array(_) | CT::Struct(_)), + Struct => matches!(a_ty.inner, CT::Struct(_)), + Array => matches!(a_ty.inner, CT::Array(_)), + Func => matches!(a_ty.inner, CT::Func(_)), _ => false, } } (HT::Abstract { shared, ty }, HT::Concrete(b)) => { - if shared { - // TODO: handle shared - todo!("check shared-ness of concrete type"); + let b_ty = &self.ty(b).composite_type; + if shared == b_ty.shared { + return false; } match ty { - None => matches!( - self.ty(b).composite_type, - CompositeType::Array(_) | CompositeType::Struct(_) - ), - NoFunc => matches!(self.ty(b).composite_type, CompositeType::Func(_)), + None => matches!(b_ty.inner, CT::Array(_) | CT::Struct(_)), + NoFunc => matches!(b_ty.inner, CT::Func(_)), _ => false, } } @@ -552,10 +570,10 @@ impl Module { .push(index); } - let list = match &ty.composite_type { - CompositeType::Array(_) => &mut self.array_types, - CompositeType::Func(_) => &mut self.func_types, - CompositeType::Struct(_) => &mut self.struct_types, + let list = match &ty.composite_type.inner { + CompositeInnerType::Array(_) => &mut self.array_types, + CompositeInnerType::Func(_) => &mut self.func_types, + CompositeInnerType::Struct(_) => &mut self.struct_types, }; list.push(index); @@ -631,10 +649,14 @@ impl Module { fn arbitrary_sub_type(&mut self, u: &mut Unstructured) -> Result { if !self.config.gc_enabled { + let composite_type = CompositeType { + inner: CompositeInnerType::Func(self.arbitrary_func_type(u)?), + shared: false, + }; return Ok(SubType { is_final: true, supertype: None, - composite_type: CompositeType::Func(self.arbitrary_func_type(u)?), + composite_type, }); } @@ -654,14 +676,14 @@ impl Module { let mut composite_type = self.types[usize::try_from(supertype).unwrap()] .composite_type .clone(); - match &mut composite_type { - CompositeType::Array(a) => { + match &mut composite_type.inner { + CompositeInnerType::Array(a) => { a.0 = self.arbitrary_matching_field_type(u, a.0)?; } - CompositeType::Func(f) => { + CompositeInnerType::Func(f) => { *f = self.arbitrary_matching_func_type(u, f)?; } - CompositeType::Struct(s) => { + CompositeInnerType::Struct(s) => { *s = self.arbitrary_matching_struct_type(u, s)?; } } @@ -739,6 +761,7 @@ impl Module { if !self.config.gc_enabled { return Ok(ty); } + use CompositeInnerType as CT; use HeapType as HT; let mut choices = vec![ty]; match ty { @@ -784,16 +807,14 @@ impl Module { match self .types .get(usize::try_from(idx).unwrap()) - .map(|ty| &ty.composite_type) + .map(|ty| (ty.composite_type.shared, &ty.composite_type.inner)) { - Some(CompositeType::Array(_)) | Some(CompositeType::Struct(_)) => { - choices.push(HT::Abstract { - shared: false, // TODO: handle shared - ty: AbstractHeapType::None, - }) - } - Some(CompositeType::Func(_)) => choices.push(HT::Abstract { - shared: false, // TODO: handle shared + Some((shared, CT::Array(_) | CT::Struct(_))) => choices.push(HT::Abstract { + shared, + ty: AbstractHeapType::None, + }), + Some((shared, CT::Func(_))) => choices.push(HT::Abstract { + shared, ty: AbstractHeapType::NoFunc, }), None => { @@ -868,6 +889,7 @@ impl Module { if !self.config.gc_enabled { return Ok(ty); } + use CompositeInnerType as CT; use HeapType as HT; let mut choices = vec![ty]; match ty { @@ -900,38 +922,37 @@ impl Module { } } HT::Concrete(mut idx) => { - // TODO: handle shared - let ht = |ty| HT::Abstract { shared: false, ty }; - match &self - .types - .get(usize::try_from(idx).unwrap()) - .map(|ty| &ty.composite_type) - { - Some(CompositeType::Array(_)) => { - choices.extend([ - ht(AbstractHeapType::Any), - ht(AbstractHeapType::Eq), - ht(AbstractHeapType::Array), - ]); - } - Some(CompositeType::Func(_)) => { - choices.push(ht(AbstractHeapType::Func)); - } - Some(CompositeType::Struct(_)) => { - choices.extend([ - ht(AbstractHeapType::Any), - ht(AbstractHeapType::Eq), - ht(AbstractHeapType::Struct), - ]); - } - None => { - // Same as in `arbitrary_matching_heap_type`: this was a - // forward reference to a concrete type that is part of - // this same rec group we are generating right now, and - // therefore we haven't generated that type yet. Just - // leave `choices` as it is and we will choose the - // original type again down below. + if let Some(sub_ty) = &self.types.get(usize::try_from(idx).unwrap()) { + let ht = |ty| HT::Abstract { + shared: sub_ty.composite_type.shared, + ty, + }; + match &sub_ty.composite_type.inner { + CT::Array(_) => { + choices.extend([ + ht(AbstractHeapType::Any), + ht(AbstractHeapType::Eq), + ht(AbstractHeapType::Array), + ]); + } + CT::Func(_) => { + choices.push(ht(AbstractHeapType::Func)); + } + CT::Struct(_) => { + choices.extend([ + ht(AbstractHeapType::Any), + ht(AbstractHeapType::Eq), + ht(AbstractHeapType::Struct), + ]); + } } + } else { + // Same as in `arbitrary_matching_heap_type`: this was a + // forward reference to a concrete type that is part of + // this same rec group we are generating right now, and + // therefore we haven't generated that type yet. Just + // leave `choices` as it is and we will choose the + // original type again down below. } while let Some(supertype) = self .types @@ -947,16 +968,28 @@ impl Module { } fn arbitrary_composite_type(&mut self, u: &mut Unstructured) -> Result { + use CompositeInnerType as CT; + let shared = false; // TODO: handle shared if !self.config.gc_enabled { - return Ok(CompositeType::Func(self.arbitrary_func_type(u)?)); + return Ok(CompositeType { + shared, + inner: CT::Func(self.arbitrary_func_type(u)?), + }); } match u.int_in_range(0..=2)? { - 0 => Ok(CompositeType::Array(ArrayType( - self.arbitrary_field_type(u)?, - ))), - 1 => Ok(CompositeType::Func(self.arbitrary_func_type(u)?)), - 2 => Ok(CompositeType::Struct(self.arbitrary_struct_type(u)?)), + 0 => Ok(CompositeType { + shared, + inner: CT::Array(ArrayType(self.arbitrary_field_type(u)?)), + }), + 1 => Ok(CompositeType { + shared, + inner: CT::Func(self.arbitrary_func_type(u)?), + }), + 2 => Ok(CompositeType { + shared, + inner: CT::Struct(self.arbitrary_struct_type(u)?), + }), _ => unreachable!(), } } @@ -1266,13 +1299,16 @@ impl Module { new_types.push(SubType { is_final: true, supertype: None, - composite_type: CompositeType::Func(Rc::clone(&func_type)), + composite_type: CompositeType::new_func(Rc::clone(&func_type), false), // TODO: handle shared }); new_index } }; - match &new_types[serialized_sig_idx - first_type_index].composite_type { - CompositeType::Func(f) => Some((serialized_sig_idx as u32, Rc::clone(f))), + match &new_types[serialized_sig_idx - first_type_index] + .composite_type + .inner + { + CompositeInnerType::Func(f) => Some((serialized_sig_idx as u32, Rc::clone(f))), _ => unimplemented!(), } }; @@ -1394,8 +1430,8 @@ impl Module { } fn func_type(&self, idx: u32) -> &Rc { - match &self.ty(idx).composite_type { - CompositeType::Func(f) => f, + match &self.ty(idx).composite_type.inner { + CompositeInnerType::Func(f) => f, _ => panic!("types[{idx}] is not a func type"), } } @@ -1708,8 +1744,8 @@ impl Module { id ) }); - match &subtype.composite_type { - wasmparser::CompositeType::Func(func_type) => { + match &subtype.composite_type.inner { + wasmparser::CompositeInnerType::Func(func_type) => { assert!( subtype.is_final, "Subtype {:?} from `exports` Wasm is not final", @@ -1738,7 +1774,10 @@ impl Module { let type_index = self.add_type(SubType { is_final: true, supertype: None, - composite_type: CompositeType::Func(Rc::clone(&new_type)), + composite_type: CompositeType::new_func( + Rc::clone(&new_type), + false, + ), // TODO: handle shared }); let func_index = self.funcs.len() as u32; self.funcs.push((type_index, new_type)); diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 97fdaa36f2..f06340ae85 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -1,6 +1,6 @@ use super::{ - CompositeType, Elements, FuncType, Instruction, InstructionKind::*, InstructionKinds, Module, - ValType, + CompositeInnerType, Elements, FuncType, Instruction, InstructionKind::*, InstructionKinds, + Module, ValType, }; use crate::{unique_string, MemoryOffsetChoices}; use arbitrary::{Result, Unstructured}; @@ -1145,8 +1145,8 @@ impl CodeBuilder<'_> { at: usize, ) -> Option<(bool, u32, ArrayType)> { let (nullable, ty) = self.concrete_ref_type_on_stack_at(at)?; - match &module.ty(ty).composite_type { - CompositeType::Array(a) => Some((nullable, ty, *a)), + match &module.ty(ty).composite_type.inner { + CompositeInnerType::Array(a) => Some((nullable, ty, *a)), _ => None, } } @@ -1159,8 +1159,8 @@ impl CodeBuilder<'_> { at: usize, ) -> Option<(bool, u32, &'a StructType)> { let (nullable, ty) = self.concrete_ref_type_on_stack_at(at)?; - match &module.ty(ty).composite_type { - CompositeType::Struct(s) => Some((nullable, ty, s)), + match &module.ty(ty).composite_type.inner { + CompositeInnerType::Struct(s) => Some((nullable, ty, s)), _ => None, } } @@ -1189,9 +1189,9 @@ impl CodeBuilder<'_> { fn concrete_funcref_on_stack(&self, module: &Module) -> Option { match self.operands().last().copied()?? { ValType::Ref(r) => match r.heap_type { - HeapType::Concrete(idx) => match &module.ty(idx).composite_type { - CompositeType::Func(_) => Some(r), - CompositeType::Struct(_) | CompositeType::Array(_) => None, + HeapType::Concrete(idx) => match &module.ty(idx).composite_type.inner { + CompositeInnerType::Func(_) => Some(r), + CompositeInnerType::Struct(_) | CompositeInnerType::Array(_) => None, }, _ => None, }, @@ -1206,8 +1206,10 @@ impl CodeBuilder<'_> { Some(Some(ValType::Ref(RefType { nullable, heap_type: HeapType::Concrete(idx), - }))) => match &module.ty(*idx).composite_type { - CompositeType::Struct(s) => !s.fields.is_empty() && (!nullable || allow_null_refs), + }))) => match &module.ty(*idx).composite_type.inner { + CompositeInnerType::Struct(s) => { + !s.fields.is_empty() && (!nullable || allow_null_refs) + } _ => false, }, _ => false, @@ -2032,8 +2034,8 @@ fn call_ref( HeapType::Concrete(idx) => idx, _ => unreachable!(), }; - let func_ty = match &module.ty(idx).composite_type { - CompositeType::Func(f) => f, + let func_ty = match &module.ty(idx).composite_type.inner { + CompositeInnerType::Func(f) => f, _ => unreachable!(), }; builder.pop_operands(module, &func_ty.params); @@ -2165,9 +2167,9 @@ fn return_call_ref_valid(module: &Module, builder: &mut CodeBuilder) -> bool { HeapType::Concrete(idx) => idx, _ => unreachable!(), }; - let func_ty = match &module.ty(idx).composite_type { - CompositeType::Func(f) => f, - CompositeType::Array(_) | CompositeType::Struct(_) => return false, + let func_ty = match &module.ty(idx).composite_type.inner { + CompositeInnerType::Func(f) => f, + CompositeInnerType::Array(_) | CompositeInnerType::Struct(_) => return false, }; let ty = builder.allocs.operands.pop().unwrap(); @@ -2191,8 +2193,8 @@ fn return_call_ref( HeapType::Concrete(idx) => idx, _ => unreachable!(), }; - let func_ty = match &module.ty(idx).composite_type { - CompositeType::Func(f) => f, + let func_ty = match &module.ty(idx).composite_type.inner { + CompositeInnerType::Func(f) => f, _ => unreachable!(), }; builder.pop_operands(module, &func_ty.params); diff --git a/crates/wasm-smith/src/core/encode.rs b/crates/wasm-smith/src/core/encode.rs index 5a661509a6..fc408e9c44 100644 --- a/crates/wasm-smith/src/core/encode.rs +++ b/crates/wasm-smith/src/core/encode.rs @@ -39,16 +39,7 @@ impl Module { section.subtype(&wasm_encoder::SubType { is_final: ty.is_final, supertype_idx: ty.supertype, - composite_type: match &ty.composite_type { - CompositeType::Array(a) => wasm_encoder::CompositeType::Array(a.clone()), - CompositeType::Func(f) => { - wasm_encoder::CompositeType::Func(wasm_encoder::FuncType::new( - f.params.iter().cloned(), - f.results.iter().cloned(), - )) - } - CompositeType::Struct(s) => wasm_encoder::CompositeType::Struct(s.clone()), - }, + composite_type: (&ty.composite_type).into(), }); } else { section.rec( @@ -57,20 +48,7 @@ impl Module { .map(|ty| wasm_encoder::SubType { is_final: ty.is_final, supertype_idx: ty.supertype, - composite_type: match &ty.composite_type { - CompositeType::Array(a) => { - wasm_encoder::CompositeType::Array(a.clone()) - } - CompositeType::Func(f) => { - wasm_encoder::CompositeType::Func(wasm_encoder::FuncType::new( - f.params.iter().cloned(), - f.results.iter().cloned(), - )) - } - CompositeType::Struct(s) => { - wasm_encoder::CompositeType::Struct(s.clone()) - } - }, + composite_type: (&ty.composite_type).into(), }), ); } diff --git a/crates/wasm-smith/tests/exports.rs b/crates/wasm-smith/tests/exports.rs index 6d3e4451a2..7c48cd3d1b 100644 --- a/crates/wasm-smith/tests/exports.rs +++ b/crates/wasm-smith/tests/exports.rs @@ -107,7 +107,11 @@ fn get_func_and_global_exports(features: WasmFeatures, module: &[u8]) -> Vec<(St let sub_type = types.get(core_id).expect("Failed to lookup core id"); assert!(sub_type.is_final); assert!(sub_type.supertype_idx.is_none()); - let CompositeType::Func(func_type) = &sub_type.composite_type else { + let CompositeType { + inner: wasmparser::CompositeInnerType::Func(func_type), + .. + } = &sub_type.composite_type + else { panic!("Expected Func CompositeType, but found {:?}", sub_type); }; exports diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs index eb1b253d48..f85ca09319 100644 --- a/crates/wasmparser/src/readers/core/types.rs +++ b/crates/wasmparser/src/readers/core/types.rs @@ -503,8 +503,8 @@ impl SubType { if let Some(idx) = &mut self.supertype_idx { f(idx)?; } - match &mut self.composite_type { - CompositeType::Func(ty) => { + match &mut self.composite_type.inner { + CompositeInnerType::Func(ty) => { for ty in ty.params_mut() { ty.remap_indices(f)?; } @@ -512,10 +512,10 @@ impl SubType { ty.remap_indices(f)?; } } - CompositeType::Array(ty) => { + CompositeInnerType::Array(ty) => { ty.0.remap_indices(f)?; } - CompositeType::Struct(ty) => { + CompositeInnerType::Struct(ty) => { for field in ty.fields.iter_mut() { field.remap_indices(f)?; } @@ -527,7 +527,17 @@ impl SubType { /// Represents a composite type in a WebAssembly module. #[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] -pub enum CompositeType { +pub struct CompositeType { + /// The type defined inside the composite type. + pub inner: CompositeInnerType, + /// Is the composite type shared? This is part of the + /// shared-everything-threads proposal. + pub shared: bool, +} + +/// A [`CompositeType`] can contain one of these types. +#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)] +pub enum CompositeInnerType { /// The type is for a function. Func(FuncType), /// The type is for an array. @@ -538,43 +548,46 @@ pub enum CompositeType { impl fmt::Display for CompositeType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Self::Array(_) => write!(f, "(array ...)"), - Self::Func(_) => write!(f, "(func ...)"), - Self::Struct(_) => write!(f, "(struct ...)"), + use CompositeInnerType::*; + if self.shared { + write!(f, "(shared ")?; + } + match self.inner { + Array(_) => write!(f, "(array ...)"), + Func(_) => write!(f, "(func ...)"), + Struct(_) => write!(f, "(struct ...)"), + }?; + if self.shared { + write!(f, ")")?; } + Ok(()) } } impl CompositeType { /// Unwrap a `FuncType` or panic. pub fn unwrap_func(&self) -> &FuncType { - match self { - Self::Func(f) => f, + match &self.inner { + CompositeInnerType::Func(f) => f, _ => panic!("not a func"), } } /// Unwrap a `ArrayType` or panic. pub fn unwrap_array(&self) -> &ArrayType { - match self { - Self::Array(a) => a, + match &self.inner { + CompositeInnerType::Array(a) => a, _ => panic!("not a array"), } } /// Unwrap a `StructType` or panic. pub fn unwrap_struct(&self) -> &StructType { - match self { - Self::Struct(s) => s, + match &self.inner { + CompositeInnerType::Struct(s) => s, _ => panic!("not a struct"), } } - - /// Is the composite type `shared`? - pub fn is_shared(&self) -> bool { - todo!("shared composite types are not yet implemented") - } } /// Represents a type of a function in a WebAssembly module. @@ -1796,9 +1809,9 @@ impl<'a> TypeSectionReader<'a> { if !ty.is_final || ty.supertype_idx.is_some() { bail!(offset, "gc proposal not supported"); } - match ty.composite_type { - CompositeType::Func(f) => Ok(f), - CompositeType::Array(_) | CompositeType::Struct(_) => { + match ty.composite_type.inner { + CompositeInnerType::Func(f) => Ok(f), + CompositeInnerType::Array(_) | CompositeInnerType::Struct(_) => { bail!(offset, "gc proposal not supported"); } } @@ -1818,12 +1831,18 @@ fn read_composite_type( ) -> Result { // NB: See `FromReader<'a> for ValType` for a table of how this // interacts with other value encodings. - Ok(match opcode { - 0x60 => CompositeType::Func(reader.read()?), - 0x5e => CompositeType::Array(reader.read()?), - 0x5f => CompositeType::Struct(reader.read()?), + let (shared, opcode) = if opcode == 0x65 { + (true, reader.read_u8()?) + } else { + (false, opcode) + }; + let inner = match opcode { + 0x60 => CompositeInnerType::Func(reader.read()?), + 0x5e => CompositeInnerType::Array(reader.read()?), + 0x5f => CompositeInnerType::Struct(reader.read()?), x => return reader.invalid_leading_byte(x, "type"), - }) + }; + Ok(CompositeType { shared, inner }) } impl<'a> FromReader<'a> for RecGroup { diff --git a/crates/wasmparser/src/readers/core/types/matches.rs b/crates/wasmparser/src/readers/core/types/matches.rs index 5976b305ac..fee15966b3 100644 --- a/crates/wasmparser/src/readers/core/types/matches.rs +++ b/crates/wasmparser/src/readers/core/types/matches.rs @@ -14,8 +14,8 @@ use crate::{ types::{CoreTypeId, RecGroupId, TypeList}, - ArrayType, CompositeType, FieldType, FuncType, RefType, StorageType, StructType, SubType, - ValType, + ArrayType, CompositeInnerType, CompositeType, FieldType, FuncType, RefType, StorageType, + StructType, SubType, ValType, }; /// Wasm type matching. @@ -103,27 +103,31 @@ impl<'a> Matches for WithRecGroup<&'a SubType> { impl<'a> Matches for WithRecGroup<&'a CompositeType> { fn matches(types: &TypeList, a: Self, b: Self) -> bool { - match (&*a, &*b) { - (CompositeType::Func(fa), CompositeType::Func(fb)) => Matches::matches( + use CompositeInnerType::*; + if (*a).shared != (*b).shared { + return false; + } + match (&(*a).inner, &(*b).inner) { + (Func(fa), Func(fb)) => Matches::matches( types, WithRecGroup::map(a, |_| fa), WithRecGroup::map(b, |_| fb), ), - (CompositeType::Func(_), _) => false, + (Func(_), _) => false, - (CompositeType::Array(aa), CompositeType::Array(ab)) => Matches::matches( + (Array(aa), Array(ab)) => Matches::matches( types, WithRecGroup::map(a, |_| *aa), WithRecGroup::map(b, |_| *ab), ), - (CompositeType::Array(_), _) => false, + (Array(_), _) => false, - (CompositeType::Struct(sa), CompositeType::Struct(sb)) => Matches::matches( + (Struct(sa), Struct(sb)) => Matches::matches( types, WithRecGroup::map(a, |_| sa), WithRecGroup::map(b, |_| sb), ), - (CompositeType::Struct(_), _) => false, + (Struct(_), _) => false, } } } diff --git a/crates/wasmparser/src/validator/component.rs b/crates/wasmparser/src/validator/component.rs index 80dbd2e380..56658af734 100644 --- a/crates/wasmparser/src/validator/component.rs +++ b/crates/wasmparser/src/validator/component.rs @@ -10,9 +10,9 @@ use super::{ ModuleType, RecordType, Remapping, ResourceId, TypeAlloc, TypeList, VariantCase, }, }; -use crate::collections::index_map::Entry; use crate::prelude::*; use crate::validator::names::{ComponentName, ComponentNameKind, KebabStr, KebabString}; +use crate::{collections::index_map::Entry, CompositeInnerType}; use crate::{ limits::*, types::{ @@ -1005,10 +1005,14 @@ impl ComponentState { self.check_options(None, &info, &options, types, offset)?; + let composite_type = CompositeType { + inner: CompositeInnerType::Func(info.into_func_type()), + shared: false, + }; let lowered_ty = SubType { is_final: true, supertype_idx: None, - composite_type: CompositeType::Func(info.into_func_type()), + composite_type, }; let (_is_new, group_id) = @@ -1026,10 +1030,14 @@ impl ComponentState { offset: usize, ) -> Result<()> { let rep = self.check_local_resource(resource, types, offset)?; + let composite_type = CompositeType { + inner: CompositeInnerType::Func(FuncType::new([rep], [ValType::I32])), + shared: false, + }; let core_ty = SubType { is_final: true, supertype_idx: None, - composite_type: CompositeType::Func(FuncType::new([rep], [ValType::I32])), + composite_type, }; let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit(offset, core_ty)); @@ -1045,10 +1053,14 @@ impl ComponentState { offset: usize, ) -> Result<()> { self.resource_at(resource, types, offset)?; + let composite_type = CompositeType { + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [])), + shared: false, + }; let core_ty = SubType { is_final: true, supertype_idx: None, - composite_type: CompositeType::Func(FuncType::new([ValType::I32], [])), + composite_type, }; let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit(offset, core_ty)); @@ -1064,10 +1076,14 @@ impl ComponentState { offset: usize, ) -> Result<()> { let rep = self.check_local_resource(resource, types, offset)?; + let composite_type = CompositeType { + inner: CompositeInnerType::Func(FuncType::new([ValType::I32], [rep])), + shared: false, + }; let core_ty = SubType { is_final: true, supertype_idx: None, - composite_type: CompositeType::Func(FuncType::new([ValType::I32], [rep])), + composite_type, }; let (_is_new, group_id) = types.intern_canonical_rec_group(RecGroup::implicit(offset, core_ty)); diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs index 173a74d833..4c78eaf4a9 100644 --- a/crates/wasmparser/src/validator/core.rs +++ b/crates/wasmparser/src/validator/core.rs @@ -9,7 +9,6 @@ use super::{ operators::{ty_to_str, OperatorValidator, OperatorValidatorAllocations}, types::{CoreTypeId, EntityType, RecGroupId, TypeAlloc, TypeList}, }; -use crate::prelude::*; use crate::{ limits::*, validator::types::TypeIdentifier, BinaryReaderError, CompositeType, ConstExpr, Data, DataKind, Element, ElementKind, ExternalKind, FuncType, Global, GlobalType, HeapType, @@ -17,6 +16,7 @@ use crate::{ TableType, TagType, TypeRef, UnpackedIndex, ValType, VisitOperator, WasmFeatures, WasmModuleResources, }; +use crate::{prelude::*, CompositeInnerType}; use alloc::sync::Arc; use core::mem; @@ -608,7 +608,7 @@ impl Module { bail!(offset, "gc proposal must be enabled to use subtypes"); } - self.check_composite_type(&ty.composite_type, features, offset)?; + self.check_composite_type(&ty.composite_type, features, &types, offset)?; let depth = if let Some(supertype_index) = ty.supertype_idx { debug_assert!(supertype_index.is_canonical()); @@ -641,17 +641,36 @@ impl Module { &mut self, ty: &CompositeType, features: &WasmFeatures, + types: &TypeList, offset: usize, ) -> Result<()> { - let check = |ty: &ValType| { + let check = |ty: &ValType, shared: bool| { features .check_value_type(*ty) - .map_err(|e| BinaryReaderError::new(e, offset)) + .map_err(|e| BinaryReaderError::new(e, offset))?; + if shared && !types.valtype_is_shared(*ty) { + return Err(BinaryReaderError::new( + "shared composite type must contain shared types", + offset, + )); + // The other cases are fine: + // - both shared or unshared: good to go + // - the func type is unshared, `ty` is shared: though + // odd, we _can_ in fact use shared values in + // unshared composite types (e.g., functions). + } + Ok(()) }; - match ty { - CompositeType::Func(t) => { - for ty in t.params().iter().chain(t.results()) { - check(ty)?; + if !features.shared_everything_threads() && ty.shared { + return Err(BinaryReaderError::new( + "shared composite types are not supported without the shared-everything-threads feature", + offset, + )); + } + match &ty.inner { + CompositeInnerType::Func(t) => { + for vt in t.params().iter().chain(t.results()) { + check(vt, ty.shared)?; } if t.results().len() > 1 && !features.multi_value() { return Err(BinaryReaderError::new( @@ -660,7 +679,7 @@ impl Module { )); } } - CompositeType::Array(t) => { + CompositeInnerType::Array(t) => { if !features.gc() { return Err(BinaryReaderError::new( "array indexed types not supported without the gc feature", @@ -668,21 +687,25 @@ impl Module { )); } match &t.0.element_type { - StorageType::I8 | StorageType::I16 => {} - StorageType::Val(value_type) => check(value_type)?, + StorageType::I8 | StorageType::I16 => { + // Note: scalar types are always `shared`. + } + StorageType::Val(value_type) => check(value_type, ty.shared)?, }; } - CompositeType::Struct(t) => { + CompositeInnerType::Struct(t) => { if !features.gc() { return Err(BinaryReaderError::new( "struct indexed types not supported without the gc feature", offset, )); } - for ty in t.fields.iter() { - match &ty.element_type { - StorageType::I8 | StorageType::I16 => {} - StorageType::Val(value_type) => check(value_type)?, + for ft in t.fields.iter() { + match &ft.element_type { + StorageType::I8 | StorageType::I16 => { + // Note: scalar types are always `shared`. + } + StorageType::Val(value_type) => check(value_type, ty.shared)?, } } } @@ -824,8 +847,12 @@ impl Module { types: &'a TypeList, offset: usize, ) -> Result<&'a FuncType> { - match &self.sub_type_at(types, type_index, offset)?.composite_type { - CompositeType::Func(f) => Ok(f), + match &self + .sub_type_at(types, type_index, offset)? + .composite_type + .inner + { + CompositeInnerType::Func(f) => Ok(f), _ => bail!(offset, "type index {type_index} is not a function type"), } } @@ -1307,8 +1334,8 @@ impl WasmModuleResources for ValidatorResources { fn tag_at(&self, at: u32) -> Option<&FuncType> { let id = *self.0.tags.get(at as usize)?; let types = self.0.snapshot.as_ref().unwrap(); - match &types[id].composite_type { - CompositeType::Func(f) => Some(f), + match &types[id].composite_type.inner { + CompositeInnerType::Func(f) => Some(f), _ => None, } } diff --git a/crates/wasmparser/src/validator/func.rs b/crates/wasmparser/src/validator/func.rs index 0c4825746c..9d393585a7 100644 --- a/crates/wasmparser/src/validator/func.rs +++ b/crates/wasmparser/src/validator/func.rs @@ -246,7 +246,10 @@ mod tests { EmptyResources(crate::SubType { supertype_idx: None, is_final: true, - composite_type: crate::CompositeType::Func(crate::FuncType::new([], [])), + composite_type: crate::CompositeType { + inner: crate::CompositeInnerType::Func(crate::FuncType::new([], [])), + shared: false, + }, }) } } diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs index 52dceebb02..e0f2c7aeef 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -22,13 +22,13 @@ // confusing it's recommended to read over that section to see how it maps to // the various methods here. -use crate::prelude::*; use crate::{ limits::MAX_WASM_FUNCTION_LOCALS, AbstractHeapType, ArrayType, BinaryReaderError, BlockType, - BrTable, Catch, CompositeType, FieldType, FuncType, GlobalType, HeapType, Ieee32, Ieee64, - MemArg, RefType, Result, StorageType, StructType, SubType, TableType, TryTable, UnpackedIndex, - ValType, VisitOperator, WasmFeatures, WasmModuleResources, V128, + BrTable, Catch, FieldType, FuncType, GlobalType, HeapType, Ieee32, Ieee64, MemArg, RefType, + Result, StorageType, StructType, SubType, TableType, TryTable, UnpackedIndex, ValType, + VisitOperator, WasmFeatures, WasmModuleResources, V128, }; +use crate::{prelude::*, CompositeInnerType}; use core::ops::{Deref, DerefMut}; pub(crate) struct OperatorValidator { @@ -1174,7 +1174,7 @@ where fn struct_type_at(&self, at: u32) -> Result<&'resources StructType> { let sub_ty = self.sub_type_at(at)?; - if let CompositeType::Struct(struct_ty) = &sub_ty.composite_type { + if let CompositeInnerType::Struct(struct_ty) = &sub_ty.composite_type.inner { Ok(struct_ty) } else { bail!( @@ -1199,7 +1199,7 @@ where fn array_type_at(&self, at: u32) -> Result<&'resources ArrayType> { let sub_ty = self.sub_type_at(at)?; - if let CompositeType::Array(array_ty) = &sub_ty.composite_type { + if let CompositeInnerType::Array(array_ty) = &sub_ty.composite_type.inner { Ok(array_ty) } else { bail!( @@ -1211,7 +1211,7 @@ where fn func_type_at(&self, at: u32) -> Result<&'resources FuncType> { let sub_ty = self.sub_type_at(at)?; - if let CompositeType::Func(func_ty) = &sub_ty.composite_type { + if let CompositeInnerType::Func(func_ty) = &sub_ty.composite_type.inner { Ok(func_ty) } else { bail!( diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index 6e4f452e38..578ba8b236 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -4,13 +4,13 @@ use super::{ component::{ComponentState, ExternKind}, core::Module, }; -use crate::prelude::*; use crate::{collections::map::Entry, AbstractHeapType}; +use crate::{prelude::*, CompositeInnerType}; use crate::{validator::names::KebabString, HeapType, ValidatorId}; use crate::{ - BinaryReaderError, CompositeType, Export, ExternalKind, FuncType, GlobalType, Import, Matches, - MemoryType, PackedIndex, PrimitiveValType, RecGroup, RefType, Result, SubType, TableType, - TypeRef, UnpackedIndex, ValType, WithRecGroup, + BinaryReaderError, Export, ExternalKind, FuncType, GlobalType, Import, Matches, MemoryType, + PackedIndex, PrimitiveValType, RecGroup, RefType, Result, SubType, TableType, TypeRef, + UnpackedIndex, ValType, WithRecGroup, }; use alloc::sync::Arc; use core::ops::{Deref, DerefMut, Index, Range}; @@ -293,11 +293,12 @@ impl TypeData for SubType { fn type_info(&self, _types: &TypeList) -> TypeInfo { // TODO(#1036): calculate actual size for func, array, struct. - let size = 1 + match &self.composite_type { - CompositeType::Func(ty) => 1 + (ty.params().len() + ty.results().len()) as u32, - CompositeType::Array(_) => 2, - CompositeType::Struct(ty) => 1 + 2 * ty.fields.len() as u32, + let size = 1 + match &self.composite_type.inner { + CompositeInnerType::Func(ty) => 1 + (ty.params().len() + ty.results().len()) as u32, + CompositeInnerType::Array(_) => 2, + CompositeInnerType::Struct(ty) => 1 + 2 * ty.fields.len() as u32, }; + // TODO: handle shared? TypeInfo::core(size) } } @@ -313,9 +314,9 @@ impl CoreType { /// Get the underlying `FuncType` within this `SubType` or panic. pub fn unwrap_func(&self) -> &FuncType { - match &self.unwrap_sub().composite_type { - CompositeType::Func(f) => f, - CompositeType::Array(_) | CompositeType::Struct(_) => { + match &self.unwrap_sub().composite_type.inner { + CompositeInnerType::Func(f) => f, + CompositeInnerType::Array(_) | CompositeInnerType::Struct(_) => { panic!("`unwrap_func` on non-func composite type") } } @@ -2744,6 +2745,7 @@ impl TypeList { }; use AbstractHeapType::*; + use CompositeInnerType as CT; use HeapType as HT; match (a.heap_type(), b.heap_type()) { (a, b) if a == b => true, @@ -2778,24 +2780,15 @@ impl TypeList { } (HT::Concrete(a), HT::Abstract { shared, ty }) => { - if shared { - // TODO: handle shared - todo!("check shared-ness of concrete type"); + let a_ty = &subtype(a_group, a).composite_type; + if a_ty.shared != shared { + return false; } match ty { - Any | Eq => matches!( - subtype(a_group, a).composite_type, - CompositeType::Array(_) | CompositeType::Struct(_) - ), - Struct => { - matches!(subtype(a_group, a).composite_type, CompositeType::Struct(_)) - } - Array => { - matches!(subtype(a_group, a).composite_type, CompositeType::Array(_)) - } - Func => { - matches!(subtype(a_group, a).composite_type, CompositeType::Func(_)) - } + Any | Eq => matches!(a_ty.inner, CT::Array(_) | CT::Struct(_)), + Struct => matches!(a_ty.inner, CT::Struct(_)), + Array => matches!(a_ty.inner, CT::Array(_)), + Func => matches!(a_ty.inner, CT::Func(_)), // Nothing else matches. (Avoid full wildcard matches so // that adding/modifying variants is easier in the future.) Extern | Exn | I31 | None | NoFunc | NoExtern | NoExn => false, @@ -2803,16 +2796,13 @@ impl TypeList { } (HT::Abstract { shared, ty }, HT::Concrete(b)) => { - if shared { - // TODO: handle shared - todo!("check shared-ness of concrete type"); + let b_ty = &subtype(b_group, b).composite_type; + if shared != b_ty.shared { + return false; } match ty { - None => matches!( - subtype(b_group, b).composite_type, - CompositeType::Array(_) | CompositeType::Struct(_) - ), - NoFunc => matches!(subtype(b_group, b).composite_type, CompositeType::Func(_)), + None => matches!(b_ty.inner, CT::Array(_) | CT::Struct(_)), + NoFunc => matches!(b_ty.inner, CT::Func(_)), // Nothing else matches. (Avoid full wildcard matches so // that adding/modifying variants is easier in the future.) Func | Extern | Exn | Any | Eq | Array | I31 | Struct | NoExtern | NoExn => { @@ -2853,9 +2843,9 @@ impl TypeList { ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => true, ValType::Ref(rt) => match rt.heap_type() { HeapType::Abstract { shared, .. } => shared, - HeapType::Concrete(index) => self[index.as_core_type_id().unwrap()] - .composite_type - .is_shared(), + HeapType::Concrete(index) => { + self[index.as_core_type_id().unwrap()].composite_type.shared + } }, } } @@ -2867,16 +2857,16 @@ impl TypeList { pub fn top_type(&self, heap_type: &HeapType) -> HeapType { use AbstractHeapType::*; match *heap_type { - HeapType::Concrete(idx) => match self[idx.as_core_type_id().unwrap()].composite_type { - CompositeType::Func(_) => HeapType::Abstract { - shared: false, // TODO: handle shared--retrieve from `func` type. - ty: Func, - }, - CompositeType::Array(_) | CompositeType::Struct(_) => HeapType::Abstract { - shared: false, // TODO: handle shared--retrieve from `array` or `struct` type. - ty: Any, - }, - }, + HeapType::Concrete(idx) => { + let ty = &self[idx.as_core_type_id().unwrap()].composite_type; + let shared = ty.shared; + match ty.inner { + CompositeInnerType::Func(_) => HeapType::Abstract { shared, ty: Func }, + CompositeInnerType::Array(_) | CompositeInnerType::Struct(_) => { + HeapType::Abstract { shared, ty: Any } + } + } + } HeapType::Abstract { shared, ty } => { let ty = match ty { Func | NoFunc => Func, diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index a6dd257539..6c6d2943e9 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -751,9 +751,9 @@ impl Printer<'_, '_> { )?; let ty = match ty { CoreType::Sub(ty) => { - let ty = match &ty.composite_type { - CompositeType::Func(f) => f, - CompositeType::Array(_) | CompositeType::Struct(_) => { + let ty = match &ty.composite_type.inner { + CompositeInnerType::Func(f) => f, + CompositeInnerType::Array(_) | CompositeInnerType::Struct(_) => { unreachable!("Wasm GC types cannot appear in components yet") } }; @@ -761,10 +761,14 @@ impl Printer<'_, '_> { self.start_group("func")?; self.print_func_type(states.last().unwrap(), &ty, None)?; self.end_group()?; + let composite_type = CompositeType { + inner: CompositeInnerType::Func(ty.clone()), + shared: false, + }; Some(SubType { is_final: true, supertype_idx: None, - composite_type: CompositeType::Func(ty.clone()), + composite_type, }) } CoreType::Module(decls) => { @@ -818,26 +822,32 @@ impl Printer<'_, '_> { } fn print_composite(&mut self, state: &State, ty: &CompositeType, ty_idx: u32) -> Result { - let r = match &ty { - CompositeType::Func(ty) => { + if ty.shared { + self.start_group("shared")?; + } + let r = match &ty.inner { + CompositeInnerType::Func(ty) => { self.start_group("func")?; let r = self.print_func_type(state, ty, None)?; self.end_group()?; // `func` r } - CompositeType::Array(ty) => { + CompositeInnerType::Array(ty) => { self.start_group("array")?; let r = self.print_array_type(state, ty)?; self.end_group()?; // `array` r } - CompositeType::Struct(ty) => { + CompositeInnerType::Struct(ty) => { self.start_group("struct")?; let r = self.print_struct_type(state, ty, ty_idx)?; self.end_group()?; // `struct` r } }; + if ty.shared { + self.end_group()?; // `shared` + } Ok(r) } @@ -881,7 +891,11 @@ impl Printer<'_, '_> { match state.core.types.get(idx as usize) { Some(Some(SubType { - composite_type: CompositeType::Func(ty), + composite_type: + CompositeType { + inner: CompositeInnerType::Func(ty), + shared: false, + }, .. })) => self.print_func_type(state, ty, names_for).map(Some), Some(Some(_)) | Some(None) | None => Ok(None), diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index c2f3e0e242..c57d633345 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -57,14 +57,21 @@ fn encode_fields( fn encode_core_type(encoder: CoreTypeEncoder, ty: &CoreTypeDef) { match ty { - CoreTypeDef::Def(core::TypeDef::Func(f)) => { - encoder.function( - f.params.iter().map(|(_, _, ty)| (*ty).into()), - f.results.iter().copied().map(Into::into), - ); - } - CoreTypeDef::Def(core::TypeDef::Struct(_)) | CoreTypeDef::Def(core::TypeDef::Array(_)) => { - todo!("encoding of GC proposal types not yet implemented") + CoreTypeDef::Def(def) => { + if def.shared { + todo!("encoding of shared types not yet implemented") + } + match &def.kind { + core::InnerTypeKind::Func(f) => { + encoder.function( + f.params.iter().map(|(_, _, ty)| (*ty).into()), + f.results.iter().copied().map(Into::into), + ); + } + core::InnerTypeKind::Struct(_) | core::InnerTypeKind::Array(_) => { + todo!("encoding of GC proposal types not yet implemented") + } + } } CoreTypeDef::Module(t) => { encoder.module(&t.into()); @@ -876,12 +883,12 @@ impl From<&ModuleType<'_>> for wasm_encoder::ModuleType { for decl in &ty.decls { match decl { - ModuleTypeDecl::Type(t) => match &t.def { - core::TypeDef::Func(f) => encoded.ty().function( + ModuleTypeDecl::Type(t) => match &t.def.kind { + core::InnerTypeKind::Func(f) => encoded.ty().function( f.params.iter().map(|(_, _, ty)| (*ty).into()), f.results.iter().copied().map(Into::into), ), - core::TypeDef::Struct(_) | core::TypeDef::Array(_) => { + core::InnerTypeKind::Struct(_) | core::InnerTypeKind::Array(_) => { todo!("encoding of GC proposal types not yet implemented") } }, diff --git a/crates/wast/src/component/expand.rs b/crates/wast/src/component/expand.rs index 10523eacc8..0e41f33166 100644 --- a/crates/wast/src/component/expand.rs +++ b/crates/wast/src/component/expand.rs @@ -428,13 +428,13 @@ impl<'a> Expander<'a> { let mut i = 0; while i < ty.decls.len() { match &mut ty.decls[i] { - ModuleTypeDecl::Type(ty) => match &ty.def { - core::TypeDef::Func(f) => { + ModuleTypeDecl::Type(ty) => match &ty.def.kind { + core::InnerTypeKind::Func(f) => { let id = gensym::fill(ty.span, &mut ty.id); func_type_to_idx.insert(f.key(), Index::Id(id)); } - core::TypeDef::Struct(_) => {} - core::TypeDef::Array(_) => {} + core::InnerTypeKind::Struct(_) => {} + core::InnerTypeKind::Array(_) => {} }, ModuleTypeDecl::Alias(_) => {} ModuleTypeDecl::Import(ty) => { diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs index 8ce1b40619..7beaa82e01 100644 --- a/crates/wast/src/core/binary.rs +++ b/crates/wast/src/core/binary.rs @@ -361,16 +361,19 @@ impl Encode for Type<'_> { } (None, _) => {} // No supertype, sub wasn't used } - match &self.def { - TypeDef::Func(func) => { + if self.def.shared { + e.push(0x65); + } + match &self.def.kind { + InnerTypeKind::Func(func) => { e.push(0x60); func.encode(e) } - TypeDef::Struct(r#struct) => { + InnerTypeKind::Struct(r#struct) => { e.push(0x5f); r#struct.encode(e) } - TypeDef::Array(array) => { + InnerTypeKind::Array(array) => { e.push(0x5e); array.encode(e) } @@ -1161,9 +1164,9 @@ fn find_names<'a>( // Handle struct fields separately from above if let ModuleField::Type(ty) = field { let mut field_names = vec![]; - match &ty.def { - TypeDef::Func(_) | TypeDef::Array(_) => {} - TypeDef::Struct(ty_struct) => { + match &ty.def.kind { + InnerTypeKind::Func(_) | InnerTypeKind::Array(_) => {} + InnerTypeKind::Struct(ty_struct) => { for (idx, field) in ty_struct.fields.iter().enumerate() { if let Some(name) = get_name(&field.id, &None) { field_names.push((idx as u32, name)) diff --git a/crates/wast/src/core/binary/dwarf.rs b/crates/wast/src/core/binary/dwarf.rs index ae9c430629..b6aa086ef9 100644 --- a/crates/wast/src/core/binary/dwarf.rs +++ b/crates/wast/src/core/binary/dwarf.rs @@ -11,7 +11,7 @@ //! easy/fun to play around with. use crate::core::binary::{EncodeOptions, Encoder, GenerateDwarf, Names, RecOrType}; -use crate::core::{Local, TypeDef, ValType}; +use crate::core::{InnerTypeKind, Local, ValType}; use crate::token::Span; use gimli::write::{ self, Address, AttributeValue, DwarfUnit, Expression, FileId, LineProgram, LineString, @@ -227,8 +227,8 @@ impl<'a> Dwarf<'a> { RecOrType::Rec(r) => &r.types, }) .nth(ty as usize); - let ty = match ty.map(|t| &t.def) { - Some(TypeDef::Func(ty)) => ty, + let ty = match ty.map(|t| &t.def.kind) { + Some(InnerTypeKind::Func(ty)) => ty, _ => return, }; diff --git a/crates/wast/src/core/resolve/names.rs b/crates/wast/src/core/resolve/names.rs index b73195cb4a..d2b7eb1db4 100644 --- a/crates/wast/src/core/resolve/names.rs +++ b/crates/wast/src/core/resolve/names.rs @@ -49,12 +49,12 @@ impl<'a> Resolver<'a> { fn register_type(&mut self, ty: &Type<'a>) -> Result<(), Error> { let type_index = self.types.register(ty.id, "type")?; - match &ty.def { + match &ty.def.kind { // For GC structure types we need to be sure to populate the // field namespace here as well. // // The field namespace is relative to the struct fields are defined in - TypeDef::Struct(r#struct) => { + InnerTypeKind::Struct(r#struct) => { for (i, field) in r#struct.fields.iter().enumerate() { if let Some(id) = field.id { self.fields @@ -65,14 +65,14 @@ impl<'a> Resolver<'a> { } } - TypeDef::Array(_) | TypeDef::Func(_) => {} + InnerTypeKind::Array(_) | InnerTypeKind::Func(_) => {} } // Record function signatures as we see them to so we can // generate errors for mismatches in references such as // `call_indirect`. - match &ty.def { - TypeDef::Func(f) => { + match &ty.def.kind { + InnerTypeKind::Func(f) => { let params = f.params.iter().map(|p| p.2).collect(); let results = f.results.clone(); self.type_info.push(TypeInfo::Func { params, results }); @@ -120,14 +120,14 @@ impl<'a> Resolver<'a> { } fn resolve_type(&self, ty: &mut Type<'a>) -> Result<(), Error> { - match &mut ty.def { - TypeDef::Func(func) => func.resolve(self)?, - TypeDef::Struct(struct_) => { + match &mut ty.def.kind { + InnerTypeKind::Func(func) => func.resolve(self)?, + InnerTypeKind::Struct(struct_) => { for field in &mut struct_.fields { self.resolve_storagetype(&mut field.ty)?; } } - TypeDef::Array(array) => self.resolve_storagetype(&mut array.ty)?, + InnerTypeKind::Array(array) => self.resolve_storagetype(&mut array.ty)?, } if let Some(parent) = &mut ty.parent { self.resolve(parent, Ns::Type)?; diff --git a/crates/wast/src/core/resolve/types.rs b/crates/wast/src/core/resolve/types.rs index 84b0e516e4..9172e50265 100644 --- a/crates/wast/src/core/resolve/types.rs +++ b/crates/wast/src/core/resolve/types.rs @@ -51,11 +51,11 @@ impl<'a> Expander<'a> { match item { ModuleField::Type(ty) => { let id = gensym::fill(ty.span, &mut ty.id); - match &mut ty.def { - TypeDef::Func(f) => { + match &mut ty.def.kind { + InnerTypeKind::Func(f) => { f.key().insert(self, Index::Id(id)); } - TypeDef::Array(_) | TypeDef::Struct(_) => {} + InnerTypeKind::Array(_) | InnerTypeKind::Struct(_) => {} } } _ => {} @@ -255,10 +255,13 @@ impl<'a> TypeKey<'a> for FuncKey<'a> { } fn to_def(&self, _span: Span) -> TypeDef<'a> { - TypeDef::Func(FunctionType { - params: self.0.iter().map(|t| (None, None, *t)).collect(), - results: self.1.clone(), - }) + TypeDef { + kind: InnerTypeKind::Func(FunctionType { + params: self.0.iter().map(|t| (None, None, *t)).collect(), + results: self.1.clone(), + }), + shared: false, // TODO: handle shared + } } fn insert(&self, cx: &mut Expander<'a>, idx: Index<'a>) { diff --git a/crates/wast/src/core/types.rs b/crates/wast/src/core/types.rs index b149f33d25..48f128c0f2 100644 --- a/crates/wast/src/core/types.rs +++ b/crates/wast/src/core/types.rs @@ -864,9 +864,9 @@ impl<'a> Parse<'a> for ExportType<'a> { } } -/// A definition of a type. +/// The inner kind of a type definition. #[derive(Debug)] -pub enum TypeDef<'a> { +pub enum InnerTypeKind<'a> { /// A function type definition. Func(FunctionType<'a>), /// A struct type definition. @@ -875,24 +875,52 @@ pub enum TypeDef<'a> { Array(ArrayType<'a>), } -impl<'a> Parse<'a> for TypeDef<'a> { +impl<'a> Parse<'a> for InnerTypeKind<'a> { fn parse(parser: Parser<'a>) -> Result { let mut l = parser.lookahead1(); if l.peek::()? { parser.parse::()?; - Ok(TypeDef::Func(parser.parse()?)) + Ok(InnerTypeKind::Func(parser.parse()?)) } else if l.peek::()? { parser.parse::()?; - Ok(TypeDef::Struct(parser.parse()?)) + Ok(InnerTypeKind::Struct(parser.parse()?)) } else if l.peek::()? { parser.parse::()?; - Ok(TypeDef::Array(parser.parse()?)) + Ok(InnerTypeKind::Array(parser.parse()?)) } else { Err(l.error()) } } } +/// A definition of a type. +#[derive(Debug)] +pub struct TypeDef<'a> { + /// The inner definition. + pub kind: InnerTypeKind<'a>, + /// Whether the type is shared or not. + pub shared: bool, +} + +impl<'a> Parse<'a> for TypeDef<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut l = parser.lookahead1(); + if l.peek::()? { + parser.parse::()?; + parser.parens(|parser| { + let kind = parser.parse()?; + Ok(TypeDef { shared: true, kind }) + }) + } else { + let kind = parser.parse()?; + Ok(TypeDef { + shared: false, + kind, + }) + } + } +} + /// A type declaration in a module #[derive(Debug)] pub struct Type<'a> { diff --git a/tests/cli/dump-branch-hints.wat.stdout b/tests/cli/dump-branch-hints.wat.stdout index 7fe7576554..9836b36909 100644 --- a/tests/cli/dump-branch-hints.wat.stdout +++ b/tests/cli/dump-branch-hints.wat.stdout @@ -3,7 +3,7 @@ 0x8 | 01 04 | type section 0xa | 01 | 1 count --- rec group 0 (implicit) --- - 0xb | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0xb | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0xe | 03 02 | func section 0x10 | 01 | 1 count 0x11 | 00 | [func 0] type 0 diff --git a/tests/cli/dump-llvm-object.wat.stdout b/tests/cli/dump-llvm-object.wat.stdout index 6d48409d9f..a421d7231f 100644 --- a/tests/cli/dump-llvm-object.wat.stdout +++ b/tests/cli/dump-llvm-object.wat.stdout @@ -3,21 +3,21 @@ 0x8 | 01 22 | type section 0xa | 06 | 6 count --- rec group 0 (implicit) --- - 0xb | 60 01 7f 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [] }) } + 0xb | 60 01 7f 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32], results: [] }), shared: false } } --- rec group 1 (implicit) --- - 0xf | 60 04 7f 7f | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32, I32, I32], results: [I32] }) } + 0xf | 60 04 7f 7f | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32, I32, I32, I32], results: [I32] }), shared: false } } | 7f 7f 01 7f --- rec group 2 (implicit) --- - 0x17 | 60 05 7f 7f | [type 2] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32, I32, I32, I32], results: [I32] }) } + 0x17 | 60 05 7f 7f | [type 2] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32, I32, I32, I32, I32], results: [I32] }), shared: false } } | 7f 7f 7f 01 | 7f --- rec group 3 (implicit) --- - 0x20 | 60 01 7f 01 | [type 3] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [I32] }) } + 0x20 | 60 01 7f 01 | [type 3] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32], results: [I32] }), shared: false } } | 7f --- rec group 4 (implicit) --- - 0x25 | 60 00 01 7f | [type 4] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [I32] }) } + 0x25 | 60 00 01 7f | [type 4] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [I32] }), shared: false } } --- rec group 5 (implicit) --- - 0x29 | 60 00 00 | [type 5] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0x29 | 60 00 00 | [type 5] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0x2c | 02 8b 01 | import section 0x2f | 04 | 4 count 0x30 | 03 65 6e 76 | import [memory 0] Import { module: "env", name: "__linear_memory", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }) } diff --git a/tests/cli/dump/alias.wat.stdout b/tests/cli/dump/alias.wat.stdout index 408c8678f2..aa444eaf9f 100644 --- a/tests/cli/dump/alias.wat.stdout +++ b/tests/cli/dump/alias.wat.stdout @@ -31,7 +31,7 @@ 0x57 | 01 04 | type section 0x59 | 01 | 1 count --- rec group 0 (implicit) --- - 0x5a | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0x5a | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0x5d | 03 02 | func section 0x5f | 01 | 1 count 0x60 | 00 | [func 0] type 0 diff --git a/tests/cli/dump/alias2.wat.stdout b/tests/cli/dump/alias2.wat.stdout index 6601ad8878..2710d46edf 100644 --- a/tests/cli/dump/alias2.wat.stdout +++ b/tests/cli/dump/alias2.wat.stdout @@ -127,7 +127,7 @@ 0x158 | 01 04 | type section 0x15a | 01 | 1 count --- rec group 0 (implicit) --- - 0x15b | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0x15b | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0x15e | 03 02 | func section 0x160 | 01 | 1 count 0x161 | 00 | [func 0] type 0 @@ -164,7 +164,7 @@ 0x1a2 | 01 04 | type section 0x1a4 | 01 | 1 count --- rec group 0 (implicit) --- - 0x1a5 | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0x1a5 | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0x1a8 | 02 19 | import section 0x1aa | 04 | 4 count 0x1ab | 00 01 31 00 | import [func 0] Import { module: "", name: "1", ty: Func(0) } diff --git a/tests/cli/dump/blockty.wat.stdout b/tests/cli/dump/blockty.wat.stdout index ed70b5200e..a923deea11 100644 --- a/tests/cli/dump/blockty.wat.stdout +++ b/tests/cli/dump/blockty.wat.stdout @@ -3,16 +3,16 @@ 0x8 | 01 17 | type section 0xa | 05 | 5 count --- rec group 0 (implicit) --- - 0xb | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0xb | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } --- rec group 1 (implicit) --- - 0xe | 60 00 01 7f | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [I32] }) } + 0xe | 60 00 01 7f | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [I32] }), shared: false } } --- rec group 2 (implicit) --- - 0x12 | 60 01 7f 00 | [type 2] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [] }) } + 0x12 | 60 01 7f 00 | [type 2] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32], results: [] }), shared: false } } --- rec group 3 (implicit) --- - 0x16 | 60 01 7f 01 | [type 3] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [I32] }) } + 0x16 | 60 01 7f 01 | [type 3] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32], results: [I32] }), shared: false } } | 7f --- rec group 4 (implicit) --- - 0x1b | 60 01 7f 02 | [type 4] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [I32, I32] }) } + 0x1b | 60 01 7f 02 | [type 4] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32], results: [I32, I32] }), shared: false } } | 7f 7f 0x21 | 03 02 | func section 0x23 | 01 | 1 count diff --git a/tests/cli/dump/bundled.wat.stdout b/tests/cli/dump/bundled.wat.stdout index 4ea95d9ee9..4408c98265 100644 --- a/tests/cli/dump/bundled.wat.stdout +++ b/tests/cli/dump/bundled.wat.stdout @@ -26,7 +26,7 @@ 0x54 | 01 09 | type section 0x56 | 01 | 1 count --- rec group 0 (implicit) --- - 0x57 | 60 04 7f 7f | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32, I32, I32], results: [I32] }) } + 0x57 | 60 04 7f 7f | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32, I32, I32, I32], results: [I32] }), shared: false } } | 7f 7f 01 7f 0x5f | 03 02 | func section 0x61 | 01 | 1 count @@ -63,10 +63,10 @@ 0xa0 | 01 09 | type section 0xa2 | 02 | 2 count --- rec group 0 (implicit) --- - 0xa3 | 60 02 7f 7f | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32], results: [] }) } + 0xa3 | 60 02 7f 7f | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32, I32], results: [] }), shared: false } } | 00 --- rec group 1 (implicit) --- - 0xa8 | 60 00 00 | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0xa8 | 60 00 00 | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0xab | 02 12 | import section 0xad | 01 | 1 count 0xae | 09 77 61 73 | import [func 0] Import { module: "wasi-file", name: "read", ty: Func(0) } @@ -107,10 +107,10 @@ 0x101 | 01 0c | type section 0x103 | 02 | 2 count --- rec group 0 (implicit) --- - 0x104 | 60 02 7f 7f | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32], results: [] }) } + 0x104 | 60 02 7f 7f | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32, I32], results: [] }), shared: false } } | 00 --- rec group 1 (implicit) --- - 0x109 | 60 03 7f 7f | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32, I32], results: [] }) } + 0x109 | 60 03 7f 7f | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32, I32, I32], results: [] }), shared: false } } | 7f 00 0x10f | 02 12 | import section 0x111 | 01 | 1 count diff --git a/tests/cli/dump/component-expand-bundle.wat.stdout b/tests/cli/dump/component-expand-bundle.wat.stdout index aa82e1f02d..d46b131707 100644 --- a/tests/cli/dump/component-expand-bundle.wat.stdout +++ b/tests/cli/dump/component-expand-bundle.wat.stdout @@ -6,7 +6,7 @@ 0x12 | 01 04 | type section 0x14 | 01 | 1 count --- rec group 0 (implicit) --- - 0x15 | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0x15 | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0x18 | 03 02 | func section 0x1a | 01 | 1 count 0x1b | 00 | [func 0] type 0 @@ -30,7 +30,7 @@ 0x3d | 01 04 | type section 0x3f | 01 | 1 count --- rec group 0 (implicit) --- - 0x40 | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0x40 | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0x43 | 02 06 | import section 0x45 | 01 | 1 count 0x46 | 00 01 61 00 | import [func 0] Import { module: "", name: "a", ty: Func(0) } diff --git a/tests/cli/dump/import-modules.wat.stdout b/tests/cli/dump/import-modules.wat.stdout index 3e6475452d..a128f8a5b1 100644 --- a/tests/cli/dump/import-modules.wat.stdout +++ b/tests/cli/dump/import-modules.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 03 0d | core type section 0xa | 01 | 1 count - 0xb | 50 02 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }), Import(Import { module: "", name: "f", ty: Func(0) })]) + 0xb | 50 02 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } }), Import(Import { module: "", name: "f", ty: Func(0) })]) | 00 00 00 00 | 01 66 00 00 0x17 | 0a 07 | component import section @@ -15,7 +15,7 @@ 0x2a | 01 04 | type section 0x2c | 01 | 1 count --- rec group 0 (implicit) --- - 0x2d | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0x2d | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0x30 | 03 02 | func section 0x32 | 01 | 1 count 0x33 | 00 | [func 0] type 0 diff --git a/tests/cli/dump/module-types.wat.stdout b/tests/cli/dump/module-types.wat.stdout index d9d16f4a11..ddde806ebd 100644 --- a/tests/cli/dump/module-types.wat.stdout +++ b/tests/cli/dump/module-types.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 03 23 | core type section 0xa | 01 | 1 count - 0xb | 50 05 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) }), Import(Import { module: "", name: "f", ty: Func(0) }), Import(Import { module: "", name: "g", ty: Global(GlobalType { content_type: I32, mutable: false, shared: false }) }), Import(Import { module: "", name: "t", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None }) }), Import(Import { module: "", name: "m", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }) })]) + 0xb | 50 05 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } }), Import(Import { module: "", name: "f", ty: Func(0) }), Import(Import { module: "", name: "g", ty: Global(GlobalType { content_type: I32, mutable: false, shared: false }) }), Import(Import { module: "", name: "t", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None }) }), Import(Import { module: "", name: "m", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }) })]) | 00 00 00 00 | 01 66 00 00 | 00 00 01 67 diff --git a/tests/cli/dump/names.wat.stdout b/tests/cli/dump/names.wat.stdout index 3585c91e5a..41ee06cc51 100644 --- a/tests/cli/dump/names.wat.stdout +++ b/tests/cli/dump/names.wat.stdout @@ -3,7 +3,7 @@ 0x8 | 01 05 | type section 0xa | 01 | 1 count --- rec group 0 (implicit) --- - 0xb | 60 01 7f 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [] }) } + 0xb | 60 01 7f 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32], results: [] }), shared: false } } 0xf | 03 02 | func section 0x11 | 01 | 1 count 0x12 | 00 | [func 0] type 0 diff --git a/tests/cli/dump/rec-group.wat.stdout b/tests/cli/dump/rec-group.wat.stdout index 7d4e5fa936..07317e38a4 100644 --- a/tests/cli/dump/rec-group.wat.stdout +++ b/tests/cli/dump/rec-group.wat.stdout @@ -4,25 +4,25 @@ 0xa | 04 | 4 count --- rec group 0 (explicit) --- 0xb | 4e 01 | - 0xd | 60 02 7f 7f | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32, I32], results: [F64] }) } + 0xd | 60 02 7f 7f | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32, I32], results: [F64] }), shared: false } } | 01 7c --- rec group 1 (explicit) --- 0x13 | 4e 03 | - 0x15 | 50 00 5f 01 | [type 1] SubType { is_final: false, supertype_idx: None, composite_type: Struct(StructType { fields: [FieldType { element_type: Val(I32), mutable: false }] }) } + 0x15 | 50 00 5f 01 | [type 1] SubType { is_final: false, supertype_idx: None, composite_type: CompositeType { inner: Struct(StructType { fields: [FieldType { element_type: Val(I32), mutable: false }] }), shared: false } } | 7f 00 - 0x1b | 50 00 5f 01 | [type 2] SubType { is_final: false, supertype_idx: None, composite_type: Struct(StructType { fields: [FieldType { element_type: Val(I32), mutable: true }] }) } + 0x1b | 50 00 5f 01 | [type 2] SubType { is_final: false, supertype_idx: None, composite_type: CompositeType { inner: Struct(StructType { fields: [FieldType { element_type: Val(I32), mutable: true }] }), shared: false } } | 7f 01 - 0x21 | 50 00 5f 08 | [type 3] SubType { is_final: false, supertype_idx: None, composite_type: Struct(StructType { fields: [FieldType { element_type: Val(I32), mutable: true }, FieldType { element_type: Val(I64), mutable: true }, FieldType { element_type: Val(F32), mutable: true }, FieldType { element_type: Val(F64), mutable: true }, FieldType { element_type: Val(V128), mutable: true }, FieldType { element_type: Val(Ref(funcref)), mutable: true }, FieldType { element_type: Val(Ref(externref)), mutable: true }, FieldType { element_type: Val(Ref((ref null (module 2)))), mutable: true }] }) } + 0x21 | 50 00 5f 08 | [type 3] SubType { is_final: false, supertype_idx: None, composite_type: CompositeType { inner: Struct(StructType { fields: [FieldType { element_type: Val(I32), mutable: true }, FieldType { element_type: Val(I64), mutable: true }, FieldType { element_type: Val(F32), mutable: true }, FieldType { element_type: Val(F64), mutable: true }, FieldType { element_type: Val(V128), mutable: true }, FieldType { element_type: Val(Ref(funcref)), mutable: true }, FieldType { element_type: Val(Ref(externref)), mutable: true }, FieldType { element_type: Val(Ref((ref null (module 2)))), mutable: true }] }), shared: false } } | 7f 01 7e 01 | 7d 01 7c 01 | 7b 01 70 01 | 6f 01 63 02 | 01 --- rec group 2 (implicit) --- - 0x36 | 50 00 5e 7f | [type 4] SubType { is_final: false, supertype_idx: None, composite_type: Array(ArrayType(FieldType { element_type: Val(I32), mutable: false })) } + 0x36 | 50 00 5e 7f | [type 4] SubType { is_final: false, supertype_idx: None, composite_type: CompositeType { inner: Array(ArrayType(FieldType { element_type: Val(I32), mutable: false })), shared: false } } | 00 --- rec group 3 (implicit) --- - 0x3b | 50 01 04 5e | [type 5] SubType { is_final: false, supertype_idx: Some(CoreTypeIndex { kind: "module", index: 4 }), composite_type: Array(ArrayType(FieldType { element_type: Val(I32), mutable: false })) } + 0x3b | 50 01 04 5e | [type 5] SubType { is_final: false, supertype_idx: Some(CoreTypeIndex { kind: "module", index: 4 }), composite_type: CompositeType { inner: Array(ArrayType(FieldType { element_type: Val(I32), mutable: false })), shared: false } } | 7f 00 0x41 | 00 0e | custom section 0x43 | 04 6e 61 6d | name: "name" diff --git a/tests/cli/dump/select.wat.stdout b/tests/cli/dump/select.wat.stdout index 48f950ae93..be65160bf7 100644 --- a/tests/cli/dump/select.wat.stdout +++ b/tests/cli/dump/select.wat.stdout @@ -3,7 +3,7 @@ 0x8 | 01 04 | type section 0xa | 01 | 1 count --- rec group 0 (implicit) --- - 0xb | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0xb | 60 00 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0xe | 03 02 | func section 0x10 | 01 | 1 count 0x11 | 00 | [func 0] type 0 diff --git a/tests/cli/dump/simple.wat.stdout b/tests/cli/dump/simple.wat.stdout index 27fc8a4d86..59e8902f66 100644 --- a/tests/cli/dump/simple.wat.stdout +++ b/tests/cli/dump/simple.wat.stdout @@ -3,9 +3,9 @@ 0x8 | 01 08 | type section 0xa | 02 | 2 count --- rec group 0 (implicit) --- - 0xb | 60 01 7f 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [I32], results: [] }) } + 0xb | 60 01 7f 00 | [type 0] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [I32], results: [] }), shared: false } } --- rec group 1 (implicit) --- - 0xf | 60 00 00 | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: Func(FuncType { params: [], results: [] }) } + 0xf | 60 00 00 | [type 1] SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } } 0x12 | 02 07 | import section 0x14 | 01 | 1 count 0x15 | 01 6d 01 6e | import [func 0] Import { module: "m", name: "n", ty: Func(0) } diff --git a/tests/local/shared-everything-threads/array.wast b/tests/local/shared-everything-threads/array.wast new file mode 100644 index 0000000000..e3fb3adbad --- /dev/null +++ b/tests/local/shared-everything-threads/array.wast @@ -0,0 +1,115 @@ +;; Shared array declaration syntax +(module + (type (shared (array i8))) + (type (sub final (shared (array i8)))) + (rec + (type (sub final (shared (array i8)))) + ) + + (global (ref 0) (array.new_default 1 (i32.const 1))) + (global (ref 1) (array.new_default 2 (i32.const 1))) + (global (ref 2) (array.new_default 0 (i32.const 1))) +) + +;; Shared arrays are distinct from non-shared arrays +(assert_invalid + (module + (type (shared (array i8))) + (type (array i8)) + + (global (ref 0) (array.new_default 1 (i32.const 1))) + ) + "type mismatch" +) + +(assert_invalid + (module + (type (shared (array i8))) + (type (array i8)) + + (global (ref 1) (array.new 0)) + ) + "type mismatch" +) + +;; Shared arrays may not be subtypes of non-shared arrays +(assert_invalid + (module + (type (sub (array i8))) + (type (sub 0 (shared (array i8)))) + ) + "sub type must match super type" +) + +;; Non-shared arrays may not be subtypes of shared arrays +(assert_invalid + (module + (type (sub (shared (array i8)))) + (type (sub 0 (array i8))) + ) + "sub type must match super type" +) + +;; Shared arrays may not contain non-shared references +(assert_invalid + (module + (type (shared (array anyref))) + ) + "must contain shared type" +) + +;; But they may contain shared references +(module + (type (shared (array (ref null (shared any))))) +) + +;; Non-shared arrays may contain shared references +(module + (type (array (ref null (shared any)))) +) + +;; Array instructions work on shared arrays. +(module + (type $i8 (shared (array (mut i8)))) + (type $i32 (shared (array (mut i32)))) + (type $unshared (array (mut i8))) + + (data) + (elem arrayref) + + (func (array.new $i8 (i32.const 0) (i32.const 0)) (drop)) + + (func (array.new_default $i8 (i32.const 0)) (drop)) + + (func (array.new_fixed $i8 0) (drop)) + + (func (param (ref null $i8)) + (array.get_s $i8 (local.get 0) (i32.const 0)) (drop)) + + (func (param (ref null $i8)) + (array.get_u $i8 (local.get 0) (i32.const 0)) (drop)) + + (func (param (ref null $i32)) + (array.get $i32 (local.get 0) (i32.const 0)) (drop)) + + (func (param (ref null $i8)) + (array.set $i8 (local.get 0) (i32.const 0) (i32.const 0))) + + (func (param (ref null $i8) (ref null $i8)) + (array.copy $i8 $i8 (local.get 0) (i32.const 0) (local.get 1) (i32.const 0) (i32.const 0))) + + (func (param (ref null $i8) (ref null $unshared)) + (array.copy $i8 $unshared (local.get 0) (i32.const 0) (local.get 1) (i32.const 0) (i32.const 0))) + + (func (param (ref null $unshared) (ref null $i8)) + (array.copy $unshared $i8 (local.get 0) (i32.const 0) (local.get 1) (i32.const 0) (i32.const 0))) + + (func (param (ref null $i8)) + (array.fill $i8 (local.get 0) (i32.const 0) (i32.const 0) (i32.const 0))) + + (func (param (ref null $i8)) + (array.init_data $i8 0 (local.get 0) (i32.const 0) (i32.const 0) (i32.const 0))) + + (func (param (ref null $i8)) + (array.init_data $i8 0 (local.get 0) (i32.const 0) (i32.const 0) (i32.const 0))) +) diff --git a/tests/local/shared-everything-threads/global-heap-types.wast b/tests/local/shared-everything-threads/global-heap-types.wast index 4b887fc126..a94df317d8 100644 --- a/tests/local/shared-everything-threads/global-heap-types.wast +++ b/tests/local/shared-everything-threads/global-heap-types.wast @@ -1,14 +1,5 @@ ;; Check shared attributes for global heap types. -;; Concrete heap types cannot be marked shared yet (TODO: this is only possible -;; once composite types can be marked shared). -;; -;; (assert_invalid -;; (module -;; (type $t (shared anyref)) -;; (global (shared (ref null (shared $t)))))) -;; "shared value type") - ;; `func` references. (module ;; Imported (long/short forms, mut, null). @@ -309,3 +300,89 @@ (module (global (ref (shared none)))) "type mismatch") +;; Concrete `func` references. +(module + (type $t (shared (func))) + + ;; Imported. + (global (import "spectest" "global_t") (shared (ref $t))) + (global (import "spectest" "global_mut_t") (shared mut (ref $t))) + (global (import "spectest" "global_null_t") (shared (ref null $t))) + (global (import "spectest" "global_mut_null_t") (shared mut (ref null $t))) + + ;; Initialized. + (global (shared (ref null $t)) (ref.null $t)) + (global (shared mut (ref null $t)) (ref.null $t)) +) + +(assert_invalid + (module + (type $t (func)) + (global (shared (ref $t)))) + "shared globals must have a shared value type") +(assert_invalid + (module + (type $t (shared (func))) + (global (ref $t))) + "type mismatch") +(assert_invalid + (module (type $t (shared (func (param funcref))))) + "shared composite type must contain shared types") + +;; Concrete `array` references. +(module + (type $t (shared (array i32))) + + ;; Imported. + (global (import "spectest" "global_t") (shared (ref $t))) + (global (import "spectest" "global_mut_t") (shared mut (ref $t))) + (global (import "spectest" "global_null_t") (shared (ref null $t))) + (global (import "spectest" "global_mut_null_t") (shared mut (ref null $t))) + + ;; Initialized. + (global (shared (ref null $t)) (ref.null $t)) + (global (shared mut (ref null $t)) (ref.null $t)) +) + +(assert_invalid + (module + (type $t (array i32)) + (global (shared (ref $t)))) + "shared globals must have a shared value type") +(assert_invalid + (module + (type $t (shared (array i32))) + (global (ref $t))) + "type mismatch") +(assert_invalid + (module (type $t (shared (array funcref)))) + "shared composite type must contain shared types") + +;; Concrete `struct` references. +(module + (type $t (shared (struct (field i32)))) + + ;; Imported. + (global (import "spectest" "global_t") (shared (ref $t))) + (global (import "spectest" "global_mut_t") (shared mut (ref $t))) + (global (import "spectest" "global_null_t") (shared (ref null $t))) + (global (import "spectest" "global_mut_null_t") (shared mut (ref null $t))) + + ;; Initialized. + (global (shared (ref null $t)) (ref.null $t)) + (global (shared mut (ref null $t)) (ref.null $t)) +) + +(assert_invalid + (module + (type $t (struct (field i32))) + (global (shared (ref $t)))) + "shared globals must have a shared value type") +(assert_invalid + (module + (type $t (shared (struct (field i32)))) + (global (ref $t))) + "type mismatch") +(assert_invalid + (module (type $t (shared (struct (field funcref))))) + "shared composite type must contain shared types") diff --git a/tests/local/shared-everything-threads/struct.wast b/tests/local/shared-everything-threads/struct.wast new file mode 100644 index 0000000000..91b1cec268 --- /dev/null +++ b/tests/local/shared-everything-threads/struct.wast @@ -0,0 +1,92 @@ +;; Shared struct declaration syntax +(module + (type (shared (struct))) + (type (sub final (shared (struct)))) + (rec + (type (sub final (shared (struct)))) + ) + + (global (ref 0) (struct.new 1)) + (global (ref 1) (struct.new 2)) + (global (ref 2) (struct.new 0)) +) + +;; Shared structs are distinct from non-shared structs +(assert_invalid + (module + (type (shared (struct))) + (type (struct)) + + (global (ref 0) (struct.new 1)) + ) + "type mismatch" +) + +(assert_invalid + (module + (type (shared (struct))) + (type (struct)) + + (global (ref 1) (struct.new 0)) + ) + "type mismatch" +) + +;; Shared structs may not be subtypes of non-shared structs +(assert_invalid + (module + (type (sub (struct))) + (type (sub 0 (shared (struct)))) + ) + "must match super type" +) + +;; Non-shared structs may not be subtypes of shared structs +(assert_invalid + (module + (type (sub (shared (struct)))) + (type (sub 0 (struct))) + ) + "must match super type" +) + +;; Shared structs may not contain non-shared references +(assert_invalid + (module + (type (shared (struct (field anyref)))) + ) + "must contain shared type" +) + +;; But they may contain shared references +(module + (type (shared (struct (field (ref null (shared any)))))) +) + +;; Non-shared structs may contain shared references +(module + (type (struct (field (ref null (shared any))))) +) + +;; Struct instructions work on shared structs. +(module + (type $i8 (shared (struct (field (mut i8))))) + (type $i32 (shared (struct (field (mut i32))))) + (type $unshared (struct (field (mut i8)))) + + (func (struct.new $i8 (i32.const 0)) (drop)) + + (func (struct.new_default $i8) (drop)) + + (func (param (ref null $i8)) + (struct.get_s $i8 0 (local.get 0)) (drop)) + + (func (param (ref null $i8)) + (struct.get_u $i8 0 (local.get 0)) (drop)) + + (func (param (ref null $i32)) + (struct.get $i32 0 (local.get 0)) (drop)) + + (func (param (ref null $i8)) + (struct.set $i8 0 (local.get 0) (i32.const 0))) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast.json b/tests/snapshots/local/shared-everything-threads/array.wast.json new file mode 100644 index 0000000000..03bf81d778 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast.json @@ -0,0 +1,60 @@ +{ + "source_filename": "tests/local/shared-everything-threads/array.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "array.0.wasm" + }, + { + "type": "assert_invalid", + "line": 16, + "filename": "array.1.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 26, + "filename": "array.2.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 37, + "filename": "array.3.wasm", + "text": "sub type must match super type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 46, + "filename": "array.4.wasm", + "text": "sub type must match super type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 55, + "filename": "array.5.wasm", + "text": "must contain shared type", + "module_type": "binary" + }, + { + "type": "module", + "line": 62, + "filename": "array.6.wasm" + }, + { + "type": "module", + "line": 67, + "filename": "array.7.wasm" + }, + { + "type": "module", + "line": 72, + "filename": "array.8.wasm" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/0.print b/tests/snapshots/local/shared-everything-threads/array.wast/0.print new file mode 100644 index 0000000000..d875a17e3d --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/0.print @@ -0,0 +1,10 @@ +(module + (type (;0;) (shared(array i8))) + (type (;1;) (shared(array i8))) + (rec + (type (;2;) (shared(array i8))) + ) + (global (;0;) (ref 0) i32.const 1 array.new_default 1) + (global (;1;) (ref 1) i32.const 1 array.new_default 2) + (global (;2;) (ref 2) i32.const 1 array.new_default 0) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/6.print b/tests/snapshots/local/shared-everything-threads/array.wast/6.print new file mode 100644 index 0000000000..7ea8fb8fb3 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/6.print @@ -0,0 +1,3 @@ +(module + (type (;0;) (shared(array (ref null (shared any))))) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/7.print b/tests/snapshots/local/shared-everything-threads/array.wast/7.print new file mode 100644 index 0000000000..949220be79 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/7.print @@ -0,0 +1,3 @@ +(module + (type (;0;) (array (ref null (shared any)))) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/8.print b/tests/snapshots/local/shared-everything-threads/array.wast/8.print new file mode 100644 index 0000000000..0355588339 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/8.print @@ -0,0 +1,97 @@ +(module + (type $i8 (;0;) (shared(array (mut i8)))) + (type $i32 (;1;) (shared(array (mut i32)))) + (type $unshared (;2;) (array (mut i8))) + (type (;3;) (func)) + (type (;4;) (func (param (ref null $i8)))) + (type (;5;) (func (param (ref null $i32)))) + (type (;6;) (func (param (ref null $i8) (ref null $i8)))) + (type (;7;) (func (param (ref null $i8) (ref null $unshared)))) + (type (;8;) (func (param (ref null $unshared) (ref null $i8)))) + (func (;0;) (type 3) + i32.const 0 + i32.const 0 + array.new $i8 + drop + ) + (func (;1;) (type 3) + i32.const 0 + array.new_default $i8 + drop + ) + (func (;2;) (type 3) + array.new_fixed $i8 0 + drop + ) + (func (;3;) (type 4) (param (ref null $i8)) + local.get 0 + i32.const 0 + array.get_s $i8 + drop + ) + (func (;4;) (type 4) (param (ref null $i8)) + local.get 0 + i32.const 0 + array.get_u $i8 + drop + ) + (func (;5;) (type 5) (param (ref null $i32)) + local.get 0 + i32.const 0 + array.get $i32 + drop + ) + (func (;6;) (type 4) (param (ref null $i8)) + local.get 0 + i32.const 0 + i32.const 0 + array.set $i8 + ) + (func (;7;) (type 6) (param (ref null $i8) (ref null $i8)) + local.get 0 + i32.const 0 + local.get 1 + i32.const 0 + i32.const 0 + array.copy $i8 $i8 + ) + (func (;8;) (type 7) (param (ref null $i8) (ref null $unshared)) + local.get 0 + i32.const 0 + local.get 1 + i32.const 0 + i32.const 0 + array.copy $i8 $unshared + ) + (func (;9;) (type 8) (param (ref null $unshared) (ref null $i8)) + local.get 0 + i32.const 0 + local.get 1 + i32.const 0 + i32.const 0 + array.copy $unshared $i8 + ) + (func (;10;) (type 4) (param (ref null $i8)) + local.get 0 + i32.const 0 + i32.const 0 + i32.const 0 + array.fill $i8 + ) + (func (;11;) (type 4) (param (ref null $i8)) + local.get 0 + i32.const 0 + i32.const 0 + i32.const 0 + array.init_data $i8 0 + ) + (func (;12;) (type 4) (param (ref null $i8)) + local.get 0 + i32.const 0 + i32.const 0 + i32.const 0 + array.init_data $i8 0 + ) + (elem (;0;) arrayref) + (data (;0;) "") +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json index 526b9da758..e0a82de6cf 100644 --- a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast.json @@ -3,315 +3,393 @@ "commands": [ { "type": "module", - "line": 13, + "line": 4, "filename": "global-heap-types.0.wasm" }, { "type": "assert_invalid", - "line": 28, + "line": 19, "filename": "global-heap-types.1.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 31, + "line": 22, "filename": "global-heap-types.2.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 34, + "line": 25, "filename": "global-heap-types.3.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 38, + "line": 29, "filename": "global-heap-types.4.wasm" }, { "type": "assert_invalid", - "line": 53, + "line": 44, "filename": "global-heap-types.5.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 56, + "line": 47, "filename": "global-heap-types.6.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 59, + "line": 50, "filename": "global-heap-types.7.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 63, + "line": 54, "filename": "global-heap-types.8.wasm" }, { "type": "assert_invalid", - "line": 78, + "line": 69, "filename": "global-heap-types.9.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 81, + "line": 72, "filename": "global-heap-types.10.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 84, + "line": 75, "filename": "global-heap-types.11.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 88, + "line": 79, "filename": "global-heap-types.12.wasm" }, { "type": "assert_invalid", - "line": 103, + "line": 94, "filename": "global-heap-types.13.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 106, + "line": 97, "filename": "global-heap-types.14.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 109, + "line": 100, "filename": "global-heap-types.15.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 113, + "line": 104, "filename": "global-heap-types.16.wasm" }, { "type": "assert_invalid", - "line": 128, + "line": 119, "filename": "global-heap-types.17.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 131, + "line": 122, "filename": "global-heap-types.18.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 134, + "line": 125, "filename": "global-heap-types.19.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 138, + "line": 129, "filename": "global-heap-types.20.wasm" }, { "type": "assert_invalid", - "line": 153, + "line": 144, "filename": "global-heap-types.21.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 156, + "line": 147, "filename": "global-heap-types.22.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 159, + "line": 150, "filename": "global-heap-types.23.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 163, + "line": 154, "filename": "global-heap-types.24.wasm" }, { "type": "assert_invalid", - "line": 178, + "line": 169, "filename": "global-heap-types.25.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 181, + "line": 172, "filename": "global-heap-types.26.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 184, + "line": 175, "filename": "global-heap-types.27.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 188, + "line": 179, "filename": "global-heap-types.28.wasm" }, { "type": "assert_invalid", - "line": 203, + "line": 194, "filename": "global-heap-types.29.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 206, + "line": 197, "filename": "global-heap-types.30.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 209, + "line": 200, "filename": "global-heap-types.31.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 213, + "line": 204, "filename": "global-heap-types.32.wasm" }, { "type": "assert_invalid", - "line": 228, + "line": 219, "filename": "global-heap-types.33.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 231, + "line": 222, "filename": "global-heap-types.34.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 234, + "line": 225, "filename": "global-heap-types.35.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 238, + "line": 229, "filename": "global-heap-types.36.wasm" }, { "type": "assert_invalid", - "line": 253, + "line": 244, "filename": "global-heap-types.37.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 256, + "line": 247, "filename": "global-heap-types.38.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 259, + "line": 250, "filename": "global-heap-types.39.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 263, + "line": 254, "filename": "global-heap-types.40.wasm" }, { "type": "assert_invalid", - "line": 278, + "line": 269, "filename": "global-heap-types.41.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 281, + "line": 272, "filename": "global-heap-types.42.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 284, + "line": 275, "filename": "global-heap-types.43.wasm", "text": "type mismatch", "module_type": "binary" }, { "type": "module", - "line": 288, + "line": 279, "filename": "global-heap-types.44.wasm" }, { "type": "assert_invalid", - "line": 303, + "line": 294, "filename": "global-heap-types.45.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 306, + "line": 297, "filename": "global-heap-types.46.wasm", "text": "shared globals must have a shared value type", "module_type": "binary" }, { "type": "assert_invalid", - "line": 309, + "line": 300, "filename": "global-heap-types.47.wasm", "text": "type mismatch", "module_type": "binary" + }, + { + "type": "module", + "line": 304, + "filename": "global-heap-types.48.wasm" + }, + { + "type": "assert_invalid", + "line": 319, + "filename": "global-heap-types.49.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 324, + "filename": "global-heap-types.50.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 329, + "filename": "global-heap-types.51.wasm", + "text": "shared composite type must contain shared types", + "module_type": "binary" + }, + { + "type": "module", + "line": 333, + "filename": "global-heap-types.52.wasm" + }, + { + "type": "assert_invalid", + "line": 348, + "filename": "global-heap-types.53.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 353, + "filename": "global-heap-types.54.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 358, + "filename": "global-heap-types.55.wasm", + "text": "shared composite type must contain shared types", + "module_type": "binary" + }, + { + "type": "module", + "line": 362, + "filename": "global-heap-types.56.wasm" + }, + { + "type": "assert_invalid", + "line": 377, + "filename": "global-heap-types.57.wasm", + "text": "shared globals must have a shared value type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 382, + "filename": "global-heap-types.58.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 387, + "filename": "global-heap-types.59.wasm", + "text": "shared composite type must contain shared types", + "module_type": "binary" } ] } \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/48.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/48.print new file mode 100644 index 0000000000..23f2967b1a --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/48.print @@ -0,0 +1,9 @@ +(module + (type $t (;0;) (shared(func))) + (import "spectest" "global_t" (global (;0;) (shared (ref $t)))) + (import "spectest" "global_mut_t" (global (;1;) (shared mut (ref $t)))) + (import "spectest" "global_null_t" (global (;2;) (shared (ref null $t)))) + (import "spectest" "global_mut_null_t" (global (;3;) (shared mut (ref null $t)))) + (global (;4;) (shared (ref null $t)) ref.null $t) + (global (;5;) (shared mut (ref null $t)) ref.null $t) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/52.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/52.print new file mode 100644 index 0000000000..82a7f60967 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/52.print @@ -0,0 +1,9 @@ +(module + (type $t (;0;) (shared(array i32))) + (import "spectest" "global_t" (global (;0;) (shared (ref $t)))) + (import "spectest" "global_mut_t" (global (;1;) (shared mut (ref $t)))) + (import "spectest" "global_null_t" (global (;2;) (shared (ref null $t)))) + (import "spectest" "global_mut_null_t" (global (;3;) (shared mut (ref null $t)))) + (global (;4;) (shared (ref null $t)) ref.null $t) + (global (;5;) (shared mut (ref null $t)) ref.null $t) +) diff --git a/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/56.print b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/56.print new file mode 100644 index 0000000000..ddf50f82df --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/global-heap-types.wast/56.print @@ -0,0 +1,9 @@ +(module + (type $t (;0;) (shared(struct (field i32)))) + (import "spectest" "global_t" (global (;0;) (shared (ref $t)))) + (import "spectest" "global_mut_t" (global (;1;) (shared mut (ref $t)))) + (import "spectest" "global_null_t" (global (;2;) (shared (ref null $t)))) + (import "spectest" "global_mut_null_t" (global (;3;) (shared mut (ref null $t)))) + (global (;4;) (shared (ref null $t)) ref.null $t) + (global (;5;) (shared mut (ref null $t)) ref.null $t) +) diff --git a/tests/snapshots/local/shared-everything-threads/struct.wast.json b/tests/snapshots/local/shared-everything-threads/struct.wast.json new file mode 100644 index 0000000000..97900a0eba --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/struct.wast.json @@ -0,0 +1,60 @@ +{ + "source_filename": "tests/local/shared-everything-threads/struct.wast", + "commands": [ + { + "type": "module", + "line": 2, + "filename": "struct.0.wasm" + }, + { + "type": "assert_invalid", + "line": 16, + "filename": "struct.1.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 26, + "filename": "struct.2.wasm", + "text": "type mismatch", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 37, + "filename": "struct.3.wasm", + "text": "must match super type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 46, + "filename": "struct.4.wasm", + "text": "must match super type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 55, + "filename": "struct.5.wasm", + "text": "must contain shared type", + "module_type": "binary" + }, + { + "type": "module", + "line": 62, + "filename": "struct.6.wasm" + }, + { + "type": "module", + "line": 67, + "filename": "struct.7.wasm" + }, + { + "type": "module", + "line": 72, + "filename": "struct.8.wasm" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/struct.wast/0.print b/tests/snapshots/local/shared-everything-threads/struct.wast/0.print new file mode 100644 index 0000000000..d0c306e0e3 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/struct.wast/0.print @@ -0,0 +1,10 @@ +(module + (type (;0;) (shared(struct))) + (type (;1;) (shared(struct))) + (rec + (type (;2;) (shared(struct))) + ) + (global (;0;) (ref 0) struct.new 1) + (global (;1;) (ref 1) struct.new 2) + (global (;2;) (ref 2) struct.new 0) +) diff --git a/tests/snapshots/local/shared-everything-threads/struct.wast/6.print b/tests/snapshots/local/shared-everything-threads/struct.wast/6.print new file mode 100644 index 0000000000..586faeaa4e --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/struct.wast/6.print @@ -0,0 +1,3 @@ +(module + (type (;0;) (shared(struct (field (ref null (shared any)))))) +) diff --git a/tests/snapshots/local/shared-everything-threads/struct.wast/7.print b/tests/snapshots/local/shared-everything-threads/struct.wast/7.print new file mode 100644 index 0000000000..f0fe8e5060 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/struct.wast/7.print @@ -0,0 +1,3 @@ +(module + (type (;0;) (struct (field (ref null (shared any))))) +) diff --git a/tests/snapshots/local/shared-everything-threads/struct.wast/8.print b/tests/snapshots/local/shared-everything-threads/struct.wast/8.print new file mode 100644 index 0000000000..95f477bf46 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/struct.wast/8.print @@ -0,0 +1,37 @@ +(module + (type $i8 (;0;) (shared(struct (field (mut i8))))) + (type $i32 (;1;) (shared(struct (field (mut i32))))) + (type $unshared (;2;) (struct (field (mut i8)))) + (type (;3;) (func)) + (type (;4;) (func (param (ref null $i8)))) + (type (;5;) (func (param (ref null $i32)))) + (func (;0;) (type 3) + i32.const 0 + struct.new $i8 + drop + ) + (func (;1;) (type 3) + struct.new_default $i8 + drop + ) + (func (;2;) (type 4) (param (ref null $i8)) + local.get 0 + struct.get_s $i8 0 + drop + ) + (func (;3;) (type 4) (param (ref null $i8)) + local.get 0 + struct.get_u $i8 0 + drop + ) + (func (;4;) (type 5) (param (ref null $i32)) + local.get 0 + struct.get $i32 0 + drop + ) + (func (;5;) (type 4) (param (ref null $i8)) + local.get 0 + i32.const 0 + struct.set $i8 0 + ) +) From b92dd7905a1c87698f75d02a27f04fae99d5cbf1 Mon Sep 17 00:00:00 2001 From: Mendy Berger <12537668+MendyBerger@users.noreply.github.com> Date: Mon, 8 Jul 2024 09:08:26 -0400 Subject: [PATCH 41/58] A few smaller improvements to wit-encoder (#1648) --- crates/wit-encoder/src/enum_.rs | 8 ++++++++ crates/wit-encoder/src/function.rs | 22 +++++++++++++++++++++- crates/wit-encoder/src/ident.rs | 8 +++++++- crates/wit-encoder/src/interface.rs | 2 +- crates/wit-encoder/src/package.rs | 8 ++++++++ crates/wit-encoder/src/record.rs | 8 ++++++++ crates/wit-encoder/src/resource.rs | 6 +++++- crates/wit-encoder/src/tuple.rs | 6 +++++- crates/wit-encoder/src/ty.rs | 8 ++++++++ crates/wit-encoder/src/variant.rs | 10 +++++++++- 10 files changed, 80 insertions(+), 6 deletions(-) diff --git a/crates/wit-encoder/src/enum_.rs b/crates/wit-encoder/src/enum_.rs index 80c0afc89b..b84f372550 100644 --- a/crates/wit-encoder/src/enum_.rs +++ b/crates/wit-encoder/src/enum_.rs @@ -18,6 +18,14 @@ where } impl Enum { + pub fn empty() -> Self { + Self::default() + } + + pub fn case(&mut self, case: impl Into) { + self.cases.push(case.into()); + } + pub fn cases(&self) -> &[EnumCase] { &self.cases } diff --git a/crates/wit-encoder/src/function.rs b/crates/wit-encoder/src/function.rs index e0ffe79e8c..6c86aa26ab 100644 --- a/crates/wit-encoder/src/function.rs +++ b/crates/wit-encoder/src/function.rs @@ -47,6 +47,10 @@ impl Params { Self::default() } + pub fn push(&mut self, name: impl Into, ty: Type) { + self.items.push((name.into(), ty)); + } + pub fn items(&self) -> &Vec<(Ident, Type)> { &self.items } @@ -115,7 +119,7 @@ impl Results { Results::Anon(type_) } - pub fn named(types: impl IntoIterator, Type)>) -> Results { + pub fn named(types: impl IntoIterator, Type)>) -> Results { Results::Named( types .into_iter() @@ -154,14 +158,30 @@ impl StandaloneFunc { } } + pub fn name(&self) -> &Ident { + &self.name + } + + pub fn name_mut(&mut self) -> &mut Ident { + &mut self.name + } + pub fn params(&mut self, params: impl Into) { self.params = params.into(); } + pub fn params_mut(&mut self) -> &mut Params { + &mut self.params + } + pub fn results(&mut self, results: impl Into) { self.results = results.into(); } + pub fn results_mut(&mut self) -> &mut Results { + &mut self.results + } + pub fn docs(&mut self, docs: Option>) { self.docs = docs.map(|d| d.into()); } diff --git a/crates/wit-encoder/src/ident.rs b/crates/wit-encoder/src/ident.rs index 0d9b86a480..4b7116f5ef 100644 --- a/crates/wit-encoder/src/ident.rs +++ b/crates/wit-encoder/src/ident.rs @@ -29,12 +29,18 @@ impl fmt::Display for Ident { } } +impl AsRef for Ident { + fn as_ref(&self) -> &str { + self.0.as_ref() + } +} + fn is_keyword(name: &str) -> bool { match name { "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" | "float32" | "float64" | "char" | "bool" | "string" | "tuple" | "list" | "option" | "result" | "use" | "type" | "resource" | "func" | "record" | "enum" | "flags" | "variant" | "static" - | "interface" | "world" | "import" | "export" | "package" => true, + | "interface" | "world" | "import" | "export" | "package" | "own" | "borrow" => true, _ => false, } } diff --git a/crates/wit-encoder/src/interface.rs b/crates/wit-encoder/src/interface.rs index 943a36d04f..5641426c62 100644 --- a/crates/wit-encoder/src/interface.rs +++ b/crates/wit-encoder/src/interface.rs @@ -38,7 +38,7 @@ impl Interface { &self.items } - pub fn functions_mut(&mut self) -> &mut Vec { + pub fn items_mut(&mut self) -> &mut Vec { &mut self.items } diff --git a/crates/wit-encoder/src/package.rs b/crates/wit-encoder/src/package.rs index 2c5eca4c23..0289e052d9 100644 --- a/crates/wit-encoder/src/package.rs +++ b/crates/wit-encoder/src/package.rs @@ -36,6 +36,14 @@ impl Package { pub fn world(&mut self, world: World) { self.items.push(PackageItem::World(world)) } + + pub fn items(&self) -> &[PackageItem] { + &self.items + } + + pub fn items_mut(&mut self) -> &mut Vec { + &mut self.items + } } impl Render for Package { diff --git a/crates/wit-encoder/src/record.rs b/crates/wit-encoder/src/record.rs index 4280ca85d9..9f91a8cd19 100644 --- a/crates/wit-encoder/src/record.rs +++ b/crates/wit-encoder/src/record.rs @@ -40,6 +40,14 @@ impl Field { pub fn docs(&mut self, docs: Option>) { self.docs = docs.map(|d| d.into()); } + + pub fn ty(&self) -> &Type { + &self.ty + } + + pub fn ty_mut(&mut self) -> &mut Type { + &mut self.ty + } } impl Into for (N, Type) diff --git a/crates/wit-encoder/src/resource.rs b/crates/wit-encoder/src/resource.rs index 0d78c4cefd..644cca97e6 100644 --- a/crates/wit-encoder/src/resource.rs +++ b/crates/wit-encoder/src/resource.rs @@ -2,7 +2,7 @@ use crate::{ident::Ident, Docs, Params, Results}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Resource { - pub funcs: Vec, + pub(crate) funcs: Vec, } impl Resource { @@ -78,6 +78,10 @@ impl ResourceFunc { self.params = params.into(); } + pub fn params_mut(&mut self) -> &mut Params { + &mut self.params + } + pub fn results(&mut self, results: impl Into) { match &self.kind { ResourceFuncKind::Method(name, _) => { diff --git a/crates/wit-encoder/src/tuple.rs b/crates/wit-encoder/src/tuple.rs index 77554ba574..0bc97bacf5 100644 --- a/crates/wit-encoder/src/tuple.rs +++ b/crates/wit-encoder/src/tuple.rs @@ -2,12 +2,16 @@ use std::fmt::Display; use crate::Type; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Tuple { pub(crate) types: Vec, } impl Tuple { + pub fn empty() -> Self { + Default::default() + } + pub fn types(&self) -> &[Type] { &self.types } diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs index ec2e0a08ae..3e45f6d6ea 100644 --- a/crates/wit-encoder/src/ty.rs +++ b/crates/wit-encoder/src/ty.rs @@ -135,6 +135,14 @@ impl VariantCase { pub fn docs(&mut self, docs: Option>) { self.docs = docs.map(|d| d.into()); } + + pub fn ty(&self) -> Option<&Type> { + self.ty.as_ref() + } + + pub fn ty_mut(&mut self) -> &mut Option { + &mut self.ty + } } impl Into for (N,) diff --git a/crates/wit-encoder/src/variant.rs b/crates/wit-encoder/src/variant.rs index dd52ab273b..2322f9da31 100644 --- a/crates/wit-encoder/src/variant.rs +++ b/crates/wit-encoder/src/variant.rs @@ -1,11 +1,19 @@ use crate::VariantCase; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] pub struct Variant { pub(crate) cases: Vec, } impl Variant { + pub fn empty() -> Self { + Self::default() + } + + pub fn case(&mut self, case: impl Into) { + self.cases.push(case.into()); + } + pub fn cases(&self) -> &[VariantCase] { &self.cases } From 1f8e7e2bd6d9ba19ce4366a89373a58ea3de5e45 Mon Sep 17 00:00:00 2001 From: primoly <168267431+primoly@users.noreply.github.com> Date: Mon, 8 Jul 2024 16:38:25 +0200 Subject: [PATCH 42/58] =?UTF-8?q?Print=20available=20worlds=20in=20the=20?= =?UTF-8?q?=E2=80=9Cmultiple=20worlds=E2=80=9D=20error=20message=20(#1654)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- crates/wit-parser/src/resolve.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/wit-parser/src/resolve.rs b/crates/wit-parser/src/resolve.rs index c5a2f237c1..be539e9e54 100644 --- a/crates/wit-parser/src/resolve.rs +++ b/crates/wit-parser/src/resolve.rs @@ -1021,8 +1021,9 @@ impl Resolve { 0 => bail!("no worlds found in package `{}`", pkg.name), 1 => return Ok(*pkg.worlds.values().next().unwrap()), _ => bail!( - "multiple worlds found in package `{}`: one must be explicitly chosen", - pkg.name + "multiple worlds found in package `{}`, one must be explicitly chosen:{}", + pkg.name, + pkg.worlds.keys().map(|name| format!("\n {name}")).collect::() ), } } From ccbe0573ece5de7e7172c0e29ad39e30d7f30525 Mon Sep 17 00:00:00 2001 From: Suhas Thalanki <54014218+thesuhas@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:08:43 -0400 Subject: [PATCH 43/58] added eq, partialeq trait for operator equality comparison (#1652) * added eq, partialeq trait for operator equality comparison * implemented eq for br table --- .../wasmparser/src/readers/core/operators.rs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/crates/wasmparser/src/readers/core/operators.rs b/crates/wasmparser/src/readers/core/operators.rs index ff3ef8430a..fe77547662 100644 --- a/crates/wasmparser/src/readers/core/operators.rs +++ b/crates/wasmparser/src/readers/core/operators.rs @@ -31,7 +31,7 @@ pub enum BlockType { } /// Represents a memory immediate in a WebAssembly memory instruction. -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Eq, PartialEq)] pub struct MemArg { /// Alignment, stored as `n` where the actual alignment is `2^n` pub align: u8, @@ -64,6 +64,16 @@ pub struct BrTable<'a> { pub(crate) default: u32, } +impl PartialEq for BrTable<'_> { + fn eq(&self, other: &Self) -> bool { + self.cnt == other.cnt + && self.default == other.default + && self.reader.remaining_buffer() == other.reader.remaining_buffer() + } +} + +impl Eq for BrTable<'_> {} + /// An IEEE binary32 immediate floating point value, represented as a u32 /// containing the bit pattern. /// @@ -155,7 +165,7 @@ macro_rules! define_operator { /// Instructions as defined [here]. /// /// [here]: https://webassembly.github.io/spec/core/binary/instructions.html - #[derive(Debug, Clone)] + #[derive(Debug, Clone, Eq, PartialEq)] #[allow(missing_docs)] pub enum Operator<'a> { $( @@ -397,7 +407,7 @@ impl<'a, V: VisitOperator<'a> + ?Sized> VisitOperator<'a> for Box { } /// A `try_table` entries representation. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct TryTable { /// The block type describing the try block itself. pub ty: BlockType, @@ -406,7 +416,7 @@ pub struct TryTable { } /// Catch clauses that can be specified in [`TryTable`]. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] #[allow(missing_docs)] pub enum Catch { /// Equivalent of `catch` From 947c152652c57159ed39a59b9e8f0a060d741490 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Mon, 8 Jul 2024 11:24:53 -0700 Subject: [PATCH 44/58] threads: add `shared` tables (#1649) * threads: add `shared` tables This change spreads `shared` attributes on to tables. As with #1480, this does not yet turn on fuzzing for `shared` things so the only checking is via the in-progress test suite in `tests/local/shared-everything-threads`. Implementing this raises an issue with the spec: the addition of `shared` in the text format means that inline table expressions (e.g., `(table (elem ...))`) are difficult to parse: the parser would potentially have to look past two tokens now, `shared` and `i32|i64` to see if a type appears that indicates we should be parsing an inline format. There are multiple options for what to do here; I opened an issue to figure this out at the spec level first ([#71]). [proposal]: https://github.com/WebAssembly/shared-everything-threads [#71]: https://github.com/WebAssembly/shared-everything-threads/issues/71 * Add a 'missing-features' test for `shared` tables * Add a negative test for indexed types --- crates/wasm-encoder/src/core/elements.rs | 1 + crates/wasm-encoder/src/core/tables.rs | 8 +++ crates/wasm-encoder/src/reencode.rs | 1 + crates/wasm-mutate/src/mutators/translate.rs | 1 + crates/wasm-smith/src/core.rs | 1 + crates/wasmparser/src/readers/core/tables.rs | 4 +- crates/wasmparser/src/readers/core/types.rs | 4 ++ crates/wasmparser/src/validator.rs | 1 + crates/wasmparser/src/validator/core.rs | 26 ++++++++-- crates/wasmparser/src/validator/types.rs | 24 +++++---- crates/wasmprinter/src/lib.rs | 3 ++ crates/wast/src/component/binary.rs | 1 + crates/wast/src/core/binary.rs | 3 ++ .../core/resolve/deinline_import_export.rs | 7 ++- crates/wast/src/core/table.rs | 25 +++++++--- crates/wast/src/core/types.rs | 5 +- crates/wit-component/src/encoding.rs | 1 + crates/wit-component/src/linking.rs | 2 + tests/cli/dump-llvm-object.wat.stdout | 2 +- tests/cli/dump/alias2.wat.stdout | 4 +- tests/cli/dump/module-types.wat.stdout | 2 +- tests/cli/dump/simple.wat.stdout | 2 +- tests/local/missing-features/tables.wast | 5 ++ .../shared-everything-threads/global.wast | 2 +- .../shared-everything-threads/table.wast | 43 ++++++++++++++++ tests/roundtrip.rs | 1 + .../local/missing-features/tables.wast.json | 12 +++++ .../shared-everything-threads/table.wast.json | 50 +++++++++++++++++++ .../table.wast/0.print | 8 +++ .../table.wast/1.print | 3 ++ 30 files changed, 220 insertions(+), 32 deletions(-) create mode 100644 tests/local/missing-features/tables.wast create mode 100644 tests/local/shared-everything-threads/table.wast create mode 100644 tests/snapshots/local/missing-features/tables.wast.json create mode 100644 tests/snapshots/local/shared-everything-threads/table.wast.json create mode 100644 tests/snapshots/local/shared-everything-threads/table.wast/0.print create mode 100644 tests/snapshots/local/shared-everything-threads/table.wast/1.print diff --git a/crates/wasm-encoder/src/core/elements.rs b/crates/wasm-encoder/src/core/elements.rs index 28a1031ed8..6ed6e03e3b 100644 --- a/crates/wasm-encoder/src/core/elements.rs +++ b/crates/wasm-encoder/src/core/elements.rs @@ -18,6 +18,7 @@ use crate::{encode_section, ConstExpr, Encode, RefType, Section, SectionId}; /// minimum: 128, /// maximum: None, /// table64: false, +/// shared: false, /// }); /// /// let mut elements = ElementSection::new(); diff --git a/crates/wasm-encoder/src/core/tables.rs b/crates/wasm-encoder/src/core/tables.rs index 6b35915b0d..3bb2d58e3a 100644 --- a/crates/wasm-encoder/src/core/tables.rs +++ b/crates/wasm-encoder/src/core/tables.rs @@ -15,6 +15,7 @@ use crate::{encode_section, ConstExpr, Encode, RefType, Section, SectionId, ValT /// minimum: 128, /// maximum: None, /// table64: false, +/// shared: false, /// }); /// /// let mut module = Module::new(); @@ -87,6 +88,10 @@ pub struct TableType { pub minimum: u64, /// Maximum size, in elements, of this table pub maximum: Option, + /// Whether this table is shared or not. + /// + /// This is included the shared-everything-threads proposal. + pub shared: bool, } impl TableType { @@ -106,6 +111,9 @@ impl Encode for TableType { if self.maximum.is_some() { flags |= 0b001; } + if self.shared { + flags |= 0b010; + } if self.table64 { flags |= 0b100; } diff --git a/crates/wasm-encoder/src/reencode.rs b/crates/wasm-encoder/src/reencode.rs index 5c6a67116f..c8fb29aa7f 100644 --- a/crates/wasm-encoder/src/reencode.rs +++ b/crates/wasm-encoder/src/reencode.rs @@ -1154,6 +1154,7 @@ pub mod utils { minimum: table_ty.initial, maximum: table_ty.maximum, table64: table_ty.table64, + shared: table_ty.shared, }) } diff --git a/crates/wasm-mutate/src/mutators/translate.rs b/crates/wasm-mutate/src/mutators/translate.rs index 706e7629ad..d1ac04c532 100644 --- a/crates/wasm-mutate/src/mutators/translate.rs +++ b/crates/wasm-mutate/src/mutators/translate.rs @@ -156,6 +156,7 @@ pub fn table_type( minimum: ty.initial, maximum: ty.maximum, table64: ty.table64, + shared: ty.shared, }) } diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index 43acc72b11..2f8b552982 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -2542,6 +2542,7 @@ pub(crate) fn arbitrary_table_type( minimum, maximum, table64, + shared: false, // TODO: handle shared }) } diff --git a/crates/wasmparser/src/readers/core/tables.rs b/crates/wasmparser/src/readers/core/tables.rs index 6c144a578f..7df2a76707 100644 --- a/crates/wasmparser/src/readers/core/tables.rs +++ b/crates/wasmparser/src/readers/core/tables.rs @@ -68,10 +68,11 @@ impl<'a> FromReader<'a> for TableType { let element_type = reader.read()?; let pos = reader.original_position(); let flags = reader.read_u8()?; - if (flags & !0b101) != 0 { + if (flags & !0b111) != 0 { bail!(pos, "invalid table resizable limits flags"); } let has_max = (flags & 0b001) != 0; + let shared = (flags & 0b010) != 0; let table64 = (flags & 0b100) != 0; Ok(TableType { element_type, @@ -88,6 +89,7 @@ impl<'a> FromReader<'a> for TableType { } else { Some(reader.read_var_u32()?.into()) }, + shared, }) } } diff --git a/crates/wasmparser/src/readers/core/types.rs b/crates/wasmparser/src/readers/core/types.rs index f85ca09319..7acc612847 100644 --- a/crates/wasmparser/src/readers/core/types.rs +++ b/crates/wasmparser/src/readers/core/types.rs @@ -1700,6 +1700,10 @@ pub struct TableType { /// For 32-bit tables (when `table64` is `false`) this is guaranteed to /// be at most `u32::MAX` for valid types. pub maximum: Option, + /// Whether this table is shared or not. + /// + /// This is included the shared-everything-threads proposal. + pub shared: bool, } impl TableType { diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index 4396cb51f4..de9e1e42cc 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -1537,6 +1537,7 @@ mod tests { maximum: None, element_type: RefType::FUNCREF, table64: false, + shared: false, } ); diff --git a/crates/wasmparser/src/validator/core.rs b/crates/wasmparser/src/validator/core.rs index 4c78eaf4a9..003dbe5916 100644 --- a/crates/wasmparser/src/validator/core.rs +++ b/crates/wasmparser/src/validator/core.rs @@ -146,7 +146,7 @@ impl ModuleState { offset: usize, ) -> Result<()> { self.module - .check_table_type(&mut table.ty, features, offset)?; + .check_table_type(&mut table.ty, features, types, offset)?; match &table.init { TableInit::RefNull => { @@ -870,7 +870,7 @@ impl Module { EntityType::Func(self.types[*type_index as usize]) } TypeRef::Table(t) => { - self.check_table_type(t, features, offset)?; + self.check_table_type(t, features, types, offset)?; EntityType::Table(*t) } TypeRef::Memory(t) => { @@ -892,10 +892,11 @@ impl Module { &self, ty: &mut TableType, features: &WasmFeatures, + types: &TypeList, offset: usize, ) -> Result<()> { - // the `funcref` value type is allowed all the way back to the MVP, so - // don't check it here + // The `funcref` value type is allowed all the way back to the MVP, so + // don't check it here. if ty.element_type != RefType::FUNCREF { self.check_ref_type(&mut ty.element_type, features, offset)? } @@ -914,6 +915,23 @@ impl Module { offset, )); } + + if ty.shared { + if !features.shared_everything_threads() { + return Err(BinaryReaderError::new( + "shared tables require the shared-everything-threads proposal", + offset, + )); + } + + if !types.reftype_is_shared(ty.element_type) { + return Err(BinaryReaderError::new( + "shared tables must have a shared element type", + offset, + )); + } + } + Ok(()) } diff --git a/crates/wasmparser/src/validator/types.rs b/crates/wasmparser/src/validator/types.rs index 578ba8b236..d713fbeee7 100644 --- a/crates/wasmparser/src/validator/types.rs +++ b/crates/wasmparser/src/validator/types.rs @@ -2834,19 +2834,23 @@ impl TypeList { } /// Is `ty` shared? - /// - /// This is complicated by reference types, since they may have concrete - /// heap types whose shared-ness must be checked by looking at the type they - /// point to. pub fn valtype_is_shared(&self, ty: ValType) -> bool { match ty { ValType::I32 | ValType::I64 | ValType::F32 | ValType::F64 | ValType::V128 => true, - ValType::Ref(rt) => match rt.heap_type() { - HeapType::Abstract { shared, .. } => shared, - HeapType::Concrete(index) => { - self[index.as_core_type_id().unwrap()].composite_type.shared - } - }, + ValType::Ref(rt) => self.reftype_is_shared(rt), + } + } + + /// Is the reference type `ty` shared? + /// + /// This is complicated by concrete heap types whose shared-ness must be + /// checked by looking at the type they point to. + pub fn reftype_is_shared(&self, ty: RefType) -> bool { + match ty.heap_type() { + HeapType::Abstract { shared, .. } => shared, + HeapType::Concrete(index) => { + self[index.as_core_type_id().unwrap()].composite_type.shared + } } } diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index 6c6d2943e9..f8e09f623a 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -1130,6 +1130,9 @@ impl Printer<'_, '_> { self.print_name(&state.core.table_names, state.core.tables)?; self.result.write_str(" ")?; } + if ty.shared { + self.print_type_keyword("shared ")?; + } if ty.table64 { self.print_type_keyword("i64 ")?; } diff --git a/crates/wast/src/component/binary.rs b/crates/wast/src/component/binary.rs index c57d633345..19806666e1 100644 --- a/crates/wast/src/component/binary.rs +++ b/crates/wast/src/component/binary.rs @@ -648,6 +648,7 @@ impl From> for wasm_encoder::TableType { minimum: ty.limits.min, maximum: ty.limits.max, table64: ty.limits.is64, + shared: ty.shared, } } } diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs index 7beaa82e01..47fd4b0dd0 100644 --- a/crates/wast/src/core/binary.rs +++ b/crates/wast/src/core/binary.rs @@ -560,6 +560,9 @@ impl<'a> Encode for TableType<'a> { if self.limits.max.is_some() { flags |= 1 << 0; } + if self.shared { + flags |= 1 << 1; + } if self.limits.is64 { flags |= 1 << 2; } diff --git a/crates/wast/src/core/resolve/deinline_import_export.rs b/crates/wast/src/core/resolve/deinline_import_export.rs index 8dbb5f6a17..06cb870b39 100644 --- a/crates/wast/src/core/resolve/deinline_import_export.rs +++ b/crates/wast/src/core/resolve/deinline_import_export.rs @@ -103,11 +103,13 @@ pub fn run(fields: &mut Vec) { }, }); } - // If data is defined inline insert an explicit `data` module - // field here instead, switching this to a `Normal` memory. + // If data is defined inline insert an explicit `data` + // module field here instead, switching this to a `Normal` + // memory. TableKind::Inline { payload, elem, + shared, is64, } => { let is64 = *is64; @@ -123,6 +125,7 @@ pub fn run(fields: &mut Vec) { is64, }, elem: *elem, + shared: *shared, }, init_expr: None, }; diff --git a/crates/wast/src/core/table.rs b/crates/wast/src/core/table.rs index 3d48903394..80875df650 100644 --- a/crates/wast/src/core/table.rs +++ b/crates/wast/src/core/table.rs @@ -29,7 +29,7 @@ pub enum TableKind<'a> { ty: TableType<'a>, }, - /// A typical memory definition which simply says the limits of the table + /// A typical memory definition which simply says the limits of the table. Normal { /// Table type. ty: TableType<'a>, @@ -37,12 +37,14 @@ pub enum TableKind<'a> { init_expr: Option>, }, - /// The elem segments of this table, starting from 0, explicitly listed + /// The elem segments of this table, starting from 0, explicitly listed. Inline { /// The element type of this table. elem: RefType<'a>, - /// Whether or not this will be creating a 64-bit table + /// Whether or not this will be creating a 64-bit table. is64: bool, + /// Whether this table is shared or not. + shared: bool, /// The element table entries to have, and the length of this list is /// the limits of the table as well. payload: ElemPayload<'a>, @@ -58,13 +60,19 @@ impl<'a> Parse<'a> for Table<'a> { // Afterwards figure out which style this is, either: // - // * `elemtype (elem ...)` - // * `(import "a" "b") limits` - // * `limits` + // * inline: `elemtype (elem ...)` + // * normal: `tabletype` + // * import: `(import "a" "b") tabletype` + // + // Where `tabletype := shared? index_type limits reftype` let mut l = parser.lookahead1(); + let is_shared = l.peek::()?; let has_index_type = l.peek::()? | l.peek::()?; - let kind = if l.peek::()? || (has_index_type && parser.peek2::()?) { + let kind = if l.peek::()? + || ((is_shared || has_index_type) && parser.peek2::()?) + { + let shared = parser.parse::>()?.is_some(); let is64 = if parser.parse::>()?.is_some() { false } else { @@ -82,9 +90,10 @@ impl<'a> Parse<'a> for Table<'a> { TableKind::Inline { elem, is64, + shared, payload, } - } else if has_index_type || l.peek::()? { + } else if is_shared || has_index_type || l.peek::()? { TableKind::Normal { ty: parser.parse()?, init_expr: if !parser.is_empty() { diff --git a/crates/wast/src/core/types.rs b/crates/wast/src/core/types.rs index 48f128c0f2..a20d65e42c 100644 --- a/crates/wast/src/core/types.rs +++ b/crates/wast/src/core/types.rs @@ -587,18 +587,21 @@ impl<'a> Parse<'a> for Limits { } } -/// Configuration for a table of a wasm mdoule +/// Configuration for a table of a wasm module. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct TableType<'a> { /// Limits on the element sizes of this table pub limits: Limits, /// The type of element stored in this table pub elem: RefType<'a>, + /// Whether or not this is a shared table. + pub shared: bool, } impl<'a> Parse<'a> for TableType<'a> { fn parse(parser: Parser<'a>) -> Result { Ok(TableType { + shared: parser.parse::>()?.is_some(), limits: parser.parse()?, elem: parser.parse()?, }) diff --git a/crates/wit-component/src/encoding.rs b/crates/wit-component/src/encoding.rs index f045cb8366..35599208ad 100644 --- a/crates/wit-component/src/encoding.rs +++ b/crates/wit-component/src/encoding.rs @@ -1269,6 +1269,7 @@ impl<'a> EncodingState<'a> { minimum: signatures.len() as u64, maximum: Some(signatures.len() as u64), table64: false, + shared: false, }; tables.table(table_type); diff --git a/crates/wit-component/src/linking.rs b/crates/wit-component/src/linking.rs index a9d3ca95b8..0ba89cbe0b 100644 --- a/crates/wit-component/src/linking.rs +++ b/crates/wit-component/src/linking.rs @@ -468,6 +468,7 @@ fn make_env_module<'a>( minimum: table_offset.into(), maximum: None, table64: false, + shared: false, }); exports.export("__indirect_function_table", ExportKind::Table, 0); module.section(&tables); @@ -560,6 +561,7 @@ fn make_init_module( minimum: 0, maximum: None, table64: false, + shared: false, }, ); diff --git a/tests/cli/dump-llvm-object.wat.stdout b/tests/cli/dump-llvm-object.wat.stdout index a421d7231f..fb518e2210 100644 --- a/tests/cli/dump-llvm-object.wat.stdout +++ b/tests/cli/dump-llvm-object.wat.stdout @@ -47,7 +47,7 @@ | 39 65 32 32 | 65 30 65 45 | 00 02 - 0x98 | 03 65 6e 76 | import [table 0] Import { module: "env", name: "__indirect_function_table", ty: Table(TableType { element_type: funcref, table64: false, initial: 4, maximum: None }) } + 0x98 | 03 65 6e 76 | import [table 0] Import { module: "env", name: "__indirect_function_table", ty: Table(TableType { element_type: funcref, table64: false, initial: 4, maximum: None, shared: false }) } | 19 5f 5f 69 | 6e 64 69 72 | 65 63 74 5f diff --git a/tests/cli/dump/alias2.wat.stdout b/tests/cli/dump/alias2.wat.stdout index 2710d46edf..c88baadeed 100644 --- a/tests/cli/dump/alias2.wat.stdout +++ b/tests/cli/dump/alias2.wat.stdout @@ -133,7 +133,7 @@ 0x161 | 00 | [func 0] type 0 0x162 | 04 04 | table section 0x164 | 01 | 1 count - 0x165 | 70 00 01 | [table 0] Table { ty: TableType { element_type: funcref, table64: false, initial: 1, maximum: None }, init: RefNull } + 0x165 | 70 00 01 | [table 0] Table { ty: TableType { element_type: funcref, table64: false, initial: 1, maximum: None, shared: false }, init: RefNull } 0x168 | 05 03 | memory section 0x16a | 01 | 1 count 0x16b | 00 01 | [memory 0] MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None } @@ -173,7 +173,7 @@ | 00 01 0x1b6 | 00 01 33 03 | import [global 0] Import { module: "", name: "3", ty: Global(GlobalType { content_type: I32, mutable: false, shared: false }) } | 7f 00 - 0x1bc | 00 01 34 01 | import [table 0] Import { module: "", name: "4", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None }) } + 0x1bc | 00 01 34 01 | import [table 0] Import { module: "", name: "4", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None, shared: false }) } | 70 00 01 0x1c3 | 00 0a | custom section 0x1c5 | 04 6e 61 6d | name: "name" diff --git a/tests/cli/dump/module-types.wat.stdout b/tests/cli/dump/module-types.wat.stdout index ddde806ebd..1e36b9d1b7 100644 --- a/tests/cli/dump/module-types.wat.stdout +++ b/tests/cli/dump/module-types.wat.stdout @@ -2,7 +2,7 @@ | 0d 00 01 00 0x8 | 03 23 | core type section 0xa | 01 | 1 count - 0xb | 50 05 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } }), Import(Import { module: "", name: "f", ty: Func(0) }), Import(Import { module: "", name: "g", ty: Global(GlobalType { content_type: I32, mutable: false, shared: false }) }), Import(Import { module: "", name: "t", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None }) }), Import(Import { module: "", name: "m", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }) })]) + 0xb | 50 05 01 60 | [core type 0] Module([Type(SubType { is_final: true, supertype_idx: None, composite_type: CompositeType { inner: Func(FuncType { params: [], results: [] }), shared: false } }), Import(Import { module: "", name: "f", ty: Func(0) }), Import(Import { module: "", name: "g", ty: Global(GlobalType { content_type: I32, mutable: false, shared: false }) }), Import(Import { module: "", name: "t", ty: Table(TableType { element_type: funcref, table64: false, initial: 1, maximum: None, shared: false }) }), Import(Import { module: "", name: "m", ty: Memory(MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None }) })]) | 00 00 00 00 | 01 66 00 00 | 00 00 01 67 diff --git a/tests/cli/dump/simple.wat.stdout b/tests/cli/dump/simple.wat.stdout index 59e8902f66..b01a25e80b 100644 --- a/tests/cli/dump/simple.wat.stdout +++ b/tests/cli/dump/simple.wat.stdout @@ -17,7 +17,7 @@ 0x20 | 01 | [func 3] type 1 0x21 | 04 04 | table section 0x23 | 01 | 1 count - 0x24 | 70 00 01 | [table 0] Table { ty: TableType { element_type: funcref, table64: false, initial: 1, maximum: None }, init: RefNull } + 0x24 | 70 00 01 | [table 0] Table { ty: TableType { element_type: funcref, table64: false, initial: 1, maximum: None, shared: false }, init: RefNull } 0x27 | 05 03 | memory section 0x29 | 01 | 1 count 0x2a | 00 01 | [memory 0] MemoryType { memory64: false, shared: false, initial: 1, maximum: None, page_size_log2: None } diff --git a/tests/local/missing-features/tables.wast b/tests/local/missing-features/tables.wast new file mode 100644 index 0000000000..49e4b6f655 --- /dev/null +++ b/tests/local/missing-features/tables.wast @@ -0,0 +1,5 @@ +(assert_invalid + (module + (table shared 1 funcref) + ) + "shared tables require the shared-everything-threads proposal") diff --git a/tests/local/shared-everything-threads/global.wast b/tests/local/shared-everything-threads/global.wast index 9d47be9ff0..1066fcf406 100644 --- a/tests/local/shared-everything-threads/global.wast +++ b/tests/local/shared-everything-threads/global.wast @@ -1,4 +1,4 @@ -;; Check shared attribute. +;; Check the `shared` attribute on globals. (module ;; Imported. diff --git a/tests/local/shared-everything-threads/table.wast b/tests/local/shared-everything-threads/table.wast new file mode 100644 index 0000000000..0b4eedc2ce --- /dev/null +++ b/tests/local/shared-everything-threads/table.wast @@ -0,0 +1,43 @@ +;; Check the `shared` attribute on tables. + +(module + ;; Imported. + (table (import "spectest" "table_ref") shared 1 (ref null (shared func))) + (table (import "spectest" "table_ref_with_max") shared 1 1 (ref null (shared func))) + + ;; Normal. + (table shared 1 (ref null (shared func))) + (table shared 1 1 (ref null (shared func))) + + ;; Inlined. + (table shared (ref null (shared func)) (elem (ref.null (shared func)))) +) + +;; Note that shared elements can live within an unshared table. +(module + (table (import "spectest" "table_ref") 1 (ref null (shared func))) +) + +(assert_malformed + (module quote "(table 1 shared funcref)") + "unexpected token") + +(assert_malformed + (module quote "(table 1 funcref shared)") + "unexpected token") + +;; The proposal creates too much ambiguity to allow this syntax: the parser +;; would need to lookahead multiple tokens. +(assert_malformed + (module quote "(table shared i64 (ref null (shared func)) (elem (ref.null (shared func))))") + "unexpected token") + +(assert_invalid + (module (table (import "spectest" "table_ref") shared 0 funcref)) + "shared tables must have a shared element type") + +(assert_invalid + (module + (type $t (func)) + (table shared 0 (ref $t))) + "shared tables must have a shared element type") diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 66fdc1e49b..6f1b64c779 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -717,6 +717,7 @@ fn error_matches(error: &str, message: &str) -> bool { // wasmparser implements more features than the default spec // interpreter, so these error looks different. return error.contains("threads must be enabled for shared memories") + || error.contains("shared tables require the shared-everything-threads proposal") || error.contains("invalid table resizable limits flags") // honestly this feels like the spec interpreter is just weird || error.contains("unexpected end-of-file") diff --git a/tests/snapshots/local/missing-features/tables.wast.json b/tests/snapshots/local/missing-features/tables.wast.json new file mode 100644 index 0000000000..5e06332b77 --- /dev/null +++ b/tests/snapshots/local/missing-features/tables.wast.json @@ -0,0 +1,12 @@ +{ + "source_filename": "tests/local/missing-features/tables.wast", + "commands": [ + { + "type": "assert_invalid", + "line": 2, + "filename": "tables.0.wasm", + "text": "shared tables require the shared-everything-threads proposal", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/table.wast.json b/tests/snapshots/local/shared-everything-threads/table.wast.json new file mode 100644 index 0000000000..8c2bc6b10f --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/table.wast.json @@ -0,0 +1,50 @@ +{ + "source_filename": "tests/local/shared-everything-threads/table.wast", + "commands": [ + { + "type": "module", + "line": 3, + "filename": "table.0.wasm" + }, + { + "type": "module", + "line": 17, + "filename": "table.1.wasm" + }, + { + "type": "assert_malformed", + "line": 22, + "filename": "table.2.wat", + "text": "unexpected token", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 26, + "filename": "table.3.wat", + "text": "unexpected token", + "module_type": "text" + }, + { + "type": "assert_malformed", + "line": 32, + "filename": "table.4.wat", + "text": "unexpected token", + "module_type": "text" + }, + { + "type": "assert_invalid", + "line": 36, + "filename": "table.5.wasm", + "text": "shared tables must have a shared element type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 40, + "filename": "table.6.wasm", + "text": "shared tables must have a shared element type", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/table.wast/0.print b/tests/snapshots/local/shared-everything-threads/table.wast/0.print new file mode 100644 index 0000000000..80ca77635d --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/table.wast/0.print @@ -0,0 +1,8 @@ +(module + (import "spectest" "table_ref" (table (;0;) shared 1 (ref null (shared func)))) + (import "spectest" "table_ref_with_max" (table (;1;) shared 1 1 (ref null (shared func)))) + (table (;2;) shared 1 (ref null (shared func))) + (table (;3;) shared 1 1 (ref null (shared func))) + (table (;4;) shared 1 1 (ref null (shared func))) + (elem (;0;) (table 4) (i32.const 0) (ref null (shared func)) (ref.null (shared func))) +) diff --git a/tests/snapshots/local/shared-everything-threads/table.wast/1.print b/tests/snapshots/local/shared-everything-threads/table.wast/1.print new file mode 100644 index 0000000000..56e5cf89be --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/table.wast/1.print @@ -0,0 +1,3 @@ +(module + (import "spectest" "table_ref" (table (;0;) 1 (ref null (shared func)))) +) From a2635eb91772d2de1607acbe14ff92ad16f25d33 Mon Sep 17 00:00:00 2001 From: Suhas Thalanki <54014218+thesuhas@users.noreply.github.com> Date: Tue, 9 Jul 2024 10:22:17 -0400 Subject: [PATCH 45/58] added conversions for component specific enums (#1655) * added conversions for component specific enums * added component_type_index * used correct type indices --- crates/wasm-encoder/src/reencode.rs | 193 ++++++++++++++++++++++++++++ 1 file changed, 193 insertions(+) diff --git a/crates/wasm-encoder/src/reencode.rs b/crates/wasm-encoder/src/reencode.rs index c8fb29aa7f..6c60ba550f 100644 --- a/crates/wasm-encoder/src/reencode.rs +++ b/crates/wasm-encoder/src/reencode.rs @@ -41,6 +41,10 @@ pub trait Reencode { utils::type_index(self, ty) } + fn component_type_index(&mut self, ty: u32) -> u32 { + utils::component_type_index(self, ty) + } + fn abstract_heap_type( &mut self, value: wasmparser::AbstractHeapType, @@ -69,6 +73,45 @@ pub trait Reencode { utils::component_primitive_val_type(self, ty) } + fn component_export_kind( + &mut self, + ty: wasmparser::ComponentExternalKind, + ) -> crate::component::ComponentExportKind { + utils::component_export_kind(self, ty) + } + + fn component_outer_alias_kind( + &mut self, + kind: wasmparser::ComponentOuterAliasKind, + ) -> crate::component::ComponentOuterAliasKind { + utils::component_outer_alias_kind(self, kind) + } + + fn component_val_type( + &mut self, + ty: wasmparser::ComponentValType, + ) -> crate::component::ComponentValType { + utils::component_val_type(self, ty) + } + + fn type_bounds(&mut self, ty: wasmparser::TypeBounds) -> crate::component::TypeBounds { + utils::type_bounds(self, ty) + } + + fn canonical_option( + &mut self, + ty: wasmparser::CanonicalOption, + ) -> crate::component::CanonicalOption { + utils::canonical_option(self, ty) + } + + fn component_type_ref( + &mut self, + ty: wasmparser::ComponentTypeRef, + ) -> crate::component::ComponentTypeRef { + utils::component_type_ref(self, ty) + } + fn const_expr( &mut self, const_expr: wasmparser::ConstExpr, @@ -806,6 +849,116 @@ pub mod utils { } } + pub fn component_export_kind( + _reencoder: &mut T, + ty: wasmparser::ComponentExternalKind, + ) -> crate::component::ComponentExportKind { + match ty { + wasmparser::ComponentExternalKind::Module => crate::ComponentExportKind::Module, + wasmparser::ComponentExternalKind::Func => crate::ComponentExportKind::Func, + wasmparser::ComponentExternalKind::Value => crate::ComponentExportKind::Value, + wasmparser::ComponentExternalKind::Type => crate::ComponentExportKind::Type, + wasmparser::ComponentExternalKind::Instance => crate::ComponentExportKind::Instance, + wasmparser::ComponentExternalKind::Component => crate::ComponentExportKind::Component, + } + } + + pub fn component_outer_alias_kind( + _reencoder: &mut T, + ty: wasmparser::ComponentOuterAliasKind, + ) -> crate::component::ComponentOuterAliasKind { + match ty { + wasmparser::ComponentOuterAliasKind::CoreModule => { + crate::component::ComponentOuterAliasKind::CoreModule + } + wasmparser::ComponentOuterAliasKind::CoreType => { + crate::component::ComponentOuterAliasKind::CoreType + } + wasmparser::ComponentOuterAliasKind::Type => { + crate::component::ComponentOuterAliasKind::Type + } + wasmparser::ComponentOuterAliasKind::Component => { + crate::ComponentOuterAliasKind::Component + } + } + } + + pub fn component_val_type( + reencoder: &mut T, + ty: wasmparser::ComponentValType, + ) -> crate::component::ComponentValType { + match ty { + wasmparser::ComponentValType::Type(u) => { + crate::component::ComponentValType::Type(reencoder.component_type_index(u)) + } + wasmparser::ComponentValType::Primitive(pty) => { + crate::component::ComponentValType::Primitive( + crate::component::PrimitiveValType::from(pty), + ) + } + } + } + + pub fn type_bounds( + reencoder: &mut T, + ty: wasmparser::TypeBounds, + ) -> crate::component::TypeBounds { + match ty { + wasmparser::TypeBounds::Eq(u) => { + crate::component::TypeBounds::Eq(reencoder.component_type_index(u)) + } + wasmparser::TypeBounds::SubResource => crate::component::TypeBounds::SubResource, + } + } + + pub fn component_type_ref( + reencoder: &mut T, + ty: wasmparser::ComponentTypeRef, + ) -> crate::component::ComponentTypeRef { + match ty { + wasmparser::ComponentTypeRef::Module(u) => { + crate::component::ComponentTypeRef::Module(reencoder.component_type_index(u)) + } + wasmparser::ComponentTypeRef::Func(u) => { + crate::component::ComponentTypeRef::Func(reencoder.component_type_index(u)) + } + wasmparser::ComponentTypeRef::Value(valty) => { + crate::component::ComponentTypeRef::Value(reencoder.component_val_type(valty)) + } + wasmparser::ComponentTypeRef::Type(bounds) => { + crate::component::ComponentTypeRef::Type(reencoder.type_bounds(bounds)) + } + wasmparser::ComponentTypeRef::Instance(u) => { + crate::component::ComponentTypeRef::Instance(reencoder.component_type_index(u)) + } + wasmparser::ComponentTypeRef::Component(u) => { + crate::component::ComponentTypeRef::Component(reencoder.component_type_index(u)) + } + } + } + + pub fn canonical_option( + reencoder: &mut T, + ty: wasmparser::CanonicalOption, + ) -> crate::component::CanonicalOption { + match ty { + wasmparser::CanonicalOption::UTF8 => crate::component::CanonicalOption::UTF8, + wasmparser::CanonicalOption::UTF16 => crate::component::CanonicalOption::UTF16, + wasmparser::CanonicalOption::CompactUTF16 => { + crate::component::CanonicalOption::CompactUTF16 + } + wasmparser::CanonicalOption::Memory(u) => { + crate::component::CanonicalOption::Memory(reencoder.memory_index(u)) + } + wasmparser::CanonicalOption::Realloc(u) => { + crate::component::CanonicalOption::Realloc(reencoder.function_index(u)) + } + wasmparser::CanonicalOption::PostReturn(u) => { + crate::component::CanonicalOption::PostReturn(reencoder.function_index(u)) + } + } + } + pub fn memory_index(_reencoder: &mut T, memory: u32) -> u32 { memory } @@ -916,6 +1069,10 @@ pub mod utils { ty } + pub fn component_type_index(_reencoder: &mut T, ty: u32) -> u32 { + ty + } + pub fn tag_type( reencoder: &mut T, tag_ty: wasmparser::TagType, @@ -1620,6 +1777,42 @@ pub mod utils { } } +impl From for crate::ComponentValType { + fn from(ty: wasmparser::ComponentValType) -> Self { + RoundtripReencoder.component_val_type(ty) + } +} + +impl From for crate::TypeBounds { + fn from(ty: wasmparser::TypeBounds) -> Self { + RoundtripReencoder.type_bounds(ty) + } +} + +impl From for crate::CanonicalOption { + fn from(opt: wasmparser::CanonicalOption) -> Self { + RoundtripReencoder.canonical_option(opt) + } +} + +impl From for crate::ComponentExportKind { + fn from(kind: wasmparser::ComponentExternalKind) -> Self { + RoundtripReencoder.component_export_kind(kind) + } +} + +impl From for crate::ComponentOuterAliasKind { + fn from(kind: wasmparser::ComponentOuterAliasKind) -> Self { + RoundtripReencoder.component_outer_alias_kind(kind) + } +} + +impl From for crate::ComponentTypeRef { + fn from(ty: wasmparser::ComponentTypeRef) -> Self { + RoundtripReencoder.component_type_ref(ty) + } +} + impl From for crate::PrimitiveValType { fn from(ty: wasmparser::PrimitiveValType) -> Self { RoundtripReencoder.component_primitive_val_type(ty) From a265ec17ab8ee9081e9390387eafda7d92107f27 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Wed, 10 Jul 2024 16:55:50 +0200 Subject: [PATCH 46/58] feat: support basic use syntax in interfaces and worlds (#1659) * feat: support basic use syntax in interfaces and worlds * fixup --- crates/wit-encoder/src/interface.rs | 11 ++++++- crates/wit-encoder/src/lib.rs | 2 ++ crates/wit-encoder/src/use_.rs | 44 ++++++++++++++++++++++++++++ crates/wit-encoder/src/world.rs | 9 +++++- crates/wit-encoder/tests/use.rs | 45 +++++++++++++++++++++++++++++ 5 files changed, 109 insertions(+), 2 deletions(-) create mode 100644 crates/wit-encoder/src/use_.rs create mode 100644 crates/wit-encoder/tests/use.rs diff --git a/crates/wit-encoder/src/interface.rs b/crates/wit-encoder/src/interface.rs index 5641426c62..bf0827161c 100644 --- a/crates/wit-encoder/src/interface.rs +++ b/crates/wit-encoder/src/interface.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{Docs, Ident, Render, RenderOpts, StandaloneFunc, TypeDef}; +use crate::{Docs, Ident, Render, RenderOpts, StandaloneFunc, TypeDef, Use}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct Interface { @@ -34,6 +34,11 @@ impl Interface { self.items.push(InterfaceItem::Function(function)); } + /// Add a `Use` to the interface + pub fn use_(&mut self, use_: Use) { + self.items.push(InterfaceItem::Use(use_)); + } + pub fn items(&self) -> &[InterfaceItem] { &self.items } @@ -51,6 +56,7 @@ impl Interface { #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub enum InterfaceItem { TypeDef(TypeDef), + Use(Use), Function(StandaloneFunc), } @@ -73,6 +79,9 @@ impl Render for InterfaceItems { } write!(f, ";\n")?; } + InterfaceItem::Use(use_) => { + use_.render(f, opts)?; + } } } Ok(()) diff --git a/crates/wit-encoder/src/lib.rs b/crates/wit-encoder/src/lib.rs index c4e8eb4fc2..902ffc007a 100644 --- a/crates/wit-encoder/src/lib.rs +++ b/crates/wit-encoder/src/lib.rs @@ -17,6 +17,7 @@ mod resource; mod result; mod tuple; mod ty; +mod use_; mod variant; mod world; @@ -34,5 +35,6 @@ pub use resource::*; pub use result::*; pub use tuple::*; pub use ty::*; +pub use use_::*; pub use variant::*; pub use world::*; diff --git a/crates/wit-encoder/src/use_.rs b/crates/wit-encoder/src/use_.rs new file mode 100644 index 0000000000..9322c15d6b --- /dev/null +++ b/crates/wit-encoder/src/use_.rs @@ -0,0 +1,44 @@ +use std::fmt; + +use crate::{Ident, Render}; + +/// Enable the union of a world with another world +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +pub struct Use { + target: Ident, + use_names_list: Vec<(String, Option)>, +} + +impl Use { + pub fn new(use_target: impl Into) -> Self { + Self { + target: use_target.into(), + use_names_list: vec![], + } + } + + pub fn item(&mut self, id: &str, alias: Option<&str>) { + self.use_names_list + .push((id.to_string(), alias.map(|s| s.to_string()))); + } +} + +impl Render for Use { + fn render(&self, f: &mut fmt::Formatter<'_>, opts: &crate::RenderOpts) -> fmt::Result { + let len = self.use_names_list.len(); + + write!(f, "{}use {}.{{ ", opts.spaces(), self.target)?; + for (i, (id, alias)) in self.use_names_list.iter().enumerate() { + if let Some(alias) = alias { + write!(f, "{id} as {alias}")?; + } else { + write!(f, "{id}")?; + } + if i < len - 1 { + write!(f, ", ")?; + } + } + write!(f, " }};\n")?; + Ok(()) + } +} diff --git a/crates/wit-encoder/src/world.rs b/crates/wit-encoder/src/world.rs index 7203524bb1..5f93320924 100644 --- a/crates/wit-encoder/src/world.rs +++ b/crates/wit-encoder/src/world.rs @@ -1,6 +1,6 @@ use std::fmt; -use crate::{ident::Ident, Docs, Include, Interface, Render, RenderOpts, StandaloneFunc}; +use crate::{ident::Ident, Docs, Include, Interface, Render, RenderOpts, StandaloneFunc, Use}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct World { @@ -55,6 +55,9 @@ impl World { pub fn include(&mut self, include: Include) { self.item(WorldItem::Include(include)); } + pub fn use_(&mut self, use_: Use) { + self.item(WorldItem::Use(use_)); + } /// Set the documentation pub fn docs(&mut self, docs: Option>) { @@ -143,6 +146,7 @@ impl Render for World { render_function(f, opts, function)?; } WorldItem::Include(include) => include.render(f, opts)?, + WorldItem::Use(use_) => use_.render(f, opts)?, } } let opts = &opts.outdent(); @@ -173,6 +177,9 @@ pub enum WorldItem { /// Include type Include(Include), + + /// Use + Use(Use), } impl WorldItem { diff --git a/crates/wit-encoder/tests/use.rs b/crates/wit-encoder/tests/use.rs new file mode 100644 index 0000000000..c3d891c6fc --- /dev/null +++ b/crates/wit-encoder/tests/use.rs @@ -0,0 +1,45 @@ +use pretty_assertions::assert_eq; +use wit_encoder::{Interface, Package, PackageName, ResourceFunc, TypeDef, Use, World}; + +const PACKAGE: &str = indoc::indoc! {" + package foo:foo; + + interface foo { + resource bar { + } + } + + interface bar { + use foo.{ bar as foobar }; + resource baz { + } + } + + world baz { + use bar.{ baz }; + } +"}; + +#[test] +fn concrete_types() { + let mut package = Package::new(PackageName::new("foo", "foo", None)); + + let mut interface = Interface::new("foo"); + interface.type_def(TypeDef::resource("bar", Vec::::new())); + package.interface(interface); + + let mut interface = Interface::new("bar"); + let mut use_ = Use::new("foo"); + use_.item("bar", Some("foobar")); + interface.use_(use_); + interface.type_def(TypeDef::resource("baz", Vec::::new())); + package.interface(interface); + + let mut world = World::new("baz"); + let mut use_ = Use::new("bar"); + use_.item("baz", None); + world.use_(use_); + package.world(world); + + assert_eq!(package.to_string(), PACKAGE); +} From 3e58f7d2f459fcf0a7857cf18a7b2b60de16bc45 Mon Sep 17 00:00:00 2001 From: Guy Bedford Date: Thu, 11 Jul 2024 16:33:04 +0200 Subject: [PATCH 47/58] fix: f32, f64 encoding for wit-encoder (#1660) * fix: f32, f64 encoding for wit-encoder * fixup tests * handle escaping at write not create --- crates/wit-encoder/src/ident.rs | 14 ++++++-------- crates/wit-encoder/tests/type_defs.rs | 3 +++ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/crates/wit-encoder/src/ident.rs b/crates/wit-encoder/src/ident.rs index 4b7116f5ef..c336173c68 100644 --- a/crates/wit-encoder/src/ident.rs +++ b/crates/wit-encoder/src/ident.rs @@ -5,12 +5,7 @@ pub struct Ident(Cow<'static, str>); impl Ident { pub fn new(s: impl Into>) -> Self { - let s: Cow<'static, str> = s.into(); - if is_keyword(&s) { - Self(Cow::Owned(format!("%{}", s))) - } else { - Self(s) - } + Self(s.into()) } } @@ -25,6 +20,9 @@ where impl fmt::Display for Ident { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + if is_keyword(&self.0) { + write!(f, "%")?; + } self.0.fmt(f) } } @@ -37,8 +35,8 @@ impl AsRef for Ident { fn is_keyword(name: &str) -> bool { match name { - "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" | "float32" | "float64" - | "char" | "bool" | "string" | "tuple" | "list" | "option" | "result" | "use" | "type" + "u8" | "u16" | "u32" | "u64" | "s8" | "s16" | "s32" | "s64" | "f32" | "f64" | "char" + | "bool" | "string" | "tuple" | "list" | "option" | "result" | "use" | "type" | "resource" | "func" | "record" | "enum" | "flags" | "variant" | "static" | "interface" | "world" | "import" | "export" | "package" | "own" | "borrow" => true, _ => false, diff --git a/crates/wit-encoder/tests/type_defs.rs b/crates/wit-encoder/tests/type_defs.rs index fb2d211a1c..2f96a9a640 100644 --- a/crates/wit-encoder/tests/type_defs.rs +++ b/crates/wit-encoder/tests/type_defs.rs @@ -103,6 +103,7 @@ const PACKAGE: &str = indoc::indoc! {" type t46 = t44; type foo = bar; type bar = u32; + type %f64 = f64; resource t50 { } resource t51 { @@ -211,6 +212,8 @@ fn types() { interface.type_def(TypeDef::type_("foo", Type::named("bar"))); interface.type_def(TypeDef::type_("bar", Type::U32)); + interface.type_def(TypeDef::type_("f64", Type::F64)); + interface.type_def(TypeDef::resource("t50", Vec::::new())); interface.type_def(TypeDef::resource( "t51", From b6b5ad593d6ffdf6f0229aeb6d5281c522f57d80 Mon Sep 17 00:00:00 2001 From: Suhas Thalanki <54014218+thesuhas@users.noreply.github.com> Date: Thu, 11 Jul 2024 15:02:55 -0400 Subject: [PATCH 48/58] added from floating point to ieee trait (#1662) * added from floating point to ieee trait * fixed with little endian conversion --- crates/wasmparser/src/readers/core/operators.rs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/crates/wasmparser/src/readers/core/operators.rs b/crates/wasmparser/src/readers/core/operators.rs index fe77547662..9fa2731abe 100644 --- a/crates/wasmparser/src/readers/core/operators.rs +++ b/crates/wasmparser/src/readers/core/operators.rs @@ -88,6 +88,14 @@ impl Ieee32 { } } +impl From for Ieee32 { + fn from(value: f32) -> Self { + Ieee32 { + 0: u32::from_le_bytes(value.to_le_bytes()), + } + } +} + impl From for f32 { fn from(bits: Ieee32) -> f32 { f32::from_bits(bits.bits()) @@ -108,6 +116,14 @@ impl Ieee64 { } } +impl From for Ieee64 { + fn from(value: f64) -> Self { + Ieee64 { + 0: u64::from_le_bytes(value.to_le_bytes()), + } + } +} + impl From for f64 { fn from(bits: Ieee64) -> f64 { f64::from_bits(bits.bits()) From d4bdb5bc294caea4f851940639e63039f6431716 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 11 Jul 2024 14:04:14 -0500 Subject: [PATCH 49/58] Improve the output of `wasm-tools component wit` (#1657) * Improve the `--out-dir` flag of `component wit` * Lift files in `deps` outside of their folders to be `deps/*.wit` instead of `deps/*/main.wit` since this structure is now supported. * Handle multi-package input documents by placing each package in the main folder. * Name the main wit file after the package name. * Add some tests for these cases. * Print all WIT packages by default This commit changes the output of `wasm-tools component wit` to by default output all WIT packages using the multi-package syntax implemented in #1577. This means that a complete view of the WIT will be seen when running this command instead of just the top-level world. * Fix build of fuzzer * Sanitize output to be more platform agnostic --- crates/wit-component/src/printing.rs | 19 +++++---- crates/wit-component/tests/components.rs | 2 +- crates/wit-component/tests/interfaces.rs | 2 +- crates/wit-component/tests/merge.rs | 2 +- fuzz/src/roundtrip_wit.rs | 2 +- src/bin/wasm-tools/component.rs | 39 +++++++++---------- src/lib.rs | 17 ++++---- tests/cli.rs | 16 +++++++- ...core-wasm-wit-multiple-packages.wit.stdout | 17 ++++++-- tests/cli/print-core-wasm-wit.wit.stdout | 17 ++++++-- .../wit-directory-output-in-deps-folder.wit | 14 +++++++ ...directory-output-in-deps-folder.wit.stdout | 3 ++ tests/cli/wit-directory-output-valid.wit | 14 +++++++ .../cli/wit-directory-output-valid.wit.stdout | 16 ++++++++ tests/cli/wit-directory-output.wit | 14 +++++++ tests/cli/wit-directory-output.wit.stdout | 2 + 16 files changed, 148 insertions(+), 48 deletions(-) create mode 100644 tests/cli/wit-directory-output-in-deps-folder.wit create mode 100644 tests/cli/wit-directory-output-in-deps-folder.wit.stdout create mode 100644 tests/cli/wit-directory-output-valid.wit create mode 100644 tests/cli/wit-directory-output-valid.wit.stdout create mode 100644 tests/cli/wit-directory-output.wit create mode 100644 tests/cli/wit-directory-output.wit.stdout diff --git a/crates/wit-component/src/printing.rs b/crates/wit-component/src/printing.rs index 28406a5023..5c2ce5813f 100644 --- a/crates/wit-component/src/printing.rs +++ b/crates/wit-component/src/printing.rs @@ -51,8 +51,13 @@ impl WitPrinter { } /// Print a set of one or more WIT packages into a string. - pub fn print(&mut self, resolve: &Resolve, pkg_ids: &[PackageId]) -> Result { - let has_multiple_packages = pkg_ids.len() > 1; + pub fn print( + &mut self, + resolve: &Resolve, + pkg_ids: &[PackageId], + force_print_package_in_curlies: bool, + ) -> Result { + let print_package_in_curlies = force_print_package_in_curlies || pkg_ids.len() > 1; for (i, pkg_id) in pkg_ids.into_iter().enumerate() { if i > 0 { self.output.push_str("\n\n"); @@ -68,9 +73,8 @@ impl WitPrinter { self.output.push_str(&format!("@{version}")); } - if has_multiple_packages { - self.output.push_str("{"); - self.output.indent += 1 + if print_package_in_curlies { + self.output.push_str(" {\n"); } else { self.print_semicolon(); self.output.push_str("\n\n"); @@ -96,9 +100,8 @@ impl WitPrinter { writeln!(&mut self.output, "}}")?; } - if has_multiple_packages { - self.output.push_str("}"); - self.output.indent -= 1 + if print_package_in_curlies { + self.output.push_str("}\n"); } } diff --git a/crates/wit-component/tests/components.rs b/crates/wit-component/tests/components.rs index de2ff79c67..9d00e2f764 100644 --- a/crates/wit-component/tests/components.rs +++ b/crates/wit-component/tests/components.rs @@ -171,7 +171,7 @@ fn run_test(path: &Path) -> Result<()> { } }; let wit = WitPrinter::default() - .print(&resolve, &[pkg]) + .print(&resolve, &[pkg], false) .context("failed to print WIT")?; assert_output(&wit, &component_wit_path)?; diff --git a/crates/wit-component/tests/interfaces.rs b/crates/wit-component/tests/interfaces.rs index 87c7ac2d4f..55a4cadd88 100644 --- a/crates/wit-component/tests/interfaces.rs +++ b/crates/wit-component/tests/interfaces.rs @@ -96,7 +96,7 @@ fn run_test(path: &Path, is_dir: bool) -> Result<()> { } fn assert_print(resolve: &Resolve, pkg_ids: &[PackageId], path: &Path, is_dir: bool) -> Result<()> { - let output = WitPrinter::default().print(resolve, &pkg_ids)?; + let output = WitPrinter::default().print(resolve, &pkg_ids, false)?; for pkg_id in pkg_ids { let pkg = &resolve.packages[*pkg_id]; let expected = if is_dir { diff --git a/crates/wit-component/tests/merge.rs b/crates/wit-component/tests/merge.rs index f9a70859bb..3dd25da957 100644 --- a/crates/wit-component/tests/merge.rs +++ b/crates/wit-component/tests/merge.rs @@ -46,7 +46,7 @@ fn merging() -> Result<()> { .join("merge") .join(&pkg.name.name) .with_extension("wit"); - let output = WitPrinter::default().print(&into, &[id])?; + let output = WitPrinter::default().print(&into, &[id], false)?; assert_output(&expected, &output)?; } } diff --git a/fuzz/src/roundtrip_wit.rs b/fuzz/src/roundtrip_wit.rs index b15cd8c415..5cc6678265 100644 --- a/fuzz/src/roundtrip_wit.rs +++ b/fuzz/src/roundtrip_wit.rs @@ -86,7 +86,7 @@ fn roundtrip_through_printing(file: &str, resolve: &Resolve, wasm: &[u8]) { for (id, pkg) in resolve.packages.iter() { let mut map = SourceMap::new(); let pkg_name = &pkg.name; - let doc = WitPrinter::default().print(resolve, &[id]).unwrap(); + let doc = WitPrinter::default().print(resolve, &[id], false).unwrap(); write_file(&format!("{file}-{pkg_name}.wit"), &doc); map.push(format!("{pkg_name}.wit").as_ref(), doc); let unresolved = map.parse().unwrap(); diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index d546f82883..1b15b2f5a4 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -523,9 +523,7 @@ impl WitOpts { // This interprets all of the output options and performs such a task. if self.json { self.emit_json(&decoded)?; - return Ok(()); - } - if self.wasm || self.wat { + } else if self.wasm || self.wat { self.emit_wasm(&decoded)?; } else { self.emit_wit(&decoded)?; @@ -618,7 +616,6 @@ impl WitOpts { assert!(!self.wasm && !self.wat); let resolve = decoded.resolve(); - let main = decoded.packages(); let mut printer = WitPrinter::default(); printer.emit_docs(!self.no_docs); @@ -641,28 +638,31 @@ impl WitOpts { *cnt += 1; } + let main = decoded.packages(); for (id, pkg) in resolve.packages.iter() { - let output = printer.print(resolve, &[id])?; - let out_dir = if main.contains(&id) { + let is_main = main.contains(&id); + let output = printer.print(resolve, &[id], is_main)?; + let out_dir = if is_main { dir.clone() } else { - let dir = dir.join("deps"); - let packages_with_same_name = &names[&pkg.name.name]; - if packages_with_same_name.len() == 1 { - dir.join(&pkg.name.name) + dir.join("deps") + }; + let packages_with_same_name = &names[&pkg.name.name]; + let stem = if packages_with_same_name.len() == 1 { + pkg.name.name.clone() + } else { + let packages_with_same_namespace = + packages_with_same_name[&pkg.name.namespace]; + if packages_with_same_namespace == 1 { + format!("{}:{}", pkg.name.namespace, pkg.name.name) } else { - let packages_with_same_namespace = - packages_with_same_name[&pkg.name.namespace]; - if packages_with_same_namespace == 1 { - dir.join(format!("{}:{}", pkg.name.namespace, pkg.name.name)) - } else { - dir.join(pkg.name.to_string()) - } + pkg.name.to_string() } }; std::fs::create_dir_all(&out_dir) .with_context(|| format!("failed to create directory: {out_dir:?}"))?; - let path = out_dir.join("main.wit"); + let filename = format!("{stem}.wit"); + let path = out_dir.join(&filename); std::fs::write(&path, &output) .with_context(|| format!("failed to write file: {path:?}"))?; println!("Writing: {}", path.display()); @@ -672,8 +672,7 @@ impl WitOpts { self.output.output( &self.general, Output::Wit { - resolve: &resolve, - ids: &main, + wit: &decoded, printer, }, )?; diff --git a/src/lib.rs b/src/lib.rs index b9536eff81..70210b8356 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -143,8 +143,7 @@ pub struct OutputArg { pub enum Output<'a> { #[cfg(feature = "component")] Wit { - resolve: &'a wit_parser::Resolve, - ids: &'a [wit_parser::PackageId], + wit: &'a wit_component::DecodedWasm, printer: wit_component::WitPrinter, }, Wasm(&'a [u8]), @@ -233,12 +232,14 @@ impl OutputArg { } Output::Json(s) => self.output_str(s), #[cfg(feature = "component")] - Output::Wit { - resolve, - ids, - mut printer, - } => { - let output = printer.print(resolve, ids)?; + Output::Wit { wit, mut printer } => { + let resolve = wit.resolve(); + let ids = resolve + .packages + .iter() + .map(|(id, _)| id) + .collect::>(); + let output = printer.print(resolve, &ids, false)?; self.output_str(&output) } } diff --git a/tests/cli.rs b/tests/cli.rs index 3c8d2cfd23..2e9d16645a 100644 --- a/tests/cli.rs +++ b/tests/cli.rs @@ -32,6 +32,7 @@ use std::env; use std::io::Write; use std::path::{Path, PathBuf}; use std::process::{Command, Output, Stdio}; +use tempfile::TempDir; fn main() { let mut tests = Vec::new(); @@ -76,6 +77,7 @@ fn run_test(test: &Path, bless: bool) -> Result<()> { let mut cmd = wasm_tools_exe(); let mut stdin = None; + let tempdir = TempDir::new()?; for arg in line.split_whitespace() { if arg == "|" { let output = execute(&mut cmd, stdin.as_deref(), false)?; @@ -83,6 +85,8 @@ fn run_test(test: &Path, bless: bool) -> Result<()> { cmd = wasm_tools_exe(); } else if arg == "%" { cmd.arg(test); + } else if arg == "%tmpdir" { + cmd.arg(tempdir.path()); } else { cmd.arg(arg); } @@ -94,12 +98,14 @@ fn run_test(test: &Path, bless: bool) -> Result<()> { bless, &output.stdout, &test.with_extension(&format!("{extension}.stdout")), + &tempdir, ) .context("failed to check stdout expectation (auto-update with BLESS=1)")?; assert_output( bless, &output.stderr, &test.with_extension(&format!("{extension}.stderr")), + &tempdir, ) .context("failed to check stderr expectation (auto-update with BLESS=1)")?; Ok(()) @@ -145,7 +151,14 @@ fn execute(cmd: &mut Command, stdin: Option<&[u8]>, should_fail: bool) -> Result Ok(output) } -fn assert_output(bless: bool, output: &[u8], path: &Path) -> Result<()> { +fn assert_output(bless: bool, output: &[u8], path: &Path, tempdir: &TempDir) -> Result<()> { + let tempdir = tempdir.path().to_str().unwrap(); + // sanitize the output to be consistent across platforms and handle per-test + // differences such as `%tmpdir`. + let output = String::from_utf8_lossy(output) + .replace(tempdir, "%tmpdir") + .replace("\\", "/"); + if bless { if output.is_empty() { drop(std::fs::remove_file(path)); @@ -162,7 +175,6 @@ fn assert_output(bless: bool, output: &[u8], path: &Path) -> Result<()> { Ok(()) } } else { - let output = std::str::from_utf8(output)?; let contents = std::fs::read_to_string(path) .with_context(|| format!("failed to read {path:?}"))? .replace("\r\n", "\n"); diff --git a/tests/cli/print-core-wasm-wit-multiple-packages.wit.stdout b/tests/cli/print-core-wasm-wit-multiple-packages.wit.stdout index 75feb032d8..57c65fce90 100644 --- a/tests/cli/print-core-wasm-wit-multiple-packages.wit.stdout +++ b/tests/cli/print-core-wasm-wit-multiple-packages.wit.stdout @@ -1,5 +1,16 @@ -package root:root; +package root:root { + world root { + import bar:bar/my-interface; + } +} + + +package bar:bar { + interface my-interface { + foo: func(); + } -world root { - import bar:bar/my-interface; + world my-world { + import my-interface; + } } diff --git a/tests/cli/print-core-wasm-wit.wit.stdout b/tests/cli/print-core-wasm-wit.wit.stdout index e560f82ea0..c1eb1f6319 100644 --- a/tests/cli/print-core-wasm-wit.wit.stdout +++ b/tests/cli/print-core-wasm-wit.wit.stdout @@ -1,5 +1,16 @@ -package root:root; +package root:root { + world root { + import foo:foo/my-interface; + } +} + + +package foo:foo { + interface my-interface { + foo: func(); + } -world root { - import foo:foo/my-interface; + world my-world { + import my-interface; + } } diff --git a/tests/cli/wit-directory-output-in-deps-folder.wit b/tests/cli/wit-directory-output-in-deps-folder.wit new file mode 100644 index 0000000000..58ff346e05 --- /dev/null +++ b/tests/cli/wit-directory-output-in-deps-folder.wit @@ -0,0 +1,14 @@ +// RUN: component embed --dummy % --world a:b/c | component wit --out-dir %tmpdir + +package a:b { + interface foo {} + + world c { + import foo; + import a:c/foo; + } +} + +package a:c { + interface foo {} +} diff --git a/tests/cli/wit-directory-output-in-deps-folder.wit.stdout b/tests/cli/wit-directory-output-in-deps-folder.wit.stdout new file mode 100644 index 0000000000..8c2fc622ef --- /dev/null +++ b/tests/cli/wit-directory-output-in-deps-folder.wit.stdout @@ -0,0 +1,3 @@ +Writing: %tmpdir/root.wit +Writing: %tmpdir/deps/b.wit +Writing: %tmpdir/deps/c.wit diff --git a/tests/cli/wit-directory-output-valid.wit b/tests/cli/wit-directory-output-valid.wit new file mode 100644 index 0000000000..01e60dca1b --- /dev/null +++ b/tests/cli/wit-directory-output-valid.wit @@ -0,0 +1,14 @@ +// RUN: component wit % --out-dir %tmpdir | component wit %tmpdir + +package a:b { + interface foo {} + + world c { + import foo; + import a:c/foo; + } +} + +package a:c { + interface foo {} +} diff --git a/tests/cli/wit-directory-output-valid.wit.stdout b/tests/cli/wit-directory-output-valid.wit.stdout new file mode 100644 index 0000000000..33576bc7a2 --- /dev/null +++ b/tests/cli/wit-directory-output-valid.wit.stdout @@ -0,0 +1,16 @@ +package a:c { + interface foo { + } + +} + + +package a:b { + interface foo { + } + + world c { + import foo; + import a:c/foo; + } +} diff --git a/tests/cli/wit-directory-output.wit b/tests/cli/wit-directory-output.wit new file mode 100644 index 0000000000..88d6f83d7d --- /dev/null +++ b/tests/cli/wit-directory-output.wit @@ -0,0 +1,14 @@ +// RUN: component wit % --out-dir %tmpdir + +package a:b { + interface foo {} + + world c { + import foo; + import a:c/foo; + } +} + +package a:c { + interface foo {} +} diff --git a/tests/cli/wit-directory-output.wit.stdout b/tests/cli/wit-directory-output.wit.stdout new file mode 100644 index 0000000000..9524cea580 --- /dev/null +++ b/tests/cli/wit-directory-output.wit.stdout @@ -0,0 +1,2 @@ +Writing: %tmpdir/c.wit +Writing: %tmpdir/b.wit From 3895cb78da9b0cade910bb598ac427938e890dcb Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 14:20:33 -0500 Subject: [PATCH 50/58] Release wasm-tools 1.213.0 (#1663) [automatically-tag-and-release-this-commit] Co-authored-by: Auto Release Process --- Cargo.lock | 114 ++++++++++++++++++++--------------------- Cargo.toml | 32 ++++++------ crates/wast/Cargo.toml | 2 +- crates/wat/Cargo.toml | 2 +- 4 files changed, 75 insertions(+), 75 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7fa6f3e0f7..2d39c9f598 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -294,7 +294,7 @@ checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" name = "component" version = "0.0.0" dependencies = [ - "wasmprinter 0.212.0", + "wasmprinter 0.213.0", "wat", "wit-bindgen-rt", ] @@ -1564,7 +1564,7 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasm-compose" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "glob", @@ -1578,9 +1578,9 @@ dependencies = [ "serde_derive", "serde_yaml", "smallvec", - "wasm-encoder 0.212.0", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasm-encoder 0.213.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wat", "wit-component", ] @@ -1596,17 +1596,17 @@ dependencies = [ [[package]] name = "wasm-encoder" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "leb128", "tempfile", - "wasmparser 0.212.0", + "wasmparser 0.213.0", ] [[package]] name = "wasm-metadata" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "clap", @@ -1615,14 +1615,14 @@ dependencies = [ "serde_derive", "serde_json", "spdx", - "wasm-encoder 0.212.0", - "wasmparser 0.212.0", + "wasm-encoder 0.213.0", + "wasmparser 0.213.0", "wat", ] [[package]] name = "wasm-mutate" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "clap", @@ -1631,9 +1631,9 @@ dependencies = [ "log", "rand", "thiserror", - "wasm-encoder 0.212.0", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasm-encoder 0.213.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wat", ] @@ -1650,14 +1650,14 @@ dependencies = [ "num_cpus", "rand", "wasm-mutate", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wasmtime", ] [[package]] name = "wasm-shrink" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "blake3", @@ -1666,14 +1666,14 @@ dependencies = [ "log", "rand", "wasm-mutate", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wat", ] [[package]] name = "wasm-smith" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "arbitrary", @@ -1686,15 +1686,15 @@ dependencies = [ "rand", "serde", "serde_derive", - "wasm-encoder 0.212.0", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasm-encoder 0.213.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wat", ] [[package]] name = "wasm-tools" -version = "1.212.0" +version = "1.213.0" dependencies = [ "addr2line", "anyhow", @@ -1718,18 +1718,18 @@ dependencies = [ "tempfile", "termcolor", "wasm-compose", - "wasm-encoder 0.212.0", + "wasm-encoder 0.213.0", "wasm-metadata", "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wast", "wat", "wit-component", "wit-encoder", - "wit-parser 0.212.0", + "wit-parser 0.213.0", "wit-smith", ] @@ -1741,8 +1741,8 @@ dependencies = [ "wasm-mutate", "wasm-shrink", "wasm-smith", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wast", "wat", ] @@ -1757,28 +1757,28 @@ dependencies = [ "libfuzzer-sys", "log", "tempfile", - "wasm-encoder 0.212.0", + "wasm-encoder 0.213.0", "wasm-mutate", "wasm-smith", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wasmtime", "wast", "wat", "wit-component", - "wit-parser 0.212.0", + "wit-parser 0.213.0", "wit-smith", ] [[package]] name = "wasm-wave" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "indexmap 2.2.6", "logos", "thiserror", - "wit-parser 0.212.0", + "wit-parser 0.213.0", ] [[package]] @@ -1797,7 +1797,7 @@ dependencies = [ [[package]] name = "wasmparser" -version = "0.212.0" +version = "0.213.0" dependencies = [ "ahash", "anyhow", @@ -1811,7 +1811,7 @@ dependencies = [ "rayon", "semver", "serde", - "wasm-encoder 0.212.0", + "wasm-encoder 0.213.0", "wast", "wat", ] @@ -1828,14 +1828,14 @@ dependencies = [ [[package]] name = "wasmprinter" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "diff", "rayon", "tempfile", "termcolor", - "wasmparser 0.212.0", + "wasmparser 0.213.0", "wast", "wat", ] @@ -2034,7 +2034,7 @@ dependencies = [ [[package]] name = "wast" -version = "212.0.0" +version = "213.0.0" dependencies = [ "anyhow", "bumpalo", @@ -2044,14 +2044,14 @@ dependencies = [ "memchr", "rand", "unicode-width", - "wasm-encoder 0.212.0", - "wasmparser 0.212.0", + "wasm-encoder 0.213.0", + "wasmparser 0.213.0", "wat", ] [[package]] name = "wat" -version = "1.212.0" +version = "1.213.0" dependencies = [ "wast", ] @@ -2188,7 +2188,7 @@ dependencies = [ [[package]] name = "wit-component" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "bitflags", @@ -2201,19 +2201,19 @@ dependencies = [ "serde", "serde_derive", "serde_json", - "wasm-encoder 0.212.0", + "wasm-encoder 0.213.0", "wasm-metadata", - "wasmparser 0.212.0", - "wasmprinter 0.212.0", + "wasmparser 0.213.0", + "wasmprinter 0.213.0", "wasmtime", "wast", "wat", - "wit-parser 0.212.0", + "wit-parser 0.213.0", ] [[package]] name = "wit-encoder" -version = "0.212.0" +version = "0.213.0" dependencies = [ "indoc", "pretty_assertions", @@ -2240,7 +2240,7 @@ dependencies = [ [[package]] name = "wit-parser" -version = "0.212.0" +version = "0.213.0" dependencies = [ "anyhow", "env_logger", @@ -2254,9 +2254,9 @@ dependencies = [ "serde_derive", "serde_json", "unicode-xid", - "wasmparser 0.212.0", + "wasmparser 0.213.0", "wat", - "wit-parser 0.212.0", + "wit-parser 0.213.0", ] [[package]] @@ -2267,13 +2267,13 @@ dependencies = [ "env_logger", "libfuzzer-sys", "log", - "wasmprinter 0.212.0", - "wit-parser 0.212.0", + "wasmprinter 0.213.0", + "wit-parser 0.213.0", ] [[package]] name = "wit-smith" -version = "0.212.0" +version = "0.213.0" dependencies = [ "arbitrary", "clap", @@ -2281,7 +2281,7 @@ dependencies = [ "log", "semver", "wit-component", - "wit-parser 0.212.0", + "wit-parser 0.213.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index b9ba2fe4c3..1ac79b1080 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wasm-tools" -version = "1.212.0" +version = "1.213.0" authors = ["The Wasmtime Project Developers"] edition.workspace = true description = "CLI tools for interoperating with WebAssembly files" @@ -52,7 +52,7 @@ all = "allow" [workspace.package] edition = '2021' -version = "0.212.0" +version = "0.213.0" # Current policy for wasm-tools is the same as Wasmtime which is that this # number can be no larger than the current stable release of Rust minus 2. rust-version = "1.76.0" @@ -87,20 +87,20 @@ termcolor = "1.2.0" indoc = "2.0.5" gimli = "0.30.0" -wasm-compose = { version = "0.212.0", path = "crates/wasm-compose" } -wasm-encoder = { version = "0.212.0", path = "crates/wasm-encoder" } -wasm-metadata = { version = "0.212.0", path = "crates/wasm-metadata" } -wasm-mutate = { version = "0.212.0", path = "crates/wasm-mutate" } -wasm-shrink = { version = "0.212.0", path = "crates/wasm-shrink" } -wasm-smith = { version = "0.212.0", path = "crates/wasm-smith" } -wasmparser = { version = "0.212.0", path = "crates/wasmparser", default-features = false, features = ['std'] } -wasmprinter = { version = "0.212.0", path = "crates/wasmprinter" } -wast = { version = "212.0.0", path = "crates/wast" } -wat = { version = "1.212.0", path = "crates/wat" } -wit-component = { version = "0.212.0", path = "crates/wit-component" } -wit-encoder = { version = "0.212.0", path = "crates/wit-encoder" } -wit-parser = { version = "0.212.0", path = "crates/wit-parser" } -wit-smith = { version = "0.212.0", path = "crates/wit-smith" } +wasm-compose = { version = "0.213.0", path = "crates/wasm-compose" } +wasm-encoder = { version = "0.213.0", path = "crates/wasm-encoder" } +wasm-metadata = { version = "0.213.0", path = "crates/wasm-metadata" } +wasm-mutate = { version = "0.213.0", path = "crates/wasm-mutate" } +wasm-shrink = { version = "0.213.0", path = "crates/wasm-shrink" } +wasm-smith = { version = "0.213.0", path = "crates/wasm-smith" } +wasmparser = { version = "0.213.0", path = "crates/wasmparser", default-features = false, features = ['std'] } +wasmprinter = { version = "0.213.0", path = "crates/wasmprinter" } +wast = { version = "213.0.0", path = "crates/wast" } +wat = { version = "1.213.0", path = "crates/wat" } +wit-component = { version = "0.213.0", path = "crates/wit-component" } +wit-encoder = { version = "0.213.0", path = "crates/wit-encoder" } +wit-parser = { version = "0.213.0", path = "crates/wit-parser" } +wit-smith = { version = "0.213.0", path = "crates/wit-smith" } [dependencies] anyhow = { workspace = true } diff --git a/crates/wast/Cargo.toml b/crates/wast/Cargo.toml index ce098c47be..728c109b0d 100644 --- a/crates/wast/Cargo.toml +++ b/crates/wast/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wast" -version = "212.0.0" +version = "213.0.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" diff --git a/crates/wat/Cargo.toml b/crates/wat/Cargo.toml index 489b4c6071..15f82f362e 100644 --- a/crates/wat/Cargo.toml +++ b/crates/wat/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "wat" -version = "1.212.0" +version = "1.213.0" authors = ["Alex Crichton "] edition.workspace = true license = "Apache-2.0 WITH LLVM-exception" From 021389c149dc7fa0c0bac97c0bd84ffe4986d889 Mon Sep 17 00:00:00 2001 From: Damian <11998334+trzeciak@users.noreply.github.com> Date: Fri, 12 Jul 2024 16:18:12 +0200 Subject: [PATCH 51/58] Increase MAX_WASM_STRING_SIZE slightly (#1650) * Increase MAX_WASM_STRING_SIZE slightly * Revert "Increase MAX_WASM_STRING_SIZE slightly" This reverts commit 2f369476b94878b44f6102d431cc904c500ac572. * Reads a string of unlimited length during parse custom name section * Reads a string of unlimited length during parse custom name section --- crates/wasmparser/src/binary_reader.rs | 19 +++++++++++++++---- crates/wasmparser/src/readers/core/names.rs | 2 +- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs index 4df0029072..7f77662dcc 100644 --- a/crates/wasmparser/src/binary_reader.rs +++ b/crates/wasmparser/src/binary_reader.rs @@ -688,6 +688,14 @@ impl<'a> BinaryReader<'a> { Ok(Ieee64(value)) } + /// (internal) Reads a fixed-size WebAssembly string from the module. + fn internal_read_string(&mut self, len: usize) -> Result<&'a str> { + let bytes = self.read_bytes(len)?; + str::from_utf8(bytes).map_err(|_| { + BinaryReaderError::new("malformed UTF-8 encoding", self.original_position() - 1) + }) + } + /// Reads a WebAssembly string from the module. /// # Errors /// If `BinaryReader` has less than up to four bytes remaining, the string's @@ -701,10 +709,13 @@ impl<'a> BinaryReader<'a> { self.original_position() - 1, )); } - let bytes = self.read_bytes(len)?; - str::from_utf8(bytes).map_err(|_| { - BinaryReaderError::new("malformed UTF-8 encoding", self.original_position() - 1) - }) + return self.internal_read_string(len); + } + + /// Reads a unlimited WebAssembly string from the module. + pub fn read_unlimited_string(&mut self) -> Result<&'a str> { + let len = self.read_var_u32()? as usize; + return self.internal_read_string(len); } #[cold] diff --git a/crates/wasmparser/src/readers/core/names.rs b/crates/wasmparser/src/readers/core/names.rs index 986e89506c..ef192acb91 100644 --- a/crates/wasmparser/src/readers/core/names.rs +++ b/crates/wasmparser/src/readers/core/names.rs @@ -33,7 +33,7 @@ pub struct Naming<'a> { impl<'a> FromReader<'a> for Naming<'a> { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { let index = reader.read_var_u32()?; - let name = reader.read_string()?; + let name = reader.read_unlimited_string()?; Ok(Naming { index, name }) } } From a63834a05df2cf9e448e4fc05379ccce45130984 Mon Sep 17 00:00:00 2001 From: Karthik Ganeshram Date: Fri, 12 Jul 2024 17:02:11 +0200 Subject: [PATCH 52/58] Handle multiple versions during `wasm-tools component wit` (#1665) * Handle multiple versions during `wasm-tools component wit` This commits makes sure that if a component depends on multiple versions of a package that it is handled correctly. It fixes the behavior by appending the version string in case there are multiple packages with the same name and namespaces specifically when there is only one namespace for the given package name. Signed-off-by: karthik2804 * add tests Signed-off-by: karthik2804 --------- Signed-off-by: karthik2804 --- src/bin/wasm-tools/component.rs | 13 +++++++++--- ...t-in-deps-folder-with-multiple-version.wit | 16 +++++++++++++++ ...ps-folder-with-multiple-version.wit.stdout | 20 +++++++++++++++++++ 3 files changed, 46 insertions(+), 3 deletions(-) create mode 100644 tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit create mode 100644 tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit.stdout diff --git a/src/bin/wasm-tools/component.rs b/src/bin/wasm-tools/component.rs index 1b15b2f5a4..adc24f30d2 100644 --- a/src/bin/wasm-tools/component.rs +++ b/src/bin/wasm-tools/component.rs @@ -648,11 +648,18 @@ impl WitOpts { dir.join("deps") }; let packages_with_same_name = &names[&pkg.name.name]; + let packages_with_same_namespace = packages_with_same_name[&pkg.name.namespace]; let stem = if packages_with_same_name.len() == 1 { - pkg.name.name.clone() + if packages_with_same_namespace == 1 { + pkg.name.name.clone() + } else { + pkg.name + .version + .as_ref() + .map(|ver| format!("{}@{}", pkg.name.name, ver)) + .unwrap_or_else(|| pkg.name.name.clone()) + } } else { - let packages_with_same_namespace = - packages_with_same_name[&pkg.name.namespace]; if packages_with_same_namespace == 1 { format!("{}:{}", pkg.name.namespace, pkg.name.name) } else { diff --git a/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit b/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit new file mode 100644 index 0000000000..980d99db88 --- /dev/null +++ b/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit @@ -0,0 +1,16 @@ +// RUN: component wit % --out-dir %tmpdir | component wit %tmpdir + +package a:b@0.2.0 { + interface foo {} +} + +package a:b { + interface foo {} +} + +package a:c { + world a { + import a:b/foo@0.2.0; + import a:b/foo; + } +} \ No newline at end of file diff --git a/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit.stdout b/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit.stdout new file mode 100644 index 0000000000..f306ee9c73 --- /dev/null +++ b/tests/cli/wit-directory-output-in-deps-folder-with-multiple-version.wit.stdout @@ -0,0 +1,20 @@ +package a:b { + interface foo { + } + +} + + +package a:b@0.2.0 { + interface foo { + } + +} + + +package a:c { + world a { + import a:b/foo@0.2.0; + import a:b/foo; + } +} From 00cc81c704c98028a299e651be5d65b0953b5d76 Mon Sep 17 00:00:00 2001 From: Damian <11998334+trzeciak@users.noreply.github.com> Date: Fri, 12 Jul 2024 17:26:58 +0200 Subject: [PATCH 53/58] Bring back legacy exception handling opcodes (#1661) * Bring back legacy exception handling opcodes under flag * Bring back legacy exception handling opcodes under flag (prepare place for tests) * Bring back legacy exception handling opcodes under flag (fixes) * Bring back legacy exception handling opcodes under flag (fixes) * Bring back legacy exception handling opcodes under flag (fixes) * Bring back legacy exception handling opcodes under flag (fixes) * Bring back legacy exception handling opcodes under flag (fixes) --- crates/wasmparser/src/features.rs | 7 ++ crates/wasmparser/src/lib.rs | 10 +- crates/wasmparser/src/validator/operators.rs | 108 +++++++++++++++--- src/bin/wasm-tools/validate.rs | 1 + tests/local/legacy-exceptions.wat | 17 +++ tests/roundtrip.rs | 8 +- .../local/legacy-exceptions.wat.print | 16 +++ 7 files changed, 145 insertions(+), 22 deletions(-) create mode 100644 tests/local/legacy-exceptions.wat create mode 100644 tests/snapshots/local/legacy-exceptions.wat.print diff --git a/crates/wasmparser/src/features.rs b/crates/wasmparser/src/features.rs index 6478cf9c91..be5e14641b 100644 --- a/crates/wasmparser/src/features.rs +++ b/crates/wasmparser/src/features.rs @@ -148,6 +148,13 @@ define_wasm_features! { pub component_model_nested_names: COMPONENT_MODEL_NESTED_NAMES(1 << 22) = false; /// Support for more than 32 flags per-type in the component model. pub component_model_more_flags: COMPONENT_MODEL_MORE_FLAGS(1 << 23) = false; + /// The WebAssembly legacy exception handling proposal (phase 1) + /// + /// # Note + /// + /// Support this feature as long as all leading browsers also support it + /// https://github.com/WebAssembly/exception-handling/blob/main/proposals/exception-handling/legacy/Exceptions.md + pub legacy_exceptions: LEGACY_EXCEPTIONS(1 << 24) = false; } } diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs index 06b1a9add5..18183a4875 100644 --- a/crates/wasmparser/src/lib.rs +++ b/crates/wasmparser/src/lib.rs @@ -158,11 +158,11 @@ macro_rules! for_each_operator { @exceptions Throw { tag_index: u32 } => visit_throw @exceptions ThrowRef => visit_throw_ref // Deprecated old instructions from the exceptions proposal - @exceptions Try { blockty: $crate::BlockType } => visit_try - @exceptions Catch { tag_index: u32 } => visit_catch - @exceptions Rethrow { relative_depth: u32 } => visit_rethrow - @exceptions Delegate { relative_depth: u32 } => visit_delegate - @exceptions CatchAll => visit_catch_all + @legacy_exceptions Try { blockty: $crate::BlockType } => visit_try + @legacy_exceptions Catch { tag_index: u32 } => visit_catch + @legacy_exceptions Rethrow { relative_depth: u32 } => visit_rethrow + @legacy_exceptions Delegate { relative_depth: u32 } => visit_delegate + @legacy_exceptions CatchAll => visit_catch_all @mvp End => visit_end @mvp Br { relative_depth: u32 } => visit_br @mvp BrIf { relative_depth: u32 } => visit_br_if diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs index e0f2c7aeef..824fe093a1 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -121,6 +121,24 @@ pub enum FrameKind { /// /// This belongs to the Wasm exception handling proposal. TryTable, + /// A Wasm legacy `try` control block. + /// + /// # Note + /// + /// See: `WasmFeatures::legacy_exceptions` Note in `crates/wasmparser/src/features.rs` + LegacyTry, + /// A Wasm legacy `catch` control block. + /// + /// # Note + /// + /// See: `WasmFeatures::legacy_exceptions` Note in `crates/wasmparser/src/features.rs` + LegacyCatch, + /// A Wasm legacy `catch_all` control block. + /// + /// # Note + /// + /// See: `WasmFeatures::legacy_exceptions` Note in `crates/wasmparser/src/features.rs` + LegacyCatchAll, } struct OperatorValidatorTemp<'validator, 'resources, T> { @@ -1322,6 +1340,7 @@ macro_rules! validate_proposal { (desc function_references) => ("function references"); (desc memory_control) => ("memory control"); (desc gc) => ("gc"); + (desc legacy_exceptions) => ("legacy exceptions"); } impl<'a, T> VisitOperator<'a> for WasmProposalValidator<'_, '_, T> @@ -1486,21 +1505,6 @@ where self.unreachable()?; Ok(()) } - fn visit_try(&mut self, _: BlockType) -> Self::Output { - bail!(self.offset, "unimplemented validation of deprecated opcode") - } - fn visit_catch(&mut self, _: u32) -> Self::Output { - bail!(self.offset, "unimplemented validation of deprecated opcode") - } - fn visit_rethrow(&mut self, _: u32) -> Self::Output { - bail!(self.offset, "unimplemented validation of deprecated opcode") - } - fn visit_delegate(&mut self, _: u32) -> Self::Output { - bail!(self.offset, "unimplemented validation of deprecated opcode") - } - fn visit_catch_all(&mut self) -> Self::Output { - bail!(self.offset, "unimplemented validation of deprecated opcode") - } fn visit_end(&mut self) -> Self::Output { let mut frame = self.pop_ctrl()?; @@ -4124,6 +4128,80 @@ where self.pop_operand(Some(ValType::Ref(RefType::I31REF)))?; self.push_operand(ValType::I32) } + fn visit_try(&mut self, mut ty: BlockType) -> Self::Output { + self.check_block_type(&mut ty)?; + for ty in self.params(ty)?.rev() { + self.pop_operand(Some(ty))?; + } + self.push_ctrl(FrameKind::LegacyTry, ty)?; + Ok(()) + } + fn visit_catch(&mut self, index: u32) -> Self::Output { + let frame = self.pop_ctrl()?; + if frame.kind != FrameKind::LegacyTry && frame.kind != FrameKind::LegacyCatch { + bail!(self.offset, "catch found outside of an `try` block"); + } + // Start a new frame and push `exnref` value. + let height = self.operands.len(); + let init_height = self.inits.len(); + self.control.push(Frame { + kind: FrameKind::LegacyCatch, + block_type: frame.block_type, + height, + unreachable: false, + init_height, + }); + // Push exception argument types. + let ty = self.tag_at(index)?; + for ty in ty.params() { + self.push_operand(*ty)?; + } + Ok(()) + } + fn visit_rethrow(&mut self, relative_depth: u32) -> Self::Output { + // This is not a jump, but we need to check that the `rethrow` + // targets an actual `catch` to get the exception. + let (_, kind) = self.jump(relative_depth)?; + if kind != FrameKind::LegacyCatch && kind != FrameKind::LegacyCatchAll { + bail!( + self.offset, + "invalid rethrow label: target was not a `catch` block" + ); + } + self.unreachable()?; + Ok(()) + } + fn visit_delegate(&mut self, relative_depth: u32) -> Self::Output { + let frame = self.pop_ctrl()?; + if frame.kind != FrameKind::LegacyTry { + bail!(self.offset, "delegate found outside of an `try` block"); + } + // This operation is not a jump, but we need to check the + // depth for validity + let _ = self.jump(relative_depth)?; + for ty in self.results(frame.block_type)? { + self.push_operand(ty)?; + } + Ok(()) + } + fn visit_catch_all(&mut self) -> Self::Output { + let frame = self.pop_ctrl()?; + if frame.kind == FrameKind::LegacyCatchAll { + bail!(self.offset, "only one catch_all allowed per `try` block"); + } else if frame.kind != FrameKind::LegacyTry && frame.kind != FrameKind::LegacyCatch { + bail!(self.offset, "catch_all found outside of a `try` block"); + } + let height = self.operands.len(); + let init_height = self.inits.len(); + self.control.push(Frame { + kind: FrameKind::LegacyCatchAll, + block_type: frame.block_type, + height, + unreachable: false, + init_height, + }); + Ok(()) + } } #[derive(Clone, Debug)] diff --git a/src/bin/wasm-tools/validate.rs b/src/bin/wasm-tools/validate.rs index c0268ccb0d..fe402b1390 100644 --- a/src/bin/wasm-tools/validate.rs +++ b/src/bin/wasm-tools/validate.rs @@ -201,6 +201,7 @@ fn parse_features(arg: &str) -> Result { ("mutable-global", WasmFeatures::MUTABLE_GLOBAL), ("relaxed-simd", WasmFeatures::RELAXED_SIMD), ("gc", WasmFeatures::GC), + ("legacy-exceptions", WasmFeatures::LEGACY_EXCEPTIONS), ]; for part in arg.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) { diff --git a/tests/local/legacy-exceptions.wat b/tests/local/legacy-exceptions.wat new file mode 100644 index 0000000000..f923a69ae1 --- /dev/null +++ b/tests/local/legacy-exceptions.wat @@ -0,0 +1,17 @@ +;; --enable-legacy-exceptions +(module + (type (;0;) (func)) + (func (;0;) (type 0) + try ;; label = @1 + try ;; label = @2 + try ;; label = @3 + throw 0 + catch_all + rethrow 0 (;@3;) + end + delegate 0 (;@2;) + catch 0 + end + ) + (tag (;0;) (type 0)) +) diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 6f1b64c779..4a8e4f2b6a 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -579,7 +579,8 @@ impl TestState { & !WasmFeatures::SHARED_EVERYTHING_THREADS & !WasmFeatures::COMPONENT_MODEL & !WasmFeatures::COMPONENT_MODEL_NESTED_NAMES - & !WasmFeatures::COMPONENT_MODEL_MORE_FLAGS; + & !WasmFeatures::COMPONENT_MODEL_MORE_FLAGS + & !WasmFeatures::LEGACY_EXCEPTIONS; for part in test.iter().filter_map(|t| t.to_str()) { match part { "testsuite" => { @@ -604,6 +605,7 @@ impl TestState { } "simd" => features.insert(WasmFeatures::SIMD), "exception-handling" => features.insert(WasmFeatures::EXCEPTIONS), + "legacy-exceptions.wat" => features.insert(WasmFeatures::LEGACY_EXCEPTIONS), "tail-call" => features.insert(WasmFeatures::TAIL_CALL), "memory64" => features.insert(WasmFeatures::MEMORY64), "component-model" => features.insert(WasmFeatures::COMPONENT_MODEL), @@ -655,11 +657,13 @@ fn error_matches(error: &str, message: &str) -> bool { || message == "alignment must be a power of two" || message == "i32 constant out of range" || message == "constant expression required" + || message == "legacy exceptions support is not enabled" { return error.contains("expected ") || error.contains("constant out of range") || error.contains("extra tokens remaining") - || error.contains("unimplemented validation of deprecated opcode"); + || error.contains("unimplemented validation of deprecated opcode") + || error.contains("legacy exceptions support is not enabled"); } if message == "illegal character" { diff --git a/tests/snapshots/local/legacy-exceptions.wat.print b/tests/snapshots/local/legacy-exceptions.wat.print new file mode 100644 index 0000000000..8ae9aa1a02 --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions.wat.print @@ -0,0 +1,16 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + try ;; label = @1 + try ;; label = @2 + try ;; label = @3 + throw 0 + catch_all + rethrow 0 (;@3;) + end + delegate 0 (;@1;) + catch 0 + end + ) + (tag (;0;) (type 0)) +) From 56ee29dcb7aa671aab4174b7ae1022f0e9f4fcff Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 12 Jul 2024 10:50:23 -0500 Subject: [PATCH 54/58] Refactor handling of `--features` on the CLI (#1668) This commit uses the `bitflags::Flags` trait to refactor the parsing of the `--features` CLI flag to avoid needing to manually list out wasm features. This is something I at least always personally forget during reviews and such so it should make the CLI support more robust. --- Cargo.lock | 1 + Cargo.toml | 3 +- src/addr2line.rs | 2 +- src/bin/wasm-tools/validate.rs | 71 ++++++------------- tests/cli/validate-features.wat | 5 ++ tests/cli/validate-features.wat.stderr | 1 + tests/cli/validate-features2.wat | 6 ++ tests/cli/validate-unknown-features.wat | 4 ++ .../cli/validate-unknown-features.wat.stderr | 4 ++ 9 files changed, 45 insertions(+), 52 deletions(-) create mode 100644 tests/cli/validate-features.wat create mode 100644 tests/cli/validate-features.wat.stderr create mode 100644 tests/cli/validate-features2.wat create mode 100644 tests/cli/validate-unknown-features.wat create mode 100644 tests/cli/validate-unknown-features.wat.stderr diff --git a/Cargo.lock b/Cargo.lock index 2d39c9f598..7edaef94cf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1699,6 +1699,7 @@ dependencies = [ "addr2line", "anyhow", "arbitrary", + "bitflags", "clap", "clap_complete", "cpp_demangle", diff --git a/Cargo.toml b/Cargo.toml index 1ac79b1080..5a84f9266c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -115,6 +115,7 @@ termcolor = { workspace = true } # Dependencies of `validate` wasmparser = { workspace = true, optional = true, features = ['validate'] } rayon = { workspace = true, optional = true } +bitflags = { workspace = true, optional = true } # Dependencies of `print` wasmprinter = { workspace = true } @@ -201,7 +202,7 @@ default = [ ] # Each subcommand is gated behind a feature and lists the dependencies it needs -validate = ['dep:wasmparser', 'rayon', 'dep:addr2line', 'dep:gimli'] +validate = ['dep:wasmparser', 'rayon', 'dep:addr2line', 'dep:gimli', 'dep:bitflags'] print = [] parse = [] smith = ['wasm-smith', 'arbitrary', 'dep:serde', 'dep:serde_derive', 'dep:serde_json'] diff --git a/src/addr2line.rs b/src/addr2line.rs index f81986ee66..1be023c57a 100644 --- a/src/addr2line.rs +++ b/src/addr2line.rs @@ -107,7 +107,7 @@ impl<'a> Addr2lineModules<'a> { Some(start) => addr .checked_sub(start) .context("address is before the beginning of the text section")?, - None => bail!("no code section found in module"), + None => return Ok(None), } }; diff --git a/src/bin/wasm-tools/validate.rs b/src/bin/wasm-tools/validate.rs index fe402b1390..b94596b51d 100644 --- a/src/bin/wasm-tools/validate.rs +++ b/src/bin/wasm-tools/validate.rs @@ -1,5 +1,6 @@ use addr2line::LookupResult; use anyhow::{anyhow, Context, Result}; +use bitflags::Flags; use rayon::prelude::*; use std::fmt::Write; use std::mem; @@ -171,38 +172,9 @@ impl Opts { fn parse_features(arg: &str) -> Result { let mut ret = WasmFeatures::default(); - const FEATURES: &[(&str, WasmFeatures)] = &[ - ("reference-types", WasmFeatures::REFERENCE_TYPES), - ("function-references", WasmFeatures::FUNCTION_REFERENCES), - ("simd", WasmFeatures::SIMD), - ("threads", WasmFeatures::THREADS), - ( - "shared-everything-threads", - WasmFeatures::SHARED_EVERYTHING_THREADS, - ), - ("bulk-memory", WasmFeatures::BULK_MEMORY), - ("multi-value", WasmFeatures::MULTI_VALUE), - ("tail-call", WasmFeatures::TAIL_CALL), - ("component-model", WasmFeatures::COMPONENT_MODEL), - ( - "component-model-values", - WasmFeatures::COMPONENT_MODEL_VALUES, - ), - ("multi-memory", WasmFeatures::MULTI_MEMORY), - ("exception-handling", WasmFeatures::EXCEPTIONS), - ("memory64", WasmFeatures::MEMORY64), - ("extended-const", WasmFeatures::EXTENDED_CONST), - ("floats", WasmFeatures::FLOATS), - ( - "saturating-float-to-int", - WasmFeatures::SATURATING_FLOAT_TO_INT, - ), - ("sign-extension", WasmFeatures::SIGN_EXTENSION), - ("mutable-global", WasmFeatures::MUTABLE_GLOBAL), - ("relaxed-simd", WasmFeatures::RELAXED_SIMD), - ("gc", WasmFeatures::GC), - ("legacy-exceptions", WasmFeatures::LEGACY_EXCEPTIONS), - ]; + fn flag_name(flag: &bitflags::Flag) -> String { + flag.name().to_lowercase().replace('_', "-") + } for part in arg.split(',').map(|s| s.trim()).filter(|s| !s.is_empty()) { let (enable, part) = if let Some(part) = part.strip_prefix("-") { @@ -212,28 +184,27 @@ fn parse_features(arg: &str) -> Result { }; match part { "all" => { - for (name, feature) in FEATURES { - // don't count this under "all" for now. - if *name == "deterministic" { - continue; - } - ret.set(*feature, enable); + for flag in WasmFeatures::FLAGS.iter() { + ret.set(*flag.value(), enable); } } name => { - let (_, feature) = FEATURES.iter().find(|(n, _)| *n == name).ok_or_else(|| { - anyhow!( - "unknown feature `{}`\nValid features: {}", - name, - FEATURES - .iter() - .map(|(name, _)| *name) - .collect::>() - .join(", "), - ) - })?; - ret.set(*feature, enable); + let flag = WasmFeatures::FLAGS + .iter() + .find(|f| flag_name(f) == name) + .ok_or_else(|| { + anyhow!( + "unknown feature `{}`\nValid features: {}", + name, + WasmFeatures::FLAGS + .iter() + .map(flag_name) + .collect::>() + .join(", "), + ) + })?; + ret.set(*flag.value(), enable); } } } diff --git a/tests/cli/validate-features.wat b/tests/cli/validate-features.wat new file mode 100644 index 0000000000..3aad684f90 --- /dev/null +++ b/tests/cli/validate-features.wat @@ -0,0 +1,5 @@ +;; FAIL: validate --features=-all % + +(module + (import "x" "y" (global v128)) +) diff --git a/tests/cli/validate-features.wat.stderr b/tests/cli/validate-features.wat.stderr new file mode 100644 index 0000000000..45ead379a2 --- /dev/null +++ b/tests/cli/validate-features.wat.stderr @@ -0,0 +1 @@ +error: SIMD support is not enabled (at offset 0xb) diff --git a/tests/cli/validate-features2.wat b/tests/cli/validate-features2.wat new file mode 100644 index 0000000000..4bf1c1352e --- /dev/null +++ b/tests/cli/validate-features2.wat @@ -0,0 +1,6 @@ +;; RUN: validate --features=-all,simd % + +(module + (import "x" "y" (global v128)) +) + diff --git a/tests/cli/validate-unknown-features.wat b/tests/cli/validate-unknown-features.wat new file mode 100644 index 0000000000..daebd5c64b --- /dev/null +++ b/tests/cli/validate-unknown-features.wat @@ -0,0 +1,4 @@ +;; FAIL: validate --features=unknown % + +(module) + diff --git a/tests/cli/validate-unknown-features.wat.stderr b/tests/cli/validate-unknown-features.wat.stderr new file mode 100644 index 0000000000..3a72311f23 --- /dev/null +++ b/tests/cli/validate-unknown-features.wat.stderr @@ -0,0 +1,4 @@ +error: invalid value 'unknown' for '--features ': unknown feature `unknown` +Valid features: mutable-global, saturating-float-to-int, sign-extension, reference-types, multi-value, bulk-memory, simd, relaxed-simd, threads, shared-everything-threads, tail-call, floats, multi-memory, exceptions, memory64, extended-const, component-model, function-references, memory-control, gc, custom-page-sizes, component-model-values, component-model-nested-names, component-model-more-flags, legacy-exceptions + +For more information, try '--help'. From 8078b664bf1f710504e1c61243c8bc522ab5f40d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Fri, 12 Jul 2024 11:05:06 -0500 Subject: [PATCH 55/58] Update some documentation about longer names (#1667) Adding some follow-up documentation to #1650 --- crates/wasmparser/src/binary_reader.rs | 6 ++++++ crates/wasmparser/src/readers/core/names.rs | 3 +++ 2 files changed, 9 insertions(+) diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs index 7f77662dcc..c93187df5d 100644 --- a/crates/wasmparser/src/binary_reader.rs +++ b/crates/wasmparser/src/binary_reader.rs @@ -697,7 +697,9 @@ impl<'a> BinaryReader<'a> { } /// Reads a WebAssembly string from the module. + /// /// # Errors + /// /// If `BinaryReader` has less than up to four bytes remaining, the string's /// length exceeds the remaining bytes, the string's length exceeds /// `limits::MAX_WASM_STRING_SIZE`, or the string contains invalid utf-8. @@ -713,6 +715,10 @@ impl<'a> BinaryReader<'a> { } /// Reads a unlimited WebAssembly string from the module. + /// + /// Note that this is similar to [`BinaryReader::read_string`] except that + /// it will not limit the size of the returned string by + /// `limits::MAX_WASM_STRING_SIZE`. pub fn read_unlimited_string(&mut self) -> Result<&'a str> { let len = self.read_var_u32()? as usize; return self.internal_read_string(len); diff --git a/crates/wasmparser/src/readers/core/names.rs b/crates/wasmparser/src/readers/core/names.rs index ef192acb91..d53211ad8a 100644 --- a/crates/wasmparser/src/readers/core/names.rs +++ b/crates/wasmparser/src/readers/core/names.rs @@ -33,6 +33,9 @@ pub struct Naming<'a> { impl<'a> FromReader<'a> for Naming<'a> { fn from_reader(reader: &mut BinaryReader<'a>) -> Result { let index = reader.read_var_u32()?; + // This seems to match what browsers do where they don't limit the + // length of names in the `name` section while they do limit the names + // in the import and export section for example. let name = reader.read_unlimited_string()?; Ok(Naming { index, name }) } From 4c7a005dca5ba873f0a1214c7a9d31ba70306517 Mon Sep 17 00:00:00 2001 From: Mendy Berger <12537668+MendyBerger@users.noreply.github.com> Date: Fri, 12 Jul 2024 12:20:17 -0400 Subject: [PATCH 56/58] Added serde support to wit-encoder (#1647) * Added serde support to wit-encoder * Added serde feature notes --- Cargo.lock | 8 ++++++-- crates/wit-encoder/Cargo.toml | 15 +++++++++++++++ crates/wit-encoder/src/docs.rs | 2 ++ crates/wit-encoder/src/enum_.rs | 4 ++++ crates/wit-encoder/src/flags.rs | 4 ++++ crates/wit-encoder/src/function.rs | 6 ++++++ crates/wit-encoder/src/ident.rs | 2 ++ crates/wit-encoder/src/include.rs | 2 ++ crates/wit-encoder/src/interface.rs | 4 ++++ crates/wit-encoder/src/package.rs | 6 ++++++ crates/wit-encoder/src/record.rs | 4 ++++ crates/wit-encoder/src/render.rs | 2 ++ crates/wit-encoder/src/resource.rs | 6 ++++++ crates/wit-encoder/src/result.rs | 2 ++ crates/wit-encoder/src/tuple.rs | 2 ++ crates/wit-encoder/src/ty.rs | 8 ++++++++ crates/wit-encoder/src/use_.rs | 2 ++ crates/wit-encoder/src/variant.rs | 2 ++ crates/wit-encoder/src/world.rs | 6 ++++++ 19 files changed, 85 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 7edaef94cf..78ef44ca57 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -942,9 +942,9 @@ checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" -version = "0.4.21" +version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "90ed8c1e510134f979dbc4f070f87d4313098b704861a105fe34231c70a3901c" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "logos" @@ -1302,6 +1302,9 @@ name = "semver" version = "1.0.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" +dependencies = [ + "serde", +] [[package]] name = "serde" @@ -2219,6 +2222,7 @@ dependencies = [ "indoc", "pretty_assertions", "semver", + "serde", ] [[package]] diff --git a/crates/wit-encoder/Cargo.toml b/crates/wit-encoder/Cargo.toml index 00f80ba338..cc72b4c766 100644 --- a/crates/wit-encoder/Cargo.toml +++ b/crates/wit-encoder/Cargo.toml @@ -10,9 +10,24 @@ version.workspace = true [lints] workspace = true +[features] +default = ["serde"] + +# Enables JSON serialization/deserialization of the wit-encoder structures. + +# *Note*: The JSON that this generates is different from the JSON generated from wit-parser. +# If you're looking to create WIT from JSON, then this is the crate and feature for you. But if +# you're parsing WIT and reading the output through JSON, then wit-parser is probably the better +# option. + +# *Note*: The exact structure of the JSON is likely not going to be very stable over time, +# so slight tweaks and variants should be expected as this crate evolves. +serde = ["dep:serde", "semver/serde"] + [dependencies] semver = { workspace = true } pretty_assertions = { workspace = true } +serde = { workspace = true, optional = true, features = ["derive"] } [dev-dependencies] indoc = { workspace = true } diff --git a/crates/wit-encoder/src/docs.rs b/crates/wit-encoder/src/docs.rs index 137fd744e7..86fe20592c 100644 --- a/crates/wit-encoder/src/docs.rs +++ b/crates/wit-encoder/src/docs.rs @@ -4,6 +4,8 @@ use crate::{Render, RenderOpts}; /// Documentation #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Docs { contents: String, } diff --git a/crates/wit-encoder/src/enum_.rs b/crates/wit-encoder/src/enum_.rs index b84f372550..73f17eb3ab 100644 --- a/crates/wit-encoder/src/enum_.rs +++ b/crates/wit-encoder/src/enum_.rs @@ -2,6 +2,8 @@ use crate::{Docs, Ident}; /// A variant without a payload #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Enum { pub(crate) cases: Vec, } @@ -36,6 +38,8 @@ impl Enum { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct EnumCase { pub(crate) name: Ident, pub(crate) docs: Option, diff --git a/crates/wit-encoder/src/flags.rs b/crates/wit-encoder/src/flags.rs index 81e2fd0f4a..69735cbb3b 100644 --- a/crates/wit-encoder/src/flags.rs +++ b/crates/wit-encoder/src/flags.rs @@ -1,6 +1,8 @@ use crate::{ident::Ident, Docs}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Flags { pub(crate) flags: Vec, } @@ -22,6 +24,8 @@ impl Flags { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Flag { pub(crate) name: Ident, pub(crate) docs: Option, diff --git a/crates/wit-encoder/src/function.rs b/crates/wit-encoder/src/function.rs index 6c86aa26ab..d00a87523e 100644 --- a/crates/wit-encoder/src/function.rs +++ b/crates/wit-encoder/src/function.rs @@ -3,6 +3,8 @@ use std::fmt::{self, Display}; use crate::{ident::Ident, Docs, Type}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Params { items: Vec<(Ident, Type)>, } @@ -61,6 +63,8 @@ impl Params { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub enum Results { Named(Params), Anon(Type), @@ -141,6 +145,8 @@ impl Results { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct StandaloneFunc { pub(crate) name: Ident, pub(crate) params: Params, diff --git a/crates/wit-encoder/src/ident.rs b/crates/wit-encoder/src/ident.rs index c336173c68..d0a2962743 100644 --- a/crates/wit-encoder/src/ident.rs +++ b/crates/wit-encoder/src/ident.rs @@ -1,6 +1,8 @@ use std::{borrow::Cow, fmt}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Ident(Cow<'static, str>); impl Ident { diff --git a/crates/wit-encoder/src/include.rs b/crates/wit-encoder/src/include.rs index 9f276381a0..f9a5cd930c 100644 --- a/crates/wit-encoder/src/include.rs +++ b/crates/wit-encoder/src/include.rs @@ -4,6 +4,8 @@ use crate::{Ident, Render}; /// Enable the union of a world with another world #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Include { use_path: Ident, include_names_list: Vec<(String, String)>, diff --git a/crates/wit-encoder/src/interface.rs b/crates/wit-encoder/src/interface.rs index bf0827161c..a4b876110b 100644 --- a/crates/wit-encoder/src/interface.rs +++ b/crates/wit-encoder/src/interface.rs @@ -3,6 +3,8 @@ use std::fmt; use crate::{Docs, Ident, Render, RenderOpts, StandaloneFunc, TypeDef, Use}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Interface { /// Name of this interface. pub(crate) name: Ident, @@ -54,6 +56,8 @@ impl Interface { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub enum InterfaceItem { TypeDef(TypeDef), Use(Use), diff --git a/crates/wit-encoder/src/package.rs b/crates/wit-encoder/src/package.rs index 0289e052d9..74fa962367 100644 --- a/crates/wit-encoder/src/package.rs +++ b/crates/wit-encoder/src/package.rs @@ -10,6 +10,8 @@ use crate::{ident::Ident, Interface, Render, RenderOpts, World}; /// have a unique identifier that affects generated components and uniquely /// identifiers this particular package. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Package { /// A unique name corresponding to this package. name: PackageName, @@ -81,6 +83,8 @@ impl fmt::Display for Package { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub enum PackageItem { Interface(Interface), World(World), @@ -92,6 +96,8 @@ pub enum PackageItem { /// This is directly encoded as an "ID" in the binary component representation /// with an interfaced tacked on as well. #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct PackageName { /// A namespace such as `wasi` in `wasi:foo/bar` namespace: String, diff --git a/crates/wit-encoder/src/record.rs b/crates/wit-encoder/src/record.rs index 9f91a8cd19..35c7f677c7 100644 --- a/crates/wit-encoder/src/record.rs +++ b/crates/wit-encoder/src/record.rs @@ -1,6 +1,8 @@ use crate::{ident::Ident, Docs, Type}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Record { pub(crate) fields: Vec, } @@ -22,6 +24,8 @@ impl Record { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Field { pub(crate) name: Ident, pub(crate) ty: Type, diff --git a/crates/wit-encoder/src/render.rs b/crates/wit-encoder/src/render.rs index 62cda3c8cc..60a710f93c 100644 --- a/crates/wit-encoder/src/render.rs +++ b/crates/wit-encoder/src/render.rs @@ -1,6 +1,8 @@ use std::fmt; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct RenderOpts { /// width of each indent pub indent_width: usize, diff --git a/crates/wit-encoder/src/resource.rs b/crates/wit-encoder/src/resource.rs index 644cca97e6..85d51714c5 100644 --- a/crates/wit-encoder/src/resource.rs +++ b/crates/wit-encoder/src/resource.rs @@ -1,6 +1,8 @@ use crate::{ident::Ident, Docs, Params, Results}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Resource { pub(crate) funcs: Vec, } @@ -24,6 +26,8 @@ impl Resource { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct ResourceFunc { pub(crate) kind: ResourceFuncKind, pub(crate) params: Params, @@ -31,6 +35,8 @@ pub struct ResourceFunc { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub enum ResourceFuncKind { Method(Ident, Results), Static(Ident, Results), diff --git a/crates/wit-encoder/src/result.rs b/crates/wit-encoder/src/result.rs index de4dc9cbfa..ee8ff11909 100644 --- a/crates/wit-encoder/src/result.rs +++ b/crates/wit-encoder/src/result.rs @@ -3,6 +3,8 @@ use std::fmt::Display; use crate::Type; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Result_ { ok: Option, err: Option, diff --git a/crates/wit-encoder/src/tuple.rs b/crates/wit-encoder/src/tuple.rs index 0bc97bacf5..ee3c9a4ce8 100644 --- a/crates/wit-encoder/src/tuple.rs +++ b/crates/wit-encoder/src/tuple.rs @@ -3,6 +3,8 @@ use std::fmt::Display; use crate::Type; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Tuple { pub(crate) types: Vec, } diff --git a/crates/wit-encoder/src/ty.rs b/crates/wit-encoder/src/ty.rs index 3e45f6d6ea..95b7268250 100644 --- a/crates/wit-encoder/src/ty.rs +++ b/crates/wit-encoder/src/ty.rs @@ -6,6 +6,8 @@ use crate::{ }; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub enum Type { Bool, U8, @@ -111,6 +113,8 @@ impl Display for Type { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct VariantCase { name: Ident, ty: Option, @@ -176,6 +180,8 @@ where } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct TypeDef { name: Ident, kind: TypeDefKind, @@ -273,6 +279,8 @@ impl TypeDef { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub enum TypeDefKind { Record(Record), Resource(Resource), diff --git a/crates/wit-encoder/src/use_.rs b/crates/wit-encoder/src/use_.rs index 9322c15d6b..bb549adba6 100644 --- a/crates/wit-encoder/src/use_.rs +++ b/crates/wit-encoder/src/use_.rs @@ -4,6 +4,8 @@ use crate::{Ident, Render}; /// Enable the union of a world with another world #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Use { target: Ident, use_names_list: Vec<(String, Option)>, diff --git a/crates/wit-encoder/src/variant.rs b/crates/wit-encoder/src/variant.rs index 2322f9da31..43bdf676c0 100644 --- a/crates/wit-encoder/src/variant.rs +++ b/crates/wit-encoder/src/variant.rs @@ -1,6 +1,8 @@ use crate::VariantCase; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct Variant { pub(crate) cases: Vec, } diff --git a/crates/wit-encoder/src/world.rs b/crates/wit-encoder/src/world.rs index 5f93320924..d380f62be0 100644 --- a/crates/wit-encoder/src/world.rs +++ b/crates/wit-encoder/src/world.rs @@ -3,6 +3,8 @@ use std::fmt; use crate::{ident::Ident, Docs, Include, Interface, Render, RenderOpts, StandaloneFunc, Use}; #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct World { /// The WIT identifier name of this world. name: Ident, @@ -156,6 +158,8 @@ impl Render for World { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub enum WorldItem { /// An imported inline interface InlineInterfaceImport(Interface), @@ -207,6 +211,8 @@ impl WorldItem { } #[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "kebab-case"))] pub struct WorldNamedInterface { /// Name of this interface. pub(crate) name: Ident, From bf282bb1054a58b7989b112de522674999d4af50 Mon Sep 17 00:00:00 2001 From: Andrew Brown Date: Fri, 12 Jul 2024 09:58:50 -0700 Subject: [PATCH 57/58] threads: add remaining instructions (#1664) * Add the `wast` instructions This refactors the way memory orderings are attached to instructions by introducing `Ordered`. This allows any kind of existing instruction argument to be wrapped up in its ordered equivalent, which is essentially what many of the shared-everything-threads instructions do. * Add the instructions everywhere else * Add tests * Fix formatting * Deduplicate validation of `struct|array.atomic.rmw.*` instructions --- crates/wasm-encoder/src/core/code.rs | 376 ++++++++++ crates/wasmparser/src/binary_reader.rs | 79 +++ crates/wasmparser/src/lib.rs | 27 + crates/wasmparser/src/validator/operators.rs | 472 ++++++++++++- crates/wasmprinter/src/operator.rs | 99 +++ crates/wast/src/core/binary.rs | 7 +- crates/wast/src/core/expr.rs | 68 +- crates/wast/src/core/resolve/names.rs | 58 +- .../shared-everything-threads/array.wast | 652 +++++++++++++++++- .../shared-everything-threads/struct.wast | 369 +++++++++- .../shared-everything-threads/table.wast | 131 ++++ .../shared-everything-threads/array.wast.json | 371 ++++++++++ .../array.wast/10.print | 10 + .../array.wast/11.print | 10 + .../array.wast/12.print | 10 + .../array.wast/13.print | 10 + .../array.wast/14.print | 10 + .../array.wast/15.print | 10 + .../array.wast/16.print | 10 + .../array.wast/17.print | 10 + .../array.wast/18.print | 10 + .../array.wast/19.print | 10 + .../array.wast/20.print | 10 + .../array.wast/21.print | 10 + .../array.wast/22.print | 10 + .../array.wast/23.print | 11 + .../array.wast/24.print | 11 + .../array.wast/25.print | 11 + .../array.wast/26.print | 11 + .../array.wast/27.print | 11 + .../array.wast/28.print | 11 + .../array.wast/29.print | 11 + .../array.wast/30.print | 11 + .../array.wast/31.print | 11 + .../array.wast/32.print | 11 + .../array.wast/33.print | 11 + .../array.wast/34.print | 11 + .../array.wast/35.print | 11 + .../array.wast/36.print | 11 + .../array.wast/37.print | 11 + .../array.wast/38.print | 11 + .../array.wast/39.print | 11 + .../array.wast/40.print | 11 + .../array.wast/41.print | 11 + .../array.wast/42.print | 11 + .../array.wast/43.print | 11 + .../array.wast/44.print | 11 + .../array.wast/45.print | 11 + .../array.wast/46.print | 11 + .../array.wast/47.print | 11 + .../array.wast/48.print | 11 + .../array.wast/49.print | 11 + .../array.wast/50.print | 11 + .../array.wast/51.print | 11 + .../array.wast/52.print | 11 + .../array.wast/53.print | 11 + .../array.wast/54.print | 11 + .../array.wast/55.print | 11 + .../array.wast/56.print | 11 + .../array.wast/57.print | 11 + .../array.wast/58.print | 11 + .../array.wast/59.print | 12 + .../array.wast/60.print | 12 + .../array.wast/61.print | 12 + .../array.wast/62.print | 12 + .../array.wast/63.print | 12 + .../array.wast/64.print | 12 + .../array.wast/9.print | 10 + .../struct.wast.json | 96 +++ .../struct.wast/9.print | 343 +++++++++ .../shared-everything-threads/table.wast.json | 17 + .../table.wast/7.print | 104 +++ .../table.wast/8.print | 75 ++ 73 files changed, 3879 insertions(+), 73 deletions(-) create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/10.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/11.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/12.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/13.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/14.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/15.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/16.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/17.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/18.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/19.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/20.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/21.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/22.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/23.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/24.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/25.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/26.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/27.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/28.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/29.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/30.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/31.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/32.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/33.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/34.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/35.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/36.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/37.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/38.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/39.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/40.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/41.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/42.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/43.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/44.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/45.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/46.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/47.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/48.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/49.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/50.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/51.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/52.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/53.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/54.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/55.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/56.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/57.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/58.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/59.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/60.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/61.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/62.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/63.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/64.print create mode 100644 tests/snapshots/local/shared-everything-threads/array.wast/9.print create mode 100644 tests/snapshots/local/shared-everything-threads/struct.wast/9.print create mode 100644 tests/snapshots/local/shared-everything-threads/table.wast/7.print create mode 100644 tests/snapshots/local/shared-everything-threads/table.wast/8.print diff --git a/crates/wasm-encoder/src/core/code.rs b/crates/wasm-encoder/src/core/code.rs index 12a4eae25a..8bfb8fdcf6 100644 --- a/crates/wasm-encoder/src/core/code.rs +++ b/crates/wasm-encoder/src/core/code.rs @@ -1087,6 +1087,122 @@ pub enum Instruction<'a> { ordering: Ordering, global_index: u32, }, + TableAtomicGet { + ordering: Ordering, + table_index: u32, + }, + TableAtomicSet { + ordering: Ordering, + table_index: u32, + }, + TableAtomicRmwXchg { + ordering: Ordering, + table_index: u32, + }, + TableAtomicRmwCmpxchg { + ordering: Ordering, + table_index: u32, + }, + StructAtomicGet { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicGetS { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicGetU { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicSet { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicRmwAdd { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicRmwSub { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicRmwAnd { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicRmwOr { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicRmwXor { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicRmwXchg { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + StructAtomicRmwCmpxchg { + ordering: Ordering, + struct_type_index: u32, + field_index: u32, + }, + ArrayAtomicGet { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicGetS { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicGetU { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicSet { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicRmwAdd { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicRmwSub { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicRmwAnd { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicRmwOr { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicRmwXor { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicRmwXchg { + ordering: Ordering, + array_type_index: u32, + }, + ArrayAtomicRmwCmpxchg { + ordering: Ordering, + array_type_index: u32, + }, + RefI31Shared, } impl Encode for Instruction<'_> { @@ -3319,6 +3435,266 @@ impl Encode for Instruction<'_> { ordering.encode(sink); global_index.encode(sink); } + Instruction::TableAtomicGet { + ordering, + table_index, + } => { + sink.push(0xFE); + sink.push(0x58); + ordering.encode(sink); + table_index.encode(sink); + } + Instruction::TableAtomicSet { + ordering, + table_index, + } => { + sink.push(0xFE); + sink.push(0x59); + ordering.encode(sink); + table_index.encode(sink); + } + Instruction::TableAtomicRmwXchg { + ordering, + table_index, + } => { + sink.push(0xFE); + sink.push(0x5A); + ordering.encode(sink); + table_index.encode(sink); + } + Instruction::TableAtomicRmwCmpxchg { + ordering, + table_index, + } => { + sink.push(0xFE); + sink.push(0x5B); + ordering.encode(sink); + table_index.encode(sink); + } + Instruction::StructAtomicGet { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x5C); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicGetS { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x5D); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicGetU { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x5E); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicSet { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x5F); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicRmwAdd { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x60); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicRmwSub { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x61); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicRmwAnd { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x62); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicRmwOr { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x63); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicRmwXor { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x64); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicRmwXchg { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x65); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::StructAtomicRmwCmpxchg { + ordering, + struct_type_index, + field_index, + } => { + sink.push(0xFE); + sink.push(0x66); + ordering.encode(sink); + struct_type_index.encode(sink); + field_index.encode(sink); + } + Instruction::ArrayAtomicGet { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x67); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicGetS { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x68); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicGetU { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x69); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicSet { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x6A); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicRmwAdd { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x6B); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicRmwSub { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x6C); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicRmwAnd { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x6D); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicRmwOr { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x6E); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicRmwXor { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x6F); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicRmwXchg { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x70); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::ArrayAtomicRmwCmpxchg { + ordering, + array_type_index, + } => { + sink.push(0xFE); + sink.push(0x71); + ordering.encode(sink); + array_type_index.encode(sink); + } + Instruction::RefI31Shared => { + sink.push(0xFE); + sink.push(0x1F); + } } } } diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs index c93187df5d..3fa9a772c8 100644 --- a/crates/wasmparser/src/binary_reader.rs +++ b/crates/wasmparser/src/binary_reader.rs @@ -1727,6 +1727,85 @@ impl<'a> BinaryReader<'a> { 0x57 => { visitor.visit_global_atomic_rmw_cmpxchg(self.read_ordering()?, self.read_var_u32()?) } + 0x58 => visitor.visit_table_atomic_get(self.read_ordering()?, self.read_var_u32()?), + 0x59 => visitor.visit_table_atomic_set(self.read_ordering()?, self.read_var_u32()?), + 0x5a => { + visitor.visit_table_atomic_rmw_xchg(self.read_ordering()?, self.read_var_u32()?) + } + 0x5b => { + visitor.visit_table_atomic_rmw_cmpxchg(self.read_ordering()?, self.read_var_u32()?) + } + 0x5c => visitor.visit_struct_atomic_get( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x5d => visitor.visit_struct_atomic_get_s( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x5e => visitor.visit_struct_atomic_get_u( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x5f => visitor.visit_struct_atomic_set( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x60 => visitor.visit_struct_atomic_rmw_add( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x61 => visitor.visit_struct_atomic_rmw_sub( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x62 => visitor.visit_struct_atomic_rmw_and( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x63 => visitor.visit_struct_atomic_rmw_or( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x64 => visitor.visit_struct_atomic_rmw_xor( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x65 => visitor.visit_struct_atomic_rmw_xchg( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x66 => visitor.visit_struct_atomic_rmw_cmpxchg( + self.read_ordering()?, + self.read_var_u32()?, + self.read_var_u32()?, + ), + 0x67 => visitor.visit_array_atomic_get(self.read_ordering()?, self.read_var_u32()?), + 0x68 => visitor.visit_array_atomic_get_s(self.read_ordering()?, self.read_var_u32()?), + 0x69 => visitor.visit_array_atomic_get_u(self.read_ordering()?, self.read_var_u32()?), + 0x6a => visitor.visit_array_atomic_set(self.read_ordering()?, self.read_var_u32()?), + 0x6b => visitor.visit_array_atomic_rmw_add(self.read_ordering()?, self.read_var_u32()?), + 0x6c => visitor.visit_array_atomic_rmw_sub(self.read_ordering()?, self.read_var_u32()?), + 0x6d => visitor.visit_array_atomic_rmw_and(self.read_ordering()?, self.read_var_u32()?), + 0x6e => visitor.visit_array_atomic_rmw_or(self.read_ordering()?, self.read_var_u32()?), + 0x6f => visitor.visit_array_atomic_rmw_xor(self.read_ordering()?, self.read_var_u32()?), + 0x70 => { + visitor.visit_array_atomic_rmw_xchg(self.read_ordering()?, self.read_var_u32()?) + } + 0x71 => { + visitor.visit_array_atomic_rmw_cmpxchg(self.read_ordering()?, self.read_var_u32()?) + } + 0x72 => visitor.visit_ref_i31_shared(), _ => bail!(pos, "unknown 0xfe subopcode: 0x{code:x}"), }) diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs index 18183a4875..0b1c02db94 100644 --- a/crates/wasmparser/src/lib.rs +++ b/crates/wasmparser/src/lib.rs @@ -505,6 +505,33 @@ macro_rules! for_each_operator { @shared_everything_threads GlobalAtomicRmwXor { ordering: $crate::Ordering, global_index: u32 } => visit_global_atomic_rmw_xor @shared_everything_threads GlobalAtomicRmwXchg { ordering: $crate::Ordering, global_index: u32 } => visit_global_atomic_rmw_xchg @shared_everything_threads GlobalAtomicRmwCmpxchg { ordering: $crate::Ordering, global_index: u32 } => visit_global_atomic_rmw_cmpxchg + @shared_everything_threads TableAtomicGet { ordering: $crate::Ordering, table_index: u32 } => visit_table_atomic_get + @shared_everything_threads TableAtomicSet { ordering: $crate::Ordering, table_index: u32 } => visit_table_atomic_set + @shared_everything_threads TableAtomicRmwXchg { ordering: $crate::Ordering, table_index: u32 } => visit_table_atomic_rmw_xchg + @shared_everything_threads TableAtomicRmwCmpxchg { ordering: $crate::Ordering, table_index: u32 } => visit_table_atomic_rmw_cmpxchg + @shared_everything_threads StructAtomicGet { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_get + @shared_everything_threads StructAtomicGetS { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_get_s + @shared_everything_threads StructAtomicGetU { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_get_u + @shared_everything_threads StructAtomicSet { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_set + @shared_everything_threads StructAtomicRmwAdd { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_rmw_add + @shared_everything_threads StructAtomicRmwSub { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_rmw_sub + @shared_everything_threads StructAtomicRmwAnd { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_rmw_and + @shared_everything_threads StructAtomicRmwOr { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_rmw_or + @shared_everything_threads StructAtomicRmwXor { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_rmw_xor + @shared_everything_threads StructAtomicRmwXchg { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_rmw_xchg + @shared_everything_threads StructAtomicRmwCmpxchg { ordering: $crate::Ordering, struct_type_index: u32, field_index: u32 } => visit_struct_atomic_rmw_cmpxchg + @shared_everything_threads ArrayAtomicGet { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_get + @shared_everything_threads ArrayAtomicGetS { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_get_s + @shared_everything_threads ArrayAtomicGetU { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_get_u + @shared_everything_threads ArrayAtomicSet { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_set + @shared_everything_threads ArrayAtomicRmwAdd { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_rmw_add + @shared_everything_threads ArrayAtomicRmwSub { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_rmw_sub + @shared_everything_threads ArrayAtomicRmwAnd { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_rmw_and + @shared_everything_threads ArrayAtomicRmwOr { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_rmw_or + @shared_everything_threads ArrayAtomicRmwXor { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_rmw_xor + @shared_everything_threads ArrayAtomicRmwXchg { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_rmw_xchg + @shared_everything_threads ArrayAtomicRmwCmpxchg { ordering: $crate::Ordering, array_type_index: u32 } => visit_array_atomic_rmw_cmpxchg + @shared_everything_threads RefI31Shared => visit_ref_i31_shared // 0xFD operators // 128-bit SIMD diff --git a/crates/wasmparser/src/validator/operators.rs b/crates/wasmparser/src/validator/operators.rs index 824fe093a1..65c44b9216 100644 --- a/crates/wasmparser/src/validator/operators.rs +++ b/crates/wasmparser/src/validator/operators.rs @@ -28,7 +28,7 @@ use crate::{ Result, StorageType, StructType, SubType, TableType, TryTable, UnpackedIndex, ValType, VisitOperator, WasmFeatures, WasmModuleResources, V128, }; -use crate::{prelude::*, CompositeInnerType}; +use crate::{prelude::*, CompositeInnerType, Ordering}; use core::ops::{Deref, DerefMut}; pub(crate) struct OperatorValidator { @@ -758,17 +758,6 @@ where Ok(index_ty) } - /// Validates that the `table` is valid and returns the type it points to. - fn check_table_index(&self, table: u32) -> Result { - match self.resources.table_at(table) { - Some(ty) => Ok(ty), - None => bail!( - self.offset, - "unknown table {table}: table index out of bounds" - ), - } - } - fn check_floats_enabled(&self) -> Result<()> { if !self.features.floats() { bail!(self.offset, "floating-point instruction disallowed"); @@ -892,7 +881,7 @@ where type_index: u32, table_index: u32, ) -> Result<&'resources FuncType> { - let tab = self.check_table_index(table_index)?; + let tab = self.table_type_at(table_index)?; if !self .resources .is_subtype(ValType::Ref(tab.element_type), ValType::FUNCREF) @@ -1173,6 +1162,52 @@ where Ok(ty) } + /// Common helper for checking the types of structs accessed with atomic RMW + /// instructions, which only allow `i32` and `i64` types. + fn check_struct_atomic_rmw( + &mut self, + op: &'static str, + struct_type_index: u32, + field_index: u32, + ) -> Result<()> { + let ty = self + .struct_field_at(struct_type_index, field_index)? + .element_type; + let field_ty = match ty { + StorageType::Val(ValType::I32) => ValType::I32, + StorageType::Val(ValType::I64) => ValType::I64, + _ => bail!( + self.offset, + "invalid type: `struct.atomic.rmw.{}` only allows `i32` and `i64`", + op + ), + }; + self.pop_operand(Some(field_ty))?; + self.pop_concrete_ref(true, struct_type_index)?; + self.push_operand(field_ty)?; + Ok(()) + } + + /// Common helper for checking the types of arrays accessed with atomic RMW + /// instructions, which only allow `i32` and `i64`. + fn check_array_atomic_rmw(&mut self, op: &'static str, type_index: u32) -> Result<()> { + let ty = self.array_type_at(type_index)?.0.element_type; + let elem_ty = match ty { + StorageType::Val(ValType::I32) => ValType::I32, + StorageType::Val(ValType::I64) => ValType::I64, + _ => bail!( + self.offset, + "invalid type: `array.atomic.rmw.{}` only allows `i32` and `i64`", + op + ), + }; + self.pop_operand(Some(elem_ty))?; + self.pop_operand(Some(ValType::I32))?; + self.pop_concrete_ref(true, type_index)?; + self.push_operand(elem_ty)?; + Ok(()) + } + fn element_type_at(&self, elem_index: u32) -> Result { match self.resources.element_type_at(elem_index) { Some(ty) => Ok(ty), @@ -1253,6 +1288,17 @@ where } } + /// Validates that the `table` is valid and returns the type it points to. + fn table_type_at(&self, table: u32) -> Result { + match self.resources.table_at(table) { + Some(ty) => Ok(ty), + None => bail!( + self.offset, + "unknown table {table}: table index out of bounds" + ), + } + } + fn params(&self, ty: BlockType) -> Result + 'resources> { Ok(match ty { BlockType::Empty | BlockType::Type(_) => Either::B(None.into_iter()), @@ -1691,11 +1737,7 @@ where self.push_operand(ty)?; Ok(()) } - fn visit_global_atomic_get( - &mut self, - _ordering: crate::Ordering, - global_index: u32, - ) -> Self::Output { + fn visit_global_atomic_get(&mut self, _ordering: Ordering, global_index: u32) -> Self::Output { self.visit_global_get(global_index)?; // No validation of `ordering` is needed because `global.atomic.get` can // be used on both shared and unshared globals. But we do need to limit @@ -1718,11 +1760,7 @@ where self.pop_operand(Some(ty.content_type))?; Ok(()) } - fn visit_global_atomic_set( - &mut self, - _ordering: crate::Ordering, - global_index: u32, - ) -> Self::Output { + fn visit_global_atomic_set(&mut self, _ordering: Ordering, global_index: u32) -> Self::Output { self.visit_global_set(global_index)?; // No validation of `ordering` is needed because `global.atomic.get` can // be used on both shared and unshared globals. @@ -3616,7 +3654,7 @@ where Ok(()) } fn visit_table_init(&mut self, segment: u32, table: u32) -> Self::Output { - let table = self.check_table_index(table)?; + let table = self.table_type_at(table)?; let segment_ty = self.element_type_at(segment)?; if !self .resources @@ -3640,8 +3678,8 @@ where Ok(()) } fn visit_table_copy(&mut self, dst_table: u32, src_table: u32) -> Self::Output { - let src = self.check_table_index(src_table)?; - let dst = self.check_table_index(dst_table)?; + let src = self.table_type_at(src_table)?; + let dst = self.table_type_at(dst_table)?; if !self.resources.is_subtype( ValType::Ref(src.element_type), ValType::Ref(dst.element_type), @@ -3663,21 +3701,51 @@ where Ok(()) } fn visit_table_get(&mut self, table: u32) -> Self::Output { - let table = self.check_table_index(table)?; + let table = self.table_type_at(table)?; debug_assert_type_indices_are_ids(table.element_type.into()); self.pop_operand(Some(table.index_type()))?; self.push_operand(table.element_type)?; Ok(()) } + fn visit_table_atomic_get(&mut self, _ordering: Ordering, table: u32) -> Self::Output { + self.visit_table_get(table)?; + // No validation of `ordering` is needed because `table.atomic.get` can + // be used on both shared and unshared tables. But we do need to limit + // which types can be used with this instruction. + let ty = self.table_type_at(table)?.element_type; + let supertype = RefType::ANYREF.shared().unwrap(); + if !self.resources.is_subtype(ty.into(), supertype.into()) { + bail!( + self.offset, + "invalid type: `table.atomic.get` only allows subtypes of `anyref`" + ); + } + Ok(()) + } fn visit_table_set(&mut self, table: u32) -> Self::Output { - let table = self.check_table_index(table)?; + let table = self.table_type_at(table)?; debug_assert_type_indices_are_ids(table.element_type.into()); self.pop_operand(Some(table.element_type.into()))?; self.pop_operand(Some(table.index_type()))?; Ok(()) } + fn visit_table_atomic_set(&mut self, _ordering: Ordering, table: u32) -> Self::Output { + self.visit_table_set(table)?; + // No validation of `ordering` is needed because `table.atomic.set` can + // be used on both shared and unshared tables. But we do need to limit + // which types can be used with this instruction. + let ty = self.table_type_at(table)?.element_type; + let supertype = RefType::ANYREF.shared().unwrap(); + if !self.resources.is_subtype(ty.into(), supertype.into()) { + bail!( + self.offset, + "invalid type: `table.atomic.set` only allows subtypes of `anyref`" + ); + } + Ok(()) + } fn visit_table_grow(&mut self, table: u32) -> Self::Output { - let table = self.check_table_index(table)?; + let table = self.table_type_at(table)?; debug_assert_type_indices_are_ids(table.element_type.into()); self.pop_operand(Some(table.index_type()))?; self.pop_operand(Some(table.element_type.into()))?; @@ -3685,18 +3753,51 @@ where Ok(()) } fn visit_table_size(&mut self, table: u32) -> Self::Output { - let table = self.check_table_index(table)?; + let table = self.table_type_at(table)?; self.push_operand(table.index_type())?; Ok(()) } fn visit_table_fill(&mut self, table: u32) -> Self::Output { - let table = self.check_table_index(table)?; + let table = self.table_type_at(table)?; debug_assert_type_indices_are_ids(table.element_type.into()); self.pop_operand(Some(table.index_type()))?; self.pop_operand(Some(table.element_type.into()))?; self.pop_operand(Some(table.index_type()))?; Ok(()) } + fn visit_table_atomic_rmw_xchg(&mut self, _ordering: Ordering, table: u32) -> Self::Output { + let table = self.table_type_at(table)?; + let elem_ty = table.element_type.into(); + debug_assert_type_indices_are_ids(elem_ty); + let supertype = RefType::ANYREF.shared().unwrap(); + if !self.resources.is_subtype(elem_ty, supertype.into()) { + bail!( + self.offset, + "invalid type: `table.atomic.rmw.xchg` only allows subtypes of `anyref`" + ); + } + self.pop_operand(Some(elem_ty))?; + self.pop_operand(Some(table.index_type()))?; + self.push_operand(elem_ty)?; + Ok(()) + } + fn visit_table_atomic_rmw_cmpxchg(&mut self, _ordering: Ordering, table: u32) -> Self::Output { + let table = self.table_type_at(table)?; + let elem_ty = table.element_type.into(); + debug_assert_type_indices_are_ids(elem_ty); + let supertype = RefType::EQREF.shared().unwrap(); + if !self.resources.is_subtype(elem_ty, supertype.into()) { + bail!( + self.offset, + "invalid type: `table.atomic.rmw.cmpxchg` only allows subtypes of `eqref`" + ); + } + self.pop_operand(Some(elem_ty))?; + self.pop_operand(Some(elem_ty))?; + self.pop_operand(Some(table.index_type()))?; + self.push_operand(elem_ty)?; + Ok(()) + } fn visit_struct_new(&mut self, struct_type_index: u32) -> Self::Output { let struct_ty = self.struct_type_at(struct_type_index)?; for ty in struct_ty.fields.iter().rev() { @@ -3730,6 +3831,32 @@ where self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty.element_type.unpack()) } + fn visit_struct_atomic_get( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.visit_struct_get(struct_type_index, field_index)?; + // The `atomic` version has some additional type restrictions. + let ty = self + .struct_field_at(struct_type_index, field_index)? + .element_type; + let is_valid_type = match ty { + StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true, + StorageType::Val(v) => self + .resources + .is_subtype(v, RefType::ANYREF.shared().unwrap().into()), + _ => false, + }; + if !is_valid_type { + bail!( + self.offset, + "invalid type: `struct.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`" + ); + } + Ok(()) + } fn visit_struct_get_s(&mut self, struct_type_index: u32, field_index: u32) -> Self::Output { let field_ty = self.struct_field_at(struct_type_index, field_index)?; if !field_ty.element_type.is_packed() { @@ -3741,6 +3868,21 @@ where self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty.element_type.unpack()) } + fn visit_struct_atomic_get_s( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.visit_struct_get_s(struct_type_index, field_index)?; + // This instruction has the same type restrictions as the non-`atomic` version. + debug_assert!(matches!( + self.struct_field_at(struct_type_index, field_index)? + .element_type, + StorageType::I8 | StorageType::I16 + )); + Ok(()) + } fn visit_struct_get_u(&mut self, struct_type_index: u32, field_index: u32) -> Self::Output { let field_ty = self.struct_field_at(struct_type_index, field_index)?; if !field_ty.element_type.is_packed() { @@ -3752,6 +3894,21 @@ where self.pop_concrete_ref(true, struct_type_index)?; self.push_operand(field_ty.element_type.unpack()) } + fn visit_struct_atomic_get_u( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.visit_struct_get_s(struct_type_index, field_index)?; + // This instruction has the same type restrictions as the non-`atomic` version. + debug_assert!(matches!( + self.struct_field_at(struct_type_index, field_index)? + .element_type, + StorageType::I8 | StorageType::I16 + )); + Ok(()) + } fn visit_struct_set(&mut self, struct_type_index: u32, field_index: u32) -> Self::Output { let field_ty = self.struct_field_at(struct_type_index, field_index)?; if !field_ty.mutable { @@ -3761,6 +3918,129 @@ where self.pop_concrete_ref(true, struct_type_index)?; Ok(()) } + fn visit_struct_atomic_set( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.visit_struct_set(struct_type_index, field_index)?; + // The `atomic` version has some additional type restrictions. + let ty = self + .struct_field_at(struct_type_index, field_index)? + .element_type; + let is_valid_type = match ty { + StorageType::I8 | StorageType::I16 => true, + StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true, + StorageType::Val(v) => self + .resources + .is_subtype(v, RefType::ANYREF.shared().unwrap().into()), + }; + if !is_valid_type { + bail!( + self.offset, + "invalid type: `struct.atomic.set` only allows `i8`, `i16`, `i32`, `i64` and subtypes of `anyref`" + ); + } + Ok(()) + } + fn visit_struct_atomic_rmw_add( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.check_struct_atomic_rmw("add", struct_type_index, field_index) + } + fn visit_struct_atomic_rmw_sub( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.check_struct_atomic_rmw("sub", struct_type_index, field_index) + } + fn visit_struct_atomic_rmw_and( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.check_struct_atomic_rmw("and", struct_type_index, field_index) + } + fn visit_struct_atomic_rmw_or( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.check_struct_atomic_rmw("or", struct_type_index, field_index) + } + fn visit_struct_atomic_rmw_xor( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + self.check_struct_atomic_rmw("xor", struct_type_index, field_index) + } + fn visit_struct_atomic_rmw_xchg( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + let field_ty = self + .struct_field_at(struct_type_index, field_index)? + .element_type; + let is_valid_type = match field_ty { + StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true, + StorageType::Val(v) => self + .resources + .is_subtype(v, RefType::ANYREF.shared().unwrap().into()), + _ => false, + }; + if !is_valid_type { + bail!( + self.offset, + "invalid type: `struct.atomic.rmw.xchg` only allows `i32`, `i64` and subtypes of `anyref`" + ); + } + let field_ty = field_ty.unpack(); + self.pop_operand(Some(field_ty))?; + self.pop_concrete_ref(true, struct_type_index)?; + self.push_operand(field_ty)?; + Ok(()) + } + fn visit_struct_atomic_rmw_cmpxchg( + &mut self, + _ordering: Ordering, + struct_type_index: u32, + field_index: u32, + ) -> Self::Output { + let field_ty = self + .struct_field_at(struct_type_index, field_index)? + .element_type; + let is_valid_type = match field_ty { + StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true, + StorageType::Val(v) => self + .resources + .is_subtype(v, RefType::EQREF.shared().unwrap().into()), + _ => false, + }; + if !is_valid_type { + bail!( + self.offset, + "invalid type: `struct.atomic.rmw.cmpxchg` only allows `i32`, `i64` and subtypes of `eqref`" + ); + } + let field_ty = field_ty.unpack(); + self.pop_operand(Some(field_ty))?; + self.pop_operand(Some(field_ty))?; + self.pop_concrete_ref(true, struct_type_index)?; + self.push_operand(field_ty)?; + Ok(()) + } fn visit_array_new(&mut self, type_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; self.pop_operand(Some(ValType::I32))?; @@ -3843,6 +4123,25 @@ where self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty.unpack()) } + fn visit_array_atomic_get(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.visit_array_get(type_index)?; + // The `atomic` version has some additional type restrictions. + let elem_ty = self.array_type_at(type_index)?.0.element_type; + let is_valid_type = match elem_ty { + StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true, + StorageType::Val(v) => self + .resources + .is_subtype(v, RefType::ANYREF.shared().unwrap().into()), + _ => false, + }; + if !is_valid_type { + bail!( + self.offset, + "invalid type: `array.atomic.get` only allows `i32`, `i64` and subtypes of `anyref`" + ); + } + Ok(()) + } fn visit_array_get_s(&mut self, type_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; let elem_ty = array_ty.0.element_type; @@ -3856,6 +4155,15 @@ where self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty.unpack()) } + fn visit_array_atomic_get_s(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.visit_array_get_s(type_index)?; + // This instruction has the same type restrictions as the non-`atomic` version. + debug_assert!(matches!( + self.array_type_at(type_index)?.0.element_type, + StorageType::I8 | StorageType::I16 + )); + Ok(()) + } fn visit_array_get_u(&mut self, type_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; let elem_ty = array_ty.0.element_type; @@ -3869,6 +4177,15 @@ where self.pop_concrete_ref(true, type_index)?; self.push_operand(elem_ty.unpack()) } + fn visit_array_atomic_get_u(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.visit_array_get_u(type_index)?; + // This instruction has the same type restrictions as the non-`atomic` version. + debug_assert!(matches!( + self.array_type_at(type_index)?.0.element_type, + StorageType::I8 | StorageType::I16 + )); + Ok(()) + } fn visit_array_set(&mut self, type_index: u32) -> Self::Output { let array_ty = self.array_type_at(type_index)?; if !array_ty.0.mutable { @@ -3879,6 +4196,25 @@ where self.pop_concrete_ref(true, type_index)?; Ok(()) } + fn visit_array_atomic_set(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.visit_array_set(type_index)?; + // The `atomic` version has some additional type restrictions. + let elem_ty = self.array_type_at(type_index)?.0.element_type; + let is_valid_type = match elem_ty { + StorageType::I8 | StorageType::I16 => true, + StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true, + StorageType::Val(v) => self + .resources + .is_subtype(v, RefType::ANYREF.shared().unwrap().into()), + }; + if !is_valid_type { + bail!( + self.offset, + "invalid type: `array.atomic.set` only allows `i8`, `i16`, `i32`, `i64` and subtypes of `anyref`" + ); + } + Ok(()) + } fn visit_array_len(&mut self) -> Self::Output { self.pop_operand(Some(RefType::ARRAY.nullable().into()))?; self.push_operand(ValType::I32) @@ -3993,6 +4329,74 @@ where self.pop_concrete_ref(true, type_index)?; Ok(()) } + fn visit_array_atomic_rmw_add(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.check_array_atomic_rmw("add", type_index) + } + fn visit_array_atomic_rmw_sub(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.check_array_atomic_rmw("sub", type_index) + } + fn visit_array_atomic_rmw_and(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.check_array_atomic_rmw("and", type_index) + } + fn visit_array_atomic_rmw_or(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.check_array_atomic_rmw("or", type_index) + } + fn visit_array_atomic_rmw_xor(&mut self, _ordering: Ordering, type_index: u32) -> Self::Output { + self.check_array_atomic_rmw("xor", type_index) + } + fn visit_array_atomic_rmw_xchg( + &mut self, + _ordering: Ordering, + type_index: u32, + ) -> Self::Output { + let elem_ty = self.array_type_at(type_index)?.0.element_type; + let is_valid_type = match elem_ty { + StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true, + StorageType::Val(v) => self + .resources + .is_subtype(v, RefType::ANYREF.shared().unwrap().into()), + _ => false, + }; + if !is_valid_type { + bail!( + self.offset, + "invalid type: `array.atomic.rmw.xchg` only allows `i32`, `i64` and subtypes of `anyref`" + ); + } + let elem_ty = elem_ty.unpack(); + self.pop_operand(Some(elem_ty))?; + self.pop_operand(Some(ValType::I32))?; + self.pop_concrete_ref(true, type_index)?; + self.push_operand(elem_ty)?; + Ok(()) + } + fn visit_array_atomic_rmw_cmpxchg( + &mut self, + _ordering: Ordering, + type_index: u32, + ) -> Self::Output { + let elem_ty = self.array_type_at(type_index)?.0.element_type; + let is_valid_type = match elem_ty { + StorageType::Val(ValType::I32) | StorageType::Val(ValType::I64) => true, + StorageType::Val(v) => self + .resources + .is_subtype(v, RefType::EQREF.shared().unwrap().into()), + _ => false, + }; + if !is_valid_type { + bail!( + self.offset, + "invalid type: `array.atomic.rmw.cmpxchg` only allows `i32`, `i64` and subtypes of `eqref`" + ); + } + let elem_ty = elem_ty.unpack(); + self.pop_operand(Some(elem_ty))?; + self.pop_operand(Some(elem_ty))?; + self.pop_operand(Some(ValType::I32))?; + self.pop_concrete_ref(true, type_index)?; + self.push_operand(elem_ty)?; + Ok(()) + } fn visit_any_convert_extern(&mut self) -> Self::Output { let extern_ref = self.pop_operand(Some(RefType::EXTERNREF.into()))?; let is_nullable = extern_ref @@ -4064,7 +4468,7 @@ where ), None => bail!( self.offset, - "type mismtach: br_on_cast to label with empty types, must have a reference type" + "type mismatch: br_on_cast to label with empty types, must have a reference type" ), }; @@ -4120,6 +4524,10 @@ where self.pop_operand(Some(ValType::I32))?; self.push_operand(ValType::Ref(RefType::I31)) } + fn visit_ref_i31_shared(&mut self) -> Self::Output { + self.pop_operand(Some(ValType::I32))?; + self.push_operand(ValType::Ref(RefType::I31)) // TODO: handle shared--is this correct? + } fn visit_i31_get_s(&mut self) -> Self::Output { self.pop_operand(Some(ValType::Ref(RefType::I31REF)))?; self.push_operand(ValType::I32) diff --git a/crates/wasmprinter/src/operator.rs b/crates/wasmprinter/src/operator.rs index fa7e2ca456..558bdc863f 100644 --- a/crates/wasmprinter/src/operator.rs +++ b/crates/wasmprinter/src/operator.rs @@ -595,6 +595,78 @@ macro_rules! define_visit { $self.push_str(" ")?; $self.printer.print_field_idx($self.state, $ty, $field)?; ); + (payload $self:ident StructAtomicGet $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicGetS $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicGetU $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicSet $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicSet $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicRmwAdd $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicRmwSub $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicRmwAnd $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicRmwOr $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicRmwXor $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicRmwXchg $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); + (payload $self:ident StructAtomicRmwCmpxchg $order:ident $ty:ident $field:ident) => ( + $self.ordering($order)?; + $self.struct_type_index($ty)?; + $self.push_str(" ")?; + $self.printer.print_field_idx($self.state, $ty, $field)?; + ); (payload $self:ident $op:ident $($arg:ident)*) => ( $($self.$arg($arg)?;)* ); @@ -1180,6 +1252,33 @@ macro_rules! define_visit { (name GlobalAtomicRmwXor) => ("global.atomic.rmw.xor"); (name GlobalAtomicRmwXchg) => ("global.atomic.rmw.xchg"); (name GlobalAtomicRmwCmpxchg) => ("global.atomic.rmw.cmpxchg"); + (name TableAtomicGet) => ("table.atomic.get"); + (name TableAtomicSet) => ("table.atomic.set"); + (name TableAtomicRmwXchg) => ("table.atomic.rmw.xchg"); + (name TableAtomicRmwCmpxchg) => ("table.atomic.rmw.cmpxchg"); + (name StructAtomicGet) => ("struct.atomic.get"); + (name StructAtomicGetS) => ("struct.atomic.get_s"); + (name StructAtomicGetU) => ("struct.atomic.get_u"); + (name StructAtomicSet) => ("struct.atomic.set"); + (name StructAtomicRmwAdd) => ("struct.atomic.rmw.add"); + (name StructAtomicRmwSub) => ("struct.atomic.rmw.sub"); + (name StructAtomicRmwAnd) => ("struct.atomic.rmw.and"); + (name StructAtomicRmwOr) => ("struct.atomic.rmw.or"); + (name StructAtomicRmwXor) => ("struct.atomic.rmw.xor"); + (name StructAtomicRmwXchg) => ("struct.atomic.rmw.xchg"); + (name StructAtomicRmwCmpxchg) => ("struct.atomic.rmw.cmpxchg"); + (name ArrayAtomicGet) => ("array.atomic.get"); + (name ArrayAtomicGetS) => ("array.atomic.get_s"); + (name ArrayAtomicGetU) => ("array.atomic.get_u"); + (name ArrayAtomicSet) => ("array.atomic.set"); + (name ArrayAtomicRmwAdd) => ("array.atomic.rmw.add"); + (name ArrayAtomicRmwSub) => ("array.atomic.rmw.sub"); + (name ArrayAtomicRmwAnd) => ("array.atomic.rmw.and"); + (name ArrayAtomicRmwOr) => ("array.atomic.rmw.or"); + (name ArrayAtomicRmwXor) => ("array.atomic.rmw.xor"); + (name ArrayAtomicRmwXchg) => ("array.atomic.rmw.xchg"); + (name ArrayAtomicRmwCmpxchg) => ("array.atomic.rmw.cmpxchg"); + (name RefI31Shared) => ("ref.i31_shared") } impl<'a> VisitOperator<'a> for PrintOperator<'_, '_, '_, '_> { diff --git a/crates/wast/src/core/binary.rs b/crates/wast/src/core/binary.rs index 47fd4b0dd0..5007c4255d 100644 --- a/crates/wast/src/core/binary.rs +++ b/crates/wast/src/core/binary.rs @@ -928,10 +928,13 @@ impl Encode for Ordering { } } -impl Encode for OrderedAccess<'_> { +impl Encode for Ordered +where + T: Encode, +{ fn encode(&self, buf: &mut Vec) { self.ordering.encode(buf); - self.index.encode(buf); + self.inner.encode(buf); } } diff --git a/crates/wast/src/core/expr.rs b/crates/wast/src/core/expr.rs index 9104a8ea7f..c45203106b 100644 --- a/crates/wast/src/core/expr.rs +++ b/crates/wast/src/core/expr.rs @@ -859,15 +859,42 @@ instructions! { I64AtomicRmw32CmpxchgU(MemArg<4>) : [0xfe, 0x4e] : "i64.atomic.rmw32.cmpxchg_u", // proposal: shared-everything-threads - GlobalAtomicGet(OrderedAccess<'a>) : [0xfe, 0x4f] : "global.atomic.get", - GlobalAtomicSet(OrderedAccess<'a>) : [0xfe, 0x50] : "global.atomic.set", - GlobalAtomicRmwAdd(OrderedAccess<'a>) : [0xfe, 0x51] : "global.atomic.rmw.add", - GlobalAtomicRmwSub(OrderedAccess<'a>) : [0xfe, 0x52] : "global.atomic.rmw.sub", - GlobalAtomicRmwAnd(OrderedAccess<'a>) : [0xfe, 0x53] : "global.atomic.rmw.and", - GlobalAtomicRmwOr(OrderedAccess<'a>) : [0xfe, 0x54] : "global.atomic.rmw.or", - GlobalAtomicRmwXor(OrderedAccess<'a>) : [0xfe, 0x55] : "global.atomic.rmw.xor", - GlobalAtomicRmwXchg(OrderedAccess<'a>) : [0xfe, 0x56] : "global.atomic.rmw.xchg", - GlobalAtomicRmwCmpxchg(OrderedAccess<'a>) : [0xfe, 0x57] : "global.atomic.rmw.cmpxchg", + GlobalAtomicGet(Ordered>) : [0xfe, 0x4f] : "global.atomic.get", + GlobalAtomicSet(Ordered>) : [0xfe, 0x50] : "global.atomic.set", + GlobalAtomicRmwAdd(Ordered>) : [0xfe, 0x51] : "global.atomic.rmw.add", + GlobalAtomicRmwSub(Ordered>) : [0xfe, 0x52] : "global.atomic.rmw.sub", + GlobalAtomicRmwAnd(Ordered>) : [0xfe, 0x53] : "global.atomic.rmw.and", + GlobalAtomicRmwOr(Ordered>) : [0xfe, 0x54] : "global.atomic.rmw.or", + GlobalAtomicRmwXor(Ordered>) : [0xfe, 0x55] : "global.atomic.rmw.xor", + GlobalAtomicRmwXchg(Ordered>) : [0xfe, 0x56] : "global.atomic.rmw.xchg", + GlobalAtomicRmwCmpxchg(Ordered>) : [0xfe, 0x57] : "global.atomic.rmw.cmpxchg", + TableAtomicGet(Ordered>) : [0xfe, 0x58] : "table.atomic.get", + TableAtomicSet(Ordered>) : [0xfe, 0x59] : "table.atomic.set", + TableAtomicRmwXchg(Ordered>) : [0xfe, 0x5A] : "table.atomic.rmw.xchg", + TableAtomicRmwCmpxchg(Ordered>) : [0xFE, 0x5B] : "table.atomic.rmw.cmpxchg", + StructAtomicGet(Ordered>) : [0xFE, 0x5C] : "struct.atomic.get", + StructAtomicGetS(Ordered>) : [0xFE, 0x5D] : "struct.atomic.get_s", + StructAtomicGetU(Ordered>) : [0xFE, 0x5E] : "struct.atomic.get_u", + StructAtomicSet(Ordered>) : [0xFE, 0x5F] : "struct.atomic.set", + StructAtomicRmwAdd(Ordered>) : [0xFE, 0x60] : "struct.atomic.rmw.add", + StructAtomicRmwSub(Ordered>) : [0xFE, 0x61] : "struct.atomic.rmw.sub", + StructAtomicRmwAnd(Ordered>) : [0xFE, 0x62] : "struct.atomic.rmw.and", + StructAtomicRmwOr(Ordered>) : [0xFE, 0x63] : "struct.atomic.rmw.or", + StructAtomicRmwXor(Ordered>) : [0xFE, 0x64] : "struct.atomic.rmw.xor", + StructAtomicRmwXchg(Ordered>) : [0xFE, 0x65] : "struct.atomic.rmw.xchg", + StructAtomicRmwCmpxchg(Ordered>) : [0xFE, 0x66] : "struct.atomic.rmw.cmpxchg", + ArrayAtomicGet(Ordered>) : [0xFE, 0x67] : "array.atomic.get", + ArrayAtomicGetS(Ordered>) : [0xFE, 0x68] : "array.atomic.get_s", + ArrayAtomicGetU(Ordered>) : [0xFE, 0x69] : "array.atomic.get_u", + ArrayAtomicSet(Ordered>) : [0xFE, 0x6A] : "array.atomic.set", + ArrayAtomicRmwAdd(Ordered>) : [0xFE, 0x6B] : "array.atomic.rmw.add", + ArrayAtomicRmwSub(Ordered>) : [0xFE, 0x6C] : "array.atomic.rmw.sub", + ArrayAtomicRmwAnd(Ordered>) : [0xFE, 0x6D] : "array.atomic.rmw.and", + ArrayAtomicRmwOr(Ordered>) : [0xFE, 0x6E] : "array.atomic.rmw.or", + ArrayAtomicRmwXor(Ordered>) : [0xFE, 0x6F] : "array.atomic.rmw.xor", + ArrayAtomicRmwXchg(Ordered>) : [0xFE, 0x70] : "array.atomic.rmw.xchg", + ArrayAtomicRmwCmpxchg(Ordered>) : [0xFE, 0x71] : "array.atomic.rmw.cmpxchg", + RefI31Shared : [0xFE, 0x72] : "ref.i31_shared", // proposal: simd // @@ -1531,6 +1558,8 @@ pub struct TableArg<'a> { pub dst: Index<'a>, } +// `TableArg` could be an unwrapped as an `Index` if not for this custom parse +// behavior: if we cannot parse a table index, we default to table `0`. impl<'a> Parse<'a> for TableArg<'a> { fn parse(parser: Parser<'a>) -> Result { let dst = if let Some(dst) = parser.parse()? { @@ -1831,20 +1860,27 @@ impl<'a> Parse<'a> for Ordering { } } -/// Extra data associated with the `global.atomic.*` instructions. +/// Add a memory [`Ordering`] to the argument `T` of some instruction. +/// +/// This is helpful for many kinds of `*.atomic.*` instructions introduced by +/// the shared-everything-threads proposal. Many of these instructions "build +/// on" existing instructions by simply adding a memory order to them. #[derive(Clone, Debug)] -pub struct OrderedAccess<'a> { +pub struct Ordered { /// The memory ordering for this atomic instruction. pub ordering: Ordering, - /// The index of the global to access. - pub index: Index<'a>, + /// The original argument type. + pub inner: T, } -impl<'a> Parse<'a> for OrderedAccess<'a> { +impl<'a, T> Parse<'a> for Ordered +where + T: Parse<'a>, +{ fn parse(parser: Parser<'a>) -> Result { let ordering = parser.parse()?; - let index = parser.parse()?; - Ok(OrderedAccess { ordering, index }) + let inner = parser.parse()?; + Ok(Ordered { ordering, inner }) } } diff --git a/crates/wast/src/core/resolve/names.rs b/crates/wast/src/core/resolve/names.rs index d2b7eb1db4..c0c5e5b586 100644 --- a/crates/wast/src/core/resolve/names.rs +++ b/crates/wast/src/core/resolve/names.rs @@ -453,6 +453,13 @@ impl<'a, 'b> ExprResolver<'a, 'b> { self.resolver.resolve(&mut i.dst, Ns::Table)?; } + TableAtomicGet(i) + | TableAtomicSet(i) + | TableAtomicRmwXchg(i) + | TableAtomicRmwCmpxchg(i) => { + self.resolver.resolve(&mut i.inner.dst, Ns::Table)?; + } + GlobalSet(i) | GlobalGet(i) => { self.resolver.resolve(i, Ns::Global)?; } @@ -466,7 +473,7 @@ impl<'a, 'b> ExprResolver<'a, 'b> { | GlobalAtomicRmwXor(i) | GlobalAtomicRmwXchg(i) | GlobalAtomicRmwCmpxchg(i) => { - self.resolver.resolve(&mut i.index, Ns::Global)?; + self.resolver.resolve(&mut i.inner, Ns::Global)?; } LocalSet(i) | LocalGet(i) | LocalTee(i) => { @@ -614,14 +621,21 @@ impl<'a, 'b> ExprResolver<'a, 'b> { } StructSet(s) | StructGet(s) | StructGetS(s) | StructGetU(s) => { - let type_index = self.resolver.resolve(&mut s.r#struct, Ns::Type)?; - if let Index::Id(field_id) = s.field { - self.resolver - .fields - .get(&type_index) - .ok_or(Error::new(field_id.span(), format!("accessing a named field `{}` in a struct without named fields, type index {}", field_id.name(), type_index)))? - .resolve(&mut s.field, "field")?; - } + self.resolve_field(s)?; + } + + StructAtomicGet(s) + | StructAtomicGetS(s) + | StructAtomicGetU(s) + | StructAtomicSet(s) + | StructAtomicRmwAdd(s) + | StructAtomicRmwSub(s) + | StructAtomicRmwAnd(s) + | StructAtomicRmwOr(s) + | StructAtomicRmwXor(s) + | StructAtomicRmwXchg(s) + | StructAtomicRmwCmpxchg(s) => { + self.resolve_field(&mut s.inner)?; } ArrayNewFixed(a) => { @@ -651,6 +665,20 @@ impl<'a, 'b> ExprResolver<'a, 'b> { self.resolver.elems.resolve(&mut a.segment, "elem")?; } + ArrayAtomicGet(i) + | ArrayAtomicGetS(i) + | ArrayAtomicGetU(i) + | ArrayAtomicSet(i) + | ArrayAtomicRmwAdd(i) + | ArrayAtomicRmwSub(i) + | ArrayAtomicRmwAnd(i) + | ArrayAtomicRmwOr(i) + | ArrayAtomicRmwXor(i) + | ArrayAtomicRmwXchg(i) + | ArrayAtomicRmwCmpxchg(i) => { + self.resolver.resolve(&mut i.inner, Ns::Type)?; + } + RefNull(ty) => self.resolver.resolve_heaptype(ty)?, _ => {} @@ -678,6 +706,18 @@ impl<'a, 'b> ExprResolver<'a, 'b> { None => Err(resolve_error(id, "label")), } } + + fn resolve_field(&self, s: &mut StructAccess<'a>) -> Result<(), Error> { + let type_index = self.resolver.resolve(&mut s.r#struct, Ns::Type)?; + if let Index::Id(field_id) = s.field { + self.resolver + .fields + .get(&type_index) + .ok_or(Error::new(field_id.span(), format!("accessing a named field `{}` in a struct without named fields, type index {}", field_id.name(), type_index)))? + .resolve(&mut s.field, "field")?; + } + Ok(()) + } } enum TypeInfo<'a> { diff --git a/tests/local/shared-everything-threads/array.wast b/tests/local/shared-everything-threads/array.wast index e3fb3adbad..064fcecf80 100644 --- a/tests/local/shared-everything-threads/array.wast +++ b/tests/local/shared-everything-threads/array.wast @@ -1,4 +1,4 @@ -;; Shared array declaration syntax +;; Shared array declaration syntax. (module (type (shared (array i8))) (type (sub final (shared (array i8)))) @@ -11,7 +11,7 @@ (global (ref 2) (array.new_default 0 (i32.const 1))) ) -;; Shared arrays are distinct from non-shared arrays +;; Shared arrays are distinct from non-shared arrays. (assert_invalid (module (type (shared (array i8))) @@ -32,7 +32,7 @@ "type mismatch" ) -;; Shared arrays may not be subtypes of non-shared arrays +;; Shared arrays may not be subtypes of non-shared arrays. (assert_invalid (module (type (sub (array i8))) @@ -41,7 +41,7 @@ "sub type must match super type" ) -;; Non-shared arrays may not be subtypes of shared arrays +;; Non-shared arrays may not be subtypes of shared arrays. (assert_invalid (module (type (sub (shared (array i8)))) @@ -50,7 +50,7 @@ "sub type must match super type" ) -;; Shared arrays may not contain non-shared references +;; Shared arrays may not contain non-shared references. (assert_invalid (module (type (shared (array anyref))) @@ -58,12 +58,12 @@ "must contain shared type" ) -;; But they may contain shared references +;; But they may contain shared references. (module (type (shared (array (ref null (shared any))))) ) -;; Non-shared arrays may contain shared references +;; Non-shared arrays may contain shared references. (module (type (array (ref null (shared any)))) ) @@ -113,3 +113,641 @@ (func (param (ref null $i8)) (array.init_data $i8 0 (local.get 0) (i32.const 0) (i32.const 0) (i32.const 0))) ) + +;; Check `array.atomic.rmw.*` instructions. +(module (; get, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-get-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get seq_cst $a) +) + +(module (; get, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-get-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (result i64) + local.get $x + local.get $y + array.atomic.get seq_cst $a) +) + +(module (; get, anyref, seq_cst ;) + (type $a (shared (array (mut (ref null (shared any)))))) + (func (export "array-atomic-get-anyref-seq_cst") (param $x (ref null $a)) (param $y i32) (result (ref null (shared any))) + local.get $x + local.get $y + array.atomic.get seq_cst $a) +) + +(module (; get, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-get-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get acq_rel $a) +) + +(module (; get, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-get-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (result i64) + local.get $x + local.get $y + array.atomic.get acq_rel $a) +) + +(module (; get, anyref, acq_rel ;) + (type $a (shared (array (mut (ref null (shared any)))))) + (func (export "array-atomic-get-anyref-acq_rel") (param $x (ref null $a)) (param $y i32) (result (ref null (shared any))) + local.get $x + local.get $y + array.atomic.get acq_rel $a) +) + +(module (; get_s, i8, seq_cst ;) + (type $a (shared (array (mut i8)))) + (func (export "array-atomic-get_s-i8-seq_cst") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s seq_cst $a) +) + +(module (; get_s, i16, seq_cst ;) + (type $a (shared (array (mut i16)))) + (func (export "array-atomic-get_s-i16-seq_cst") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s seq_cst $a) +) + +(module (; get_s, i8, acq_rel ;) + (type $a (shared (array (mut i8)))) + (func (export "array-atomic-get_s-i8-acq_rel") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s acq_rel $a) +) + +(module (; get_s, i16, acq_rel ;) + (type $a (shared (array (mut i16)))) + (func (export "array-atomic-get_s-i16-acq_rel") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s acq_rel $a) +) + +(module (; get_u, i8, seq_cst ;) + (type $a (shared (array (mut i8)))) + (func (export "array-atomic-get_u-i8-seq_cst") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u seq_cst $a) +) + +(module (; get_u, i16, seq_cst ;) + (type $a (shared (array (mut i16)))) + (func (export "array-atomic-get_u-i16-seq_cst") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u seq_cst $a) +) + +(module (; get_u, i8, acq_rel ;) + (type $a (shared (array (mut i8)))) + (func (export "array-atomic-get_u-i8-acq_rel") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u acq_rel $a) +) + +(module (; get_u, i16, acq_rel ;) + (type $a (shared (array (mut i16)))) + (func (export "array-atomic-get_u-i16-acq_rel") (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u acq_rel $a) +) + +(module (; set, i8, seq_cst ;) + (type $a (shared (array (mut i8)))) + (func (export "array-atomic-set-i8-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a) +) + +(module (; set, i16, seq_cst ;) + (type $a (shared (array (mut i16)))) + (func (export "array-atomic-set-i16-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a) +) + +(module (; set, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-set-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a) +) + +(module (; set, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-set-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i64) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a) +) + +(module (; set, anyref, seq_cst ;) + (type $a (shared (array (mut (ref null (shared any)))))) + (func (export "array-atomic-set-anyref-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a) +) + +(module (; set, i8, acq_rel ;) + (type $a (shared (array (mut i8)))) + (func (export "array-atomic-set-i8-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a) +) + +(module (; set, i16, acq_rel ;) + (type $a (shared (array (mut i16)))) + (func (export "array-atomic-set-i16-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a) +) + +(module (; set, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-set-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a) +) + +(module (; set, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-set-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i64) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a) +) + +(module (; set, anyref, acq_rel ;) + (type $a (shared (array (mut (ref null (shared any)))))) + (func (export "array-atomic-set-anyref-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a) +) + +(module (; rmw.add, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.add-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add seq_cst $a) +) + +(module (; rmw.add, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.add-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add seq_cst $a) +) + +(module (; rmw.add, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.add-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add acq_rel $a) +) + +(module (; rmw.add, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.add-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add acq_rel $a) +) + +(module (; rmw.sub, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.sub-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub seq_cst $a) +) + +(module (; rmw.sub, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.sub-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub seq_cst $a) +) + +(module (; rmw.sub, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.sub-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub acq_rel $a) +) + +(module (; rmw.sub, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.sub-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub acq_rel $a) +) + +(module (; rmw.and, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.and-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and seq_cst $a) +) + +(module (; rmw.and, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.and-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and seq_cst $a) +) + +(module (; rmw.and, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.and-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and acq_rel $a) +) + +(module (; rmw.and, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.and-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and acq_rel $a) +) + +(module (; rmw.or, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.or-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or seq_cst $a) +) + +(module (; rmw.or, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.or-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or seq_cst $a) +) + +(module (; rmw.or, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.or-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or acq_rel $a) +) + +(module (; rmw.or, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.or-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or acq_rel $a) +) + +(module (; rmw.xor, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.xor-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor seq_cst $a) +) + +(module (; rmw.xor, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.xor-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor seq_cst $a) +) + +(module (; rmw.xor, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.xor-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor acq_rel $a) +) + +(module (; rmw.xor, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.xor-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor acq_rel $a) +) + +(module (; rmw.xchg, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.xchg-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg seq_cst $a) +) + +(module (; rmw.xchg, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.xchg-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg seq_cst $a) +) + +(module (; rmw.xchg, anyref, seq_cst ;) + (type $a (shared (array (mut (ref null (shared any)))))) + (func (export "array-atomic-rmw.xchg-anyref-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg seq_cst $a) +) + +(module (; rmw.xchg, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.xchg-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg acq_rel $a) +) + +(module (; rmw.xchg, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.xchg-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg acq_rel $a) +) + +(module (; rmw.xchg, anyref, acq_rel ;) + (type $a (shared (array (mut (ref null (shared any)))))) + (func (export "array-atomic-rmw.xchg-anyref-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg acq_rel $a) +) + +(module (; rmw.cmpxchg, i32, seq_cst ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.cmpxchg-i32-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i32) (param $A i32) (result i32) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg seq_cst $a) +) + +(module (; rmw.cmpxchg, i64, seq_cst ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.cmpxchg-i64-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z i64) (param $A i64) (result i64) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg seq_cst $a) +) + +(module (; rmw.cmpxchg, eqref, seq_cst ;) + (type $a (shared (array (mut (ref null (shared eq)))))) + (func (export "array-atomic-rmw.cmpxchg-eqref-seq_cst") (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared eq))) (param $A (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg seq_cst $a) +) + +(module (; rmw.cmpxchg, i32, acq_rel ;) + (type $a (shared (array (mut i32)))) + (func (export "array-atomic-rmw.cmpxchg-i32-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i32) (param $A i32) (result i32) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg acq_rel $a) +) + +(module (; rmw.cmpxchg, i64, acq_rel ;) + (type $a (shared (array (mut i64)))) + (func (export "array-atomic-rmw.cmpxchg-i64-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z i64) (param $A i64) (result i64) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg acq_rel $a) +) + +(module (; rmw.cmpxchg, eqref, acq_rel ;) + (type $a (shared (array (mut (ref null (shared eq)))))) + (func (export "array-atomic-rmw.cmpxchg-eqref-acq_rel") (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared eq))) (param $A (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg acq_rel $a) +) + +(assert_invalid (; get, i8 ;) + (module + (type $a (shared (array (mut i8)))) + (func (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get seq_cst $a) + ) + "packed storage type" +) +(assert_invalid (; get_s, i32 ;) + (module + (type $a (shared (array (mut i32)))) + (func (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s seq_cst $a) + ) + "non-packed storage type" +) +(assert_invalid (; get_s, anyref ;) + (module + (type $a (shared (array (mut (ref null (shared any)))))) + (func (param $x (ref null $a)) (param $y i32) (result (ref null (shared any))) + local.get $x + local.get $y + array.atomic.get_s seq_cst $a) + ) + "non-packed storage type" +) +(assert_invalid (; get_u, i32 ;) + (module + (type $a (shared (array (mut i32)))) + (func (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u seq_cst $a) + ) + "non-packed storage type" +) +(assert_invalid (; get_u, anyref ;) + (module + (type $a (shared (array (mut (ref null (shared any)))))) + (func (param $x (ref null $a)) (param $y i32) (result (ref null (shared any))) + local.get $x + local.get $y + array.atomic.get_u seq_cst $a) + ) + "non-packed storage type" +) +(assert_invalid (; rmw.add, anyref ;) + (module + (type $a (shared (array (mut (ref null (shared any)))))) + (func (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add seq_cst $a) + ) + "invalid type" +) +(assert_invalid (; rmw.sub, anyref ;) + (module + (type $a (shared (array (mut (ref null (shared any)))))) + (func (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub seq_cst $a) + ) + "invalid type" +) +(assert_invalid (; rmw.and, anyref ;) + (module + (type $a (shared (array (mut (ref null (shared any)))))) + (func (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and seq_cst $a) + ) + "invalid type" +) +(assert_invalid (; rmw.or, anyref ;) + (module + (type $a (shared (array (mut (ref null (shared any)))))) + (func (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or seq_cst $a) + ) + "invalid type" +) +(assert_invalid (; rmw.xor, anyref ;) + (module + (type $a (shared (array (mut (ref null (shared any)))))) + (func (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor seq_cst $a) + ) + "invalid type" +) +(assert_invalid (; rmw.xchg, i8 ;) + (module + (type $a (shared (array (mut i8)))) + (func (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg seq_cst $a) + ) + "invalid type" +) +(assert_invalid (; rmw.cmpxchg, i8 ;) + (module + (type $a (shared (array (mut i8)))) + (func (param $x (ref null $a)) (param $y i32) (param $z i32) (param $A i32) (result i32) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg seq_cst $a) + ) + "invalid type" +) +(assert_invalid (; rmw.cmpxchg, anyref ;) + (module + (type $a (shared (array (mut (ref null (shared any)))))) + (func (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (param $A (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg seq_cst $a) + ) + "invalid type" +) diff --git a/tests/local/shared-everything-threads/struct.wast b/tests/local/shared-everything-threads/struct.wast index 91b1cec268..cd6664631a 100644 --- a/tests/local/shared-everything-threads/struct.wast +++ b/tests/local/shared-everything-threads/struct.wast @@ -1,4 +1,4 @@ -;; Shared struct declaration syntax +;; Shared struct declaration syntax. (module (type (shared (struct))) (type (sub final (shared (struct)))) @@ -11,7 +11,7 @@ (global (ref 2) (struct.new 0)) ) -;; Shared structs are distinct from non-shared structs +;; Shared structs are distinct from non-shared structs. (assert_invalid (module (type (shared (struct))) @@ -32,7 +32,7 @@ "type mismatch" ) -;; Shared structs may not be subtypes of non-shared structs +;; Shared structs may not be subtypes of non-shared structs. (assert_invalid (module (type (sub (struct))) @@ -41,7 +41,7 @@ "must match super type" ) -;; Non-shared structs may not be subtypes of shared structs +;; Non-shared structs may not be subtypes of shared structs. (assert_invalid (module (type (sub (shared (struct)))) @@ -50,7 +50,7 @@ "must match super type" ) -;; Shared structs may not contain non-shared references +;; Shared structs may not contain non-shared references. (assert_invalid (module (type (shared (struct (field anyref)))) @@ -58,12 +58,12 @@ "must contain shared type" ) -;; But they may contain shared references +;; But they may contain shared references. (module (type (shared (struct (field (ref null (shared any)))))) ) -;; Non-shared structs may contain shared references +;; Non-shared structs may contain shared references. (module (type (struct (field (ref null (shared any))))) ) @@ -90,3 +90,358 @@ (func (param (ref null $i8)) (struct.set $i8 0 (local.get 0) (i32.const 0))) ) + +;; Check struct.atomic.rmw.* instructions +(module + (type $s (shared (struct + (field $i8 (mut i8)) + (field $i16 (mut i16)) + (field $i32 (mut i32)) + (field $i64 (mut i64)) + (field $anyref (mut (ref null (shared any)))) + (field $eqref (mut (ref null (shared eq))))))) + (func (export "struct-atomic-get-i32-seq_cst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get seq_cst $s $i32) + (func (export "struct-atomic-get-i64-seq_cst") (param $x (ref null $s)) (result i64) + local.get $x + struct.atomic.get seq_cst $s $i64) + (func (export "struct-atomic-get-anyref-seq_cst") (param $x (ref null $s)) (result (ref null (shared any))) + local.get $x + struct.atomic.get seq_cst $s $anyref) + (func (export "struct-atomic-get-i32-acq_rel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get acq_rel $s $i32) + (func (export "struct-atomic-get-i64-acq_rel") (param $x (ref null $s)) (result i64) + local.get $x + struct.atomic.get acq_rel $s $i64) + (func (export "struct-atomic-get-anyref-acq_rel") (param $x (ref null $s)) (result (ref null (shared any))) + local.get $x + struct.atomic.get acq_rel $s $anyref) + (func (export "struct-atomic-get_s-i8-seq_cst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s seq_cst $s $i8) + (func (export "struct-atomic-get_s-i16-seq_cst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s seq_cst $s $i16) + (func (export "struct-atomic-get_s-i8-acq_rel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s acq_rel $s $i8) + (func (export "struct-atomic-get_s-i16-acq_rel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s acq_rel $s $i16) + (func (export "struct-atomic-get_u-i8-seq_cst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u seq_cst $s $i8) + (func (export "struct-atomic-get_u-i16-seq_cst") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u seq_cst $s $i16) + (func (export "struct-atomic-get_u-i8-acq_rel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u acq_rel $s $i8) + (func (export "struct-atomic-get_u-i16-acq_rel") (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u acq_rel $s $i16) + (func (export "struct-atomic-set-i8-seq_cst") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $i8) + (func (export "struct-atomic-set-i16-seq_cst") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $i16) + (func (export "struct-atomic-set-i32-seq_cst") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $i32) + (func (export "struct-atomic-set-i64-seq_cst") (param $x (ref null $s)) (param $y i64) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $i64) + (func (export "struct-atomic-set-anyref-seq_cst") (param $x (ref null $s)) (param $y (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $anyref) + (func (export "struct-atomic-set-i8-acq_rel") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $i8) + (func (export "struct-atomic-set-i16-acq_rel") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $i16) + (func (export "struct-atomic-set-i32-acq_rel") (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $i32) + (func (export "struct-atomic-set-i64-acq_rel") (param $x (ref null $s)) (param $y i64) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $i64) + (func (export "struct-atomic-set-anyref-acq_rel") (param $x (ref null $s)) (param $y (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $anyref) + (func (export "struct-atomic-rmw.add-i32-seq_cst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.add seq_cst $s $i32) + (func (export "struct-atomic-rmw.add-i64-seq_cst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.add seq_cst $s $i64) + (func (export "struct-atomic-rmw.add-i32-acq_rel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.add acq_rel $s $i32) + (func (export "struct-atomic-rmw.add-i64-acq_rel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.add acq_rel $s $i64) + (func (export "struct-atomic-rmw.sub-i32-seq_cst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.sub seq_cst $s $i32) + (func (export "struct-atomic-rmw.sub-i64-seq_cst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.sub seq_cst $s $i64) + (func (export "struct-atomic-rmw.sub-i32-acq_rel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.sub acq_rel $s $i32) + (func (export "struct-atomic-rmw.sub-i64-acq_rel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.sub acq_rel $s $i64) + (func (export "struct-atomic-rmw.and-i32-seq_cst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.and seq_cst $s $i32) + (func (export "struct-atomic-rmw.and-i64-seq_cst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.and seq_cst $s $i64) + (func (export "struct-atomic-rmw.and-i32-acq_rel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.and acq_rel $s $i32) + (func (export "struct-atomic-rmw.and-i64-acq_rel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.and acq_rel $s $i64) + (func (export "struct-atomic-rmw.or-i32-seq_cst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.or seq_cst $s $i32) + (func (export "struct-atomic-rmw.or-i64-seq_cst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.or seq_cst $s $i64) + (func (export "struct-atomic-rmw.or-i32-acq_rel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.or acq_rel $s $i32) + (func (export "struct-atomic-rmw.or-i64-acq_rel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.or acq_rel $s $i64) + (func (export "struct-atomic-rmw.xor-i32-seq_cst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xor seq_cst $s $i32) + (func (export "struct-atomic-rmw.xor-i64-seq_cst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xor seq_cst $s $i64) + (func (export "struct-atomic-rmw.xor-i32-acq_rel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xor acq_rel $s $i32) + (func (export "struct-atomic-rmw.xor-i64-acq_rel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xor acq_rel $s $i64) + (func (export "struct-atomic-rmw.xchg-i32-seq_cst") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xchg seq_cst $s $i32) + (func (export "struct-atomic-rmw.xchg-i64-seq_cst") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xchg seq_cst $s $i64) + (func (export "struct-atomic-rmw.xchg-anyref-seq_cst") (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.xchg seq_cst $s $anyref) + (func (export "struct-atomic-rmw.xchg-i32-acq_rel") (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xchg acq_rel $s $i32) + (func (export "struct-atomic-rmw.xchg-i64-acq_rel") (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xchg acq_rel $s $i64) + (func (export "struct-atomic-rmw.xchg-anyref-acq_rel") (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.xchg acq_rel $s $anyref) + (func (export "struct-atomic-rmw.cmpxchg-i32-seq_cst") (param $x (ref null $s)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seq_cst $s $i32) + (func (export "struct-atomic-rmw.cmpxchg-i64-seq_cst") (param $x (ref null $s)) (param $y i64) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seq_cst $s $i64) + (func (export "struct-atomic-rmw.cmpxchg-eqref-seq_cst") (param $x (ref null $s)) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seq_cst $s $eqref) + (func (export "struct-atomic-rmw.cmpxchg-i32-acq_rel") (param $x (ref null $s)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acq_rel $s $i32) + (func (export "struct-atomic-rmw.cmpxchg-i64-acq_rel") (param $x (ref null $s)) (param $y i64) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acq_rel $s $i64) + (func (export "struct-atomic-rmw.cmpxchg-eqref-acq_rel") (param $x (ref null $s)) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acq_rel $s $eqref) +) + +(assert_invalid (; get, i8 ;) + (module + (type $s (shared (struct (field $i8 (mut i8))))) + (func (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get seq_cst $s $i8) + ) + "non-packed storage type" +) +(assert_invalid (; get_s, i32 ;) + (module + (type $s (shared (struct (field $i32 (mut i32))))) + (func (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s seq_cst $s $i32) + ) + "non-packed storage types" +) +(assert_invalid (; get_s, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (result (ref null (shared any))) + local.get $x + struct.atomic.get_s seq_cst $s $anyref) + ) + "non-packed storage types" +) +(assert_invalid (; get_u, i32 ;) + (module + (type $s (shared (struct (field $i32 (mut i32))))) + (func (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u seq_cst $s $i32) + ) + "non-packed storage types" +) +(assert_invalid (; get_u, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (result (ref null (shared any))) + local.get $x + struct.atomic.get_u seq_cst $s $anyref) + ) + "non-packed storage types" +) +(assert_invalid (; rmw.add, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.add seq_cst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.sub, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.sub seq_cst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.and, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.and seq_cst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.or, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.or seq_cst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.xor, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.xor seq_cst $s $anyref) + ) + "invalid type" +) +(assert_invalid (; rmw.xchg, i8 ;) + (module + (type $s (shared (struct (field $i8 (mut i8))))) + (func (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xchg seq_cst $s $i8) + ) + "invalid type" +) +(assert_invalid (; rmw.cmpxchg, i8 ;) + (module + (type $s (shared (struct (field $i8 (mut i8))))) + (func (param $x (ref null $s)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seq_cst $s $i8) + ) + "invalid type" +) +(assert_invalid (; rmw.cmpxchg, anyref ;) + (module + (type $s (shared (struct (field $anyref (mut (ref null (shared any))))))) + (func (param $x (ref null $s)) (param $y (ref null (shared any))) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seq_cst $s $anyref) + ) + "invalid type" +) diff --git a/tests/local/shared-everything-threads/table.wast b/tests/local/shared-everything-threads/table.wast index 0b4eedc2ce..b733ec4c2b 100644 --- a/tests/local/shared-everything-threads/table.wast +++ b/tests/local/shared-everything-threads/table.wast @@ -41,3 +41,134 @@ (type $t (func)) (table shared 0 (ref $t))) "shared tables must have a shared element type") + +;; Check `table.atomic.*` instructions. +(module (;eq;) + (table $a (import "spectest" "table_eq") shared 1 (ref null (shared eq))) + (table $b shared 1 (ref null (shared eq))) + (func (export "table-atomic-get-eq-seq_cst-$a") (param $x i32) (result (ref null (shared eq))) + local.get $x + table.atomic.get seq_cst $a) + (func (export "table-atomic-get-eq-seq_cst-$b") (param $x i32) (result (ref null (shared eq))) + local.get $x + table.atomic.get seq_cst $b) + (func (export "table-atomic-get-eq-acq_rel-$a") (param $x i32) (result (ref null (shared eq))) + local.get $x + table.atomic.get acq_rel $a) + (func (export "table-atomic-get-eq-acq_rel-$b") (param $x i32) (result (ref null (shared eq))) + local.get $x + table.atomic.get acq_rel $b) + (func (export "table-atomic-set-eq-seq_cst-$a") (param $x i32) (param $y (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.set seq_cst $a) + (func (export "table-atomic-set-eq-seq_cst-$b") (param $x i32) (param $y (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.set seq_cst $b) + (func (export "table-atomic-set-eq-acq_rel-$a") (param $x i32) (param $y (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.set acq_rel $a) + (func (export "table-atomic-set-eq-acq_rel-$b") (param $x i32) (param $y (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.set acq_rel $b) + (func (export "table-atomic-rmw.xchg-eq-seq_cst-$a") (param $x i32) (param $y (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.rmw.xchg seq_cst $a) + (func (export "table-atomic-rmw.xchg-eq-seq_cst-$b") (param $x i32) (param $y (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.rmw.xchg seq_cst $b) + (func (export "table-atomic-rmw.xchg-eq-acq_rel-$a") (param $x i32) (param $y (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.rmw.xchg acq_rel $a) + (func (export "table-atomic-rmw.xchg-eq-acq_rel-$b") (param $x i32) (param $y (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.rmw.xchg acq_rel $b) + (func (export "table-atomic-rmw.cmpxchg-eq-seq_cst-$a") (param $x i32) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg seq_cst $a) + (func (export "table-atomic-rmw.cmpxchg-eq-seq_cst-$b") (param $x i32) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg seq_cst $b) + (func (export "table-atomic-rmw.cmpxchg-eq-acq_rel-$a") (param $x i32) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg acq_rel $a) + (func (export "table-atomic-rmw.cmpxchg-eq-acq_rel-$b") (param $x i32) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg acq_rel $b) +) + +(module (;any;) + (table $a (import "spectest" "table_any") shared 1 (ref null (shared any))) + (table $b shared 1 (ref null (shared any))) + (func (export "table-atomic-get-any-seq_cst-$a") (param $x i32) (result (ref null (shared any))) + local.get $x + table.atomic.get seq_cst $a) + (func (export "table-atomic-get-any-seq_cst-$b") (param $x i32) (result (ref null (shared any))) + local.get $x + table.atomic.get seq_cst $b) + (func (export "table-atomic-get-any-acq_rel-$a") (param $x i32) (result (ref null (shared any))) + local.get $x + table.atomic.get acq_rel $a) + (func (export "table-atomic-get-any-acq_rel-$b") (param $x i32) (result (ref null (shared any))) + local.get $x + table.atomic.get acq_rel $b) + (func (export "table-atomic-set-any-seq_cst-$a") (param $x i32) (param $y (ref null (shared any))) + local.get $x + local.get $y + table.atomic.set seq_cst $a) + (func (export "table-atomic-set-any-seq_cst-$b") (param $x i32) (param $y (ref null (shared any))) + local.get $x + local.get $y + table.atomic.set seq_cst $b) + (func (export "table-atomic-set-any-acq_rel-$a") (param $x i32) (param $y (ref null (shared any))) + local.get $x + local.get $y + table.atomic.set acq_rel $a) + (func (export "table-atomic-set-any-acq_rel-$b") (param $x i32) (param $y (ref null (shared any))) + local.get $x + local.get $y + table.atomic.set acq_rel $b) + (func (export "table-atomic-rmw.xchg-any-seq_cst-$a") (param $x i32) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + table.atomic.rmw.xchg seq_cst $a) + (func (export "table-atomic-rmw.xchg-any-seq_cst-$b") (param $x i32) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + table.atomic.rmw.xchg seq_cst $b) + (func (export "table-atomic-rmw.xchg-any-acq_rel-$a") (param $x i32) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + table.atomic.rmw.xchg acq_rel $a) + (func (export "table-atomic-rmw.xchg-any-acq_rel-$b") (param $x i32) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + table.atomic.rmw.xchg acq_rel $b) + ;; table.atomic.rmw.cmpxchg only works with subtypes of eqref. +) + +;; Check that cmpxchg only works with eqref subtypes. +(assert_invalid + (module + (table $a shared 0 (ref null (shared any))) + (func (param $x i32) (param $y (ref null (shared any))) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg seq_cst $a)) + "invalid type") diff --git a/tests/snapshots/local/shared-everything-threads/array.wast.json b/tests/snapshots/local/shared-everything-threads/array.wast.json index 03bf81d778..5118986647 100644 --- a/tests/snapshots/local/shared-everything-threads/array.wast.json +++ b/tests/snapshots/local/shared-everything-threads/array.wast.json @@ -55,6 +55,377 @@ "type": "module", "line": 72, "filename": "array.8.wasm" + }, + { + "type": "module", + "line": 118, + "filename": "array.9.wasm" + }, + { + "type": "module", + "line": 126, + "filename": "array.10.wasm" + }, + { + "type": "module", + "line": 134, + "filename": "array.11.wasm" + }, + { + "type": "module", + "line": 142, + "filename": "array.12.wasm" + }, + { + "type": "module", + "line": 150, + "filename": "array.13.wasm" + }, + { + "type": "module", + "line": 158, + "filename": "array.14.wasm" + }, + { + "type": "module", + "line": 166, + "filename": "array.15.wasm" + }, + { + "type": "module", + "line": 174, + "filename": "array.16.wasm" + }, + { + "type": "module", + "line": 182, + "filename": "array.17.wasm" + }, + { + "type": "module", + "line": 190, + "filename": "array.18.wasm" + }, + { + "type": "module", + "line": 198, + "filename": "array.19.wasm" + }, + { + "type": "module", + "line": 206, + "filename": "array.20.wasm" + }, + { + "type": "module", + "line": 214, + "filename": "array.21.wasm" + }, + { + "type": "module", + "line": 222, + "filename": "array.22.wasm" + }, + { + "type": "module", + "line": 230, + "filename": "array.23.wasm" + }, + { + "type": "module", + "line": 239, + "filename": "array.24.wasm" + }, + { + "type": "module", + "line": 248, + "filename": "array.25.wasm" + }, + { + "type": "module", + "line": 257, + "filename": "array.26.wasm" + }, + { + "type": "module", + "line": 266, + "filename": "array.27.wasm" + }, + { + "type": "module", + "line": 275, + "filename": "array.28.wasm" + }, + { + "type": "module", + "line": 284, + "filename": "array.29.wasm" + }, + { + "type": "module", + "line": 293, + "filename": "array.30.wasm" + }, + { + "type": "module", + "line": 302, + "filename": "array.31.wasm" + }, + { + "type": "module", + "line": 311, + "filename": "array.32.wasm" + }, + { + "type": "module", + "line": 320, + "filename": "array.33.wasm" + }, + { + "type": "module", + "line": 329, + "filename": "array.34.wasm" + }, + { + "type": "module", + "line": 338, + "filename": "array.35.wasm" + }, + { + "type": "module", + "line": 347, + "filename": "array.36.wasm" + }, + { + "type": "module", + "line": 356, + "filename": "array.37.wasm" + }, + { + "type": "module", + "line": 365, + "filename": "array.38.wasm" + }, + { + "type": "module", + "line": 374, + "filename": "array.39.wasm" + }, + { + "type": "module", + "line": 383, + "filename": "array.40.wasm" + }, + { + "type": "module", + "line": 392, + "filename": "array.41.wasm" + }, + { + "type": "module", + "line": 401, + "filename": "array.42.wasm" + }, + { + "type": "module", + "line": 410, + "filename": "array.43.wasm" + }, + { + "type": "module", + "line": 419, + "filename": "array.44.wasm" + }, + { + "type": "module", + "line": 428, + "filename": "array.45.wasm" + }, + { + "type": "module", + "line": 437, + "filename": "array.46.wasm" + }, + { + "type": "module", + "line": 446, + "filename": "array.47.wasm" + }, + { + "type": "module", + "line": 455, + "filename": "array.48.wasm" + }, + { + "type": "module", + "line": 464, + "filename": "array.49.wasm" + }, + { + "type": "module", + "line": 473, + "filename": "array.50.wasm" + }, + { + "type": "module", + "line": 482, + "filename": "array.51.wasm" + }, + { + "type": "module", + "line": 491, + "filename": "array.52.wasm" + }, + { + "type": "module", + "line": 500, + "filename": "array.53.wasm" + }, + { + "type": "module", + "line": 509, + "filename": "array.54.wasm" + }, + { + "type": "module", + "line": 518, + "filename": "array.55.wasm" + }, + { + "type": "module", + "line": 527, + "filename": "array.56.wasm" + }, + { + "type": "module", + "line": 536, + "filename": "array.57.wasm" + }, + { + "type": "module", + "line": 545, + "filename": "array.58.wasm" + }, + { + "type": "module", + "line": 554, + "filename": "array.59.wasm" + }, + { + "type": "module", + "line": 564, + "filename": "array.60.wasm" + }, + { + "type": "module", + "line": 574, + "filename": "array.61.wasm" + }, + { + "type": "module", + "line": 584, + "filename": "array.62.wasm" + }, + { + "type": "module", + "line": 594, + "filename": "array.63.wasm" + }, + { + "type": "module", + "line": 604, + "filename": "array.64.wasm" + }, + { + "type": "assert_invalid", + "line": 615, + "filename": "array.65.wasm", + "text": "packed storage type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 625, + "filename": "array.66.wasm", + "text": "non-packed storage type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 635, + "filename": "array.67.wasm", + "text": "non-packed storage type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 645, + "filename": "array.68.wasm", + "text": "non-packed storage type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 655, + "filename": "array.69.wasm", + "text": "non-packed storage type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 665, + "filename": "array.70.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 676, + "filename": "array.71.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 687, + "filename": "array.72.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 698, + "filename": "array.73.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 709, + "filename": "array.74.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 720, + "filename": "array.75.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 731, + "filename": "array.76.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 743, + "filename": "array.77.wasm", + "text": "invalid type", + "module_type": "binary" } ] } \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/10.print b/tests/snapshots/local/shared-everything-threads/array.wast/10.print new file mode 100644 index 0000000000..f733e00572 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/10.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i64) + local.get $x + local.get $y + array.atomic.get seq_cst $a + ) + (export "array-atomic-get-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/11.print b/tests/snapshots/local/shared-everything-threads/array.wast/11.print new file mode 100644 index 0000000000..76dbd3ecb7 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/11.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut (ref null (shared any)))))) + (type (;1;) (func (param (ref null $a) i32) (result (ref null (shared any))))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result (ref null (shared any))) + local.get $x + local.get $y + array.atomic.get seq_cst $a + ) + (export "array-atomic-get-anyref-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/12.print b/tests/snapshots/local/shared-everything-threads/array.wast/12.print new file mode 100644 index 0000000000..5cf5608397 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/12.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get acq_rel $a + ) + (export "array-atomic-get-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/13.print b/tests/snapshots/local/shared-everything-threads/array.wast/13.print new file mode 100644 index 0000000000..b4b3919e6f --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/13.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i64) + local.get $x + local.get $y + array.atomic.get acq_rel $a + ) + (export "array-atomic-get-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/14.print b/tests/snapshots/local/shared-everything-threads/array.wast/14.print new file mode 100644 index 0000000000..189c2ea090 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/14.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut (ref null (shared any)))))) + (type (;1;) (func (param (ref null $a) i32) (result (ref null (shared any))))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result (ref null (shared any))) + local.get $x + local.get $y + array.atomic.get acq_rel $a + ) + (export "array-atomic-get-anyref-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/15.print b/tests/snapshots/local/shared-everything-threads/array.wast/15.print new file mode 100644 index 0000000000..00e0b30082 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/15.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i8)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s seq_cst $a + ) + (export "array-atomic-get_s-i8-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/16.print b/tests/snapshots/local/shared-everything-threads/array.wast/16.print new file mode 100644 index 0000000000..82d7db16b1 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/16.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i16)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s seq_cst $a + ) + (export "array-atomic-get_s-i16-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/17.print b/tests/snapshots/local/shared-everything-threads/array.wast/17.print new file mode 100644 index 0000000000..0794e77851 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/17.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i8)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s acq_rel $a + ) + (export "array-atomic-get_s-i8-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/18.print b/tests/snapshots/local/shared-everything-threads/array.wast/18.print new file mode 100644 index 0000000000..f7a27cdcd8 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/18.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i16)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_s acq_rel $a + ) + (export "array-atomic-get_s-i16-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/19.print b/tests/snapshots/local/shared-everything-threads/array.wast/19.print new file mode 100644 index 0000000000..2ccc59945c --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/19.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i8)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u seq_cst $a + ) + (export "array-atomic-get_u-i8-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/20.print b/tests/snapshots/local/shared-everything-threads/array.wast/20.print new file mode 100644 index 0000000000..144068c536 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/20.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i16)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u seq_cst $a + ) + (export "array-atomic-get_u-i16-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/21.print b/tests/snapshots/local/shared-everything-threads/array.wast/21.print new file mode 100644 index 0000000000..e5bed640b9 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/21.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i8)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u acq_rel $a + ) + (export "array-atomic-get_u-i8-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/22.print b/tests/snapshots/local/shared-everything-threads/array.wast/22.print new file mode 100644 index 0000000000..069d3eb673 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/22.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i16)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get_u acq_rel $a + ) + (export "array-atomic-get_u-i16-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/23.print b/tests/snapshots/local/shared-everything-threads/array.wast/23.print new file mode 100644 index 0000000000..b1727c3227 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/23.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i8)))) + (type (;1;) (func (param (ref null $a) i32 i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a + ) + (export "array-atomic-set-i8-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/24.print b/tests/snapshots/local/shared-everything-threads/array.wast/24.print new file mode 100644 index 0000000000..95641f947b --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/24.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i16)))) + (type (;1;) (func (param (ref null $a) i32 i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a + ) + (export "array-atomic-set-i16-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/25.print b/tests/snapshots/local/shared-everything-threads/array.wast/25.print new file mode 100644 index 0000000000..b8e21b1aa8 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/25.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a + ) + (export "array-atomic-set-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/26.print b/tests/snapshots/local/shared-everything-threads/array.wast/26.print new file mode 100644 index 0000000000..6e11fb3a41 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/26.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a + ) + (export "array-atomic-set-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/27.print b/tests/snapshots/local/shared-everything-threads/array.wast/27.print new file mode 100644 index 0000000000..db65bed132 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/27.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut (ref null (shared any)))))) + (type (;1;) (func (param (ref null $a) i32 (ref null (shared any))))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.set seq_cst $a + ) + (export "array-atomic-set-anyref-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/28.print b/tests/snapshots/local/shared-everything-threads/array.wast/28.print new file mode 100644 index 0000000000..941e3f0d84 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/28.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i8)))) + (type (;1;) (func (param (ref null $a) i32 i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a + ) + (export "array-atomic-set-i8-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/29.print b/tests/snapshots/local/shared-everything-threads/array.wast/29.print new file mode 100644 index 0000000000..4e3d7c91cd --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/29.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i16)))) + (type (;1;) (func (param (ref null $a) i32 i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a + ) + (export "array-atomic-set-i16-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/30.print b/tests/snapshots/local/shared-everything-threads/array.wast/30.print new file mode 100644 index 0000000000..17aec3ab82 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/30.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a + ) + (export "array-atomic-set-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/31.print b/tests/snapshots/local/shared-everything-threads/array.wast/31.print new file mode 100644 index 0000000000..43139589ed --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/31.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a + ) + (export "array-atomic-set-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/32.print b/tests/snapshots/local/shared-everything-threads/array.wast/32.print new file mode 100644 index 0000000000..ed1acbaf33 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/32.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut (ref null (shared any)))))) + (type (;1;) (func (param (ref null $a) i32 (ref null (shared any))))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.set acq_rel $a + ) + (export "array-atomic-set-anyref-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/33.print b/tests/snapshots/local/shared-everything-threads/array.wast/33.print new file mode 100644 index 0000000000..d230670ccd --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/33.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add seq_cst $a + ) + (export "array-atomic-rmw.add-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/34.print b/tests/snapshots/local/shared-everything-threads/array.wast/34.print new file mode 100644 index 0000000000..28ea109420 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/34.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add seq_cst $a + ) + (export "array-atomic-rmw.add-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/35.print b/tests/snapshots/local/shared-everything-threads/array.wast/35.print new file mode 100644 index 0000000000..8cc27f279f --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/35.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add acq_rel $a + ) + (export "array-atomic-rmw.add-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/36.print b/tests/snapshots/local/shared-everything-threads/array.wast/36.print new file mode 100644 index 0000000000..3be7d18db2 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/36.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.add acq_rel $a + ) + (export "array-atomic-rmw.add-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/37.print b/tests/snapshots/local/shared-everything-threads/array.wast/37.print new file mode 100644 index 0000000000..36af9d868b --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/37.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub seq_cst $a + ) + (export "array-atomic-rmw.sub-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/38.print b/tests/snapshots/local/shared-everything-threads/array.wast/38.print new file mode 100644 index 0000000000..5bb94f29a1 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/38.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub seq_cst $a + ) + (export "array-atomic-rmw.sub-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/39.print b/tests/snapshots/local/shared-everything-threads/array.wast/39.print new file mode 100644 index 0000000000..0b4badbb86 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/39.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub acq_rel $a + ) + (export "array-atomic-rmw.sub-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/40.print b/tests/snapshots/local/shared-everything-threads/array.wast/40.print new file mode 100644 index 0000000000..ba01772628 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/40.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.sub acq_rel $a + ) + (export "array-atomic-rmw.sub-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/41.print b/tests/snapshots/local/shared-everything-threads/array.wast/41.print new file mode 100644 index 0000000000..5c61030757 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/41.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and seq_cst $a + ) + (export "array-atomic-rmw.and-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/42.print b/tests/snapshots/local/shared-everything-threads/array.wast/42.print new file mode 100644 index 0000000000..2edfd317cf --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/42.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and seq_cst $a + ) + (export "array-atomic-rmw.and-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/43.print b/tests/snapshots/local/shared-everything-threads/array.wast/43.print new file mode 100644 index 0000000000..9a6e89fc15 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/43.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and acq_rel $a + ) + (export "array-atomic-rmw.and-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/44.print b/tests/snapshots/local/shared-everything-threads/array.wast/44.print new file mode 100644 index 0000000000..f82346cddb --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/44.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.and acq_rel $a + ) + (export "array-atomic-rmw.and-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/45.print b/tests/snapshots/local/shared-everything-threads/array.wast/45.print new file mode 100644 index 0000000000..59adfe733b --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/45.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or seq_cst $a + ) + (export "array-atomic-rmw.or-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/46.print b/tests/snapshots/local/shared-everything-threads/array.wast/46.print new file mode 100644 index 0000000000..13dd111893 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/46.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or seq_cst $a + ) + (export "array-atomic-rmw.or-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/47.print b/tests/snapshots/local/shared-everything-threads/array.wast/47.print new file mode 100644 index 0000000000..ffca25c496 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/47.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or acq_rel $a + ) + (export "array-atomic-rmw.or-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/48.print b/tests/snapshots/local/shared-everything-threads/array.wast/48.print new file mode 100644 index 0000000000..94fbc49ce6 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/48.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.or acq_rel $a + ) + (export "array-atomic-rmw.or-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/49.print b/tests/snapshots/local/shared-everything-threads/array.wast/49.print new file mode 100644 index 0000000000..0e6016a7ab --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/49.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor seq_cst $a + ) + (export "array-atomic-rmw.xor-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/50.print b/tests/snapshots/local/shared-everything-threads/array.wast/50.print new file mode 100644 index 0000000000..446233a0e1 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/50.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor seq_cst $a + ) + (export "array-atomic-rmw.xor-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/51.print b/tests/snapshots/local/shared-everything-threads/array.wast/51.print new file mode 100644 index 0000000000..7867026121 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/51.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor acq_rel $a + ) + (export "array-atomic-rmw.xor-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/52.print b/tests/snapshots/local/shared-everything-threads/array.wast/52.print new file mode 100644 index 0000000000..1e8eb5b9ef --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/52.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xor acq_rel $a + ) + (export "array-atomic-rmw.xor-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/53.print b/tests/snapshots/local/shared-everything-threads/array.wast/53.print new file mode 100644 index 0000000000..71e2c7a8c2 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/53.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg seq_cst $a + ) + (export "array-atomic-rmw.xchg-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/54.print b/tests/snapshots/local/shared-everything-threads/array.wast/54.print new file mode 100644 index 0000000000..1770c9685c --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/54.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg seq_cst $a + ) + (export "array-atomic-rmw.xchg-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/55.print b/tests/snapshots/local/shared-everything-threads/array.wast/55.print new file mode 100644 index 0000000000..6ca3ea8062 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/55.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut (ref null (shared any)))))) + (type (;1;) (func (param (ref null $a) i32 (ref null (shared any))) (result (ref null (shared any))))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg seq_cst $a + ) + (export "array-atomic-rmw.xchg-anyref-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/56.print b/tests/snapshots/local/shared-everything-threads/array.wast/56.print new file mode 100644 index 0000000000..2a03171eae --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/56.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg acq_rel $a + ) + (export "array-atomic-rmw.xchg-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/57.print b/tests/snapshots/local/shared-everything-threads/array.wast/57.print new file mode 100644 index 0000000000..618521206b --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/57.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg acq_rel $a + ) + (export "array-atomic-rmw.xchg-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/58.print b/tests/snapshots/local/shared-everything-threads/array.wast/58.print new file mode 100644 index 0000000000..2670aba692 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/58.print @@ -0,0 +1,11 @@ +(module + (type $a (;0;) (shared(array (mut (ref null (shared any)))))) + (type (;1;) (func (param (ref null $a) i32 (ref null (shared any))) (result (ref null (shared any))))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + local.get $z + array.atomic.rmw.xchg acq_rel $a + ) + (export "array-atomic-rmw.xchg-anyref-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/59.print b/tests/snapshots/local/shared-everything-threads/array.wast/59.print new file mode 100644 index 0000000000..9b4ed0eced --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/59.print @@ -0,0 +1,12 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (param $A i32) (result i32) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg seq_cst $a + ) + (export "array-atomic-rmw.cmpxchg-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/60.print b/tests/snapshots/local/shared-everything-threads/array.wast/60.print new file mode 100644 index 0000000000..f485f7a7e3 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/60.print @@ -0,0 +1,12 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (param $A i64) (result i64) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg seq_cst $a + ) + (export "array-atomic-rmw.cmpxchg-i64-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/61.print b/tests/snapshots/local/shared-everything-threads/array.wast/61.print new file mode 100644 index 0000000000..c9be7ed8c2 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/61.print @@ -0,0 +1,12 @@ +(module + (type $a (;0;) (shared(array (mut (ref null (shared eq)))))) + (type (;1;) (func (param (ref null $a) i32 (ref null (shared eq)) (ref null (shared eq))) (result (ref null (shared eq))))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared eq))) (param $A (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg seq_cst $a + ) + (export "array-atomic-rmw.cmpxchg-eqref-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/62.print b/tests/snapshots/local/shared-everything-threads/array.wast/62.print new file mode 100644 index 0000000000..15e7ca30d3 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/62.print @@ -0,0 +1,12 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32 i32 i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i32) (param $A i32) (result i32) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg acq_rel $a + ) + (export "array-atomic-rmw.cmpxchg-i32-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/63.print b/tests/snapshots/local/shared-everything-threads/array.wast/63.print new file mode 100644 index 0000000000..7bce20ede1 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/63.print @@ -0,0 +1,12 @@ +(module + (type $a (;0;) (shared(array (mut i64)))) + (type (;1;) (func (param (ref null $a) i32 i64 i64) (result i64))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z i64) (param $A i64) (result i64) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg acq_rel $a + ) + (export "array-atomic-rmw.cmpxchg-i64-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/64.print b/tests/snapshots/local/shared-everything-threads/array.wast/64.print new file mode 100644 index 0000000000..d61185983f --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/64.print @@ -0,0 +1,12 @@ +(module + (type $a (;0;) (shared(array (mut (ref null (shared eq)))))) + (type (;1;) (func (param (ref null $a) i32 (ref null (shared eq)) (ref null (shared eq))) (result (ref null (shared eq))))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (param $z (ref null (shared eq))) (param $A (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + local.get $A + array.atomic.rmw.cmpxchg acq_rel $a + ) + (export "array-atomic-rmw.cmpxchg-eqref-acq_rel" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/array.wast/9.print b/tests/snapshots/local/shared-everything-threads/array.wast/9.print new file mode 100644 index 0000000000..f3d308bd71 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/array.wast/9.print @@ -0,0 +1,10 @@ +(module + (type $a (;0;) (shared(array (mut i32)))) + (type (;1;) (func (param (ref null $a) i32) (result i32))) + (func (;0;) (type 1) (param $x (ref null $a)) (param $y i32) (result i32) + local.get $x + local.get $y + array.atomic.get seq_cst $a + ) + (export "array-atomic-get-i32-seq_cst" (func 0)) +) diff --git a/tests/snapshots/local/shared-everything-threads/struct.wast.json b/tests/snapshots/local/shared-everything-threads/struct.wast.json index 97900a0eba..106b265ca1 100644 --- a/tests/snapshots/local/shared-everything-threads/struct.wast.json +++ b/tests/snapshots/local/shared-everything-threads/struct.wast.json @@ -55,6 +55,102 @@ "type": "module", "line": 72, "filename": "struct.8.wasm" + }, + { + "type": "module", + "line": 95, + "filename": "struct.9.wasm" + }, + { + "type": "assert_invalid", + "line": 322, + "filename": "struct.10.wasm", + "text": "non-packed storage type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 331, + "filename": "struct.11.wasm", + "text": "non-packed storage types", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 340, + "filename": "struct.12.wasm", + "text": "non-packed storage types", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 349, + "filename": "struct.13.wasm", + "text": "non-packed storage types", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 358, + "filename": "struct.14.wasm", + "text": "non-packed storage types", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 367, + "filename": "struct.15.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 377, + "filename": "struct.16.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 387, + "filename": "struct.17.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 397, + "filename": "struct.18.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 407, + "filename": "struct.19.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 417, + "filename": "struct.20.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 427, + "filename": "struct.21.wasm", + "text": "invalid type", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 438, + "filename": "struct.22.wasm", + "text": "invalid type", + "module_type": "binary" } ] } \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/struct.wast/9.print b/tests/snapshots/local/shared-everything-threads/struct.wast/9.print new file mode 100644 index 0000000000..7987e5e408 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/struct.wast/9.print @@ -0,0 +1,343 @@ +(module + (type $s (;0;) (shared(struct (field $i8 (mut i8)) (field $i16 (mut i16)) (field $i32 (mut i32)) (field $i64 (mut i64)) (field $anyref (mut (ref null (shared any)))) (field $eqref (mut (ref null (shared eq))))))) + (type (;1;) (func (param (ref null $s)) (result i32))) + (type (;2;) (func (param (ref null $s)) (result i64))) + (type (;3;) (func (param (ref null $s)) (result (ref null (shared any))))) + (type (;4;) (func (param (ref null $s) i32))) + (type (;5;) (func (param (ref null $s) i64))) + (type (;6;) (func (param (ref null $s) (ref null (shared any))))) + (type (;7;) (func (param (ref null $s) i32) (result i32))) + (type (;8;) (func (param (ref null $s) i64) (result i64))) + (type (;9;) (func (param (ref null $s) (ref null (shared any))) (result (ref null (shared any))))) + (type (;10;) (func (param (ref null $s) i32 i32) (result i32))) + (type (;11;) (func (param (ref null $s) i64 i64) (result i64))) + (type (;12;) (func (param (ref null $s) (ref null (shared eq)) (ref null (shared eq))) (result (ref null (shared eq))))) + (func (;0;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get seq_cst $s $i32 + ) + (func (;1;) (type 2) (param $x (ref null $s)) (result i64) + local.get $x + struct.atomic.get seq_cst $s $i64 + ) + (func (;2;) (type 3) (param $x (ref null $s)) (result (ref null (shared any))) + local.get $x + struct.atomic.get seq_cst $s $anyref + ) + (func (;3;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get acq_rel $s $i32 + ) + (func (;4;) (type 2) (param $x (ref null $s)) (result i64) + local.get $x + struct.atomic.get acq_rel $s $i64 + ) + (func (;5;) (type 3) (param $x (ref null $s)) (result (ref null (shared any))) + local.get $x + struct.atomic.get acq_rel $s $anyref + ) + (func (;6;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s seq_cst $s $i8 + ) + (func (;7;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s seq_cst $s $i16 + ) + (func (;8;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s acq_rel $s $i8 + ) + (func (;9;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_s acq_rel $s $i16 + ) + (func (;10;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u seq_cst $s $i8 + ) + (func (;11;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u seq_cst $s $i16 + ) + (func (;12;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u acq_rel $s $i8 + ) + (func (;13;) (type 1) (param $x (ref null $s)) (result i32) + local.get $x + struct.atomic.get_u acq_rel $s $i16 + ) + (func (;14;) (type 4) (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $i8 + ) + (func (;15;) (type 4) (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $i16 + ) + (func (;16;) (type 4) (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $i32 + ) + (func (;17;) (type 5) (param $x (ref null $s)) (param $y i64) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $i64 + ) + (func (;18;) (type 6) (param $x (ref null $s)) (param $y (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.set seq_cst $s $anyref + ) + (func (;19;) (type 4) (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $i8 + ) + (func (;20;) (type 4) (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $i16 + ) + (func (;21;) (type 4) (param $x (ref null $s)) (param $y i32) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $i32 + ) + (func (;22;) (type 5) (param $x (ref null $s)) (param $y i64) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $i64 + ) + (func (;23;) (type 6) (param $x (ref null $s)) (param $y (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.set acq_rel $s $anyref + ) + (func (;24;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.add seq_cst $s $i32 + ) + (func (;25;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.add seq_cst $s $i64 + ) + (func (;26;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.add acq_rel $s $i32 + ) + (func (;27;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.add acq_rel $s $i64 + ) + (func (;28;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.sub seq_cst $s $i32 + ) + (func (;29;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.sub seq_cst $s $i64 + ) + (func (;30;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.sub acq_rel $s $i32 + ) + (func (;31;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.sub acq_rel $s $i64 + ) + (func (;32;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.and seq_cst $s $i32 + ) + (func (;33;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.and seq_cst $s $i64 + ) + (func (;34;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.and acq_rel $s $i32 + ) + (func (;35;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.and acq_rel $s $i64 + ) + (func (;36;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.or seq_cst $s $i32 + ) + (func (;37;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.or seq_cst $s $i64 + ) + (func (;38;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.or acq_rel $s $i32 + ) + (func (;39;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.or acq_rel $s $i64 + ) + (func (;40;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xor seq_cst $s $i32 + ) + (func (;41;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xor seq_cst $s $i64 + ) + (func (;42;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xor acq_rel $s $i32 + ) + (func (;43;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xor acq_rel $s $i64 + ) + (func (;44;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xchg seq_cst $s $i32 + ) + (func (;45;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xchg seq_cst $s $i64 + ) + (func (;46;) (type 9) (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.xchg seq_cst $s $anyref + ) + (func (;47;) (type 7) (param $x (ref null $s)) (param $y i32) (result i32) + local.get $x + local.get $y + struct.atomic.rmw.xchg acq_rel $s $i32 + ) + (func (;48;) (type 8) (param $x (ref null $s)) (param $y i64) (result i64) + local.get $x + local.get $y + struct.atomic.rmw.xchg acq_rel $s $i64 + ) + (func (;49;) (type 9) (param $x (ref null $s)) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + struct.atomic.rmw.xchg acq_rel $s $anyref + ) + (func (;50;) (type 10) (param $x (ref null $s)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seq_cst $s $i32 + ) + (func (;51;) (type 11) (param $x (ref null $s)) (param $y i64) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seq_cst $s $i64 + ) + (func (;52;) (type 12) (param $x (ref null $s)) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg seq_cst $s $eqref + ) + (func (;53;) (type 10) (param $x (ref null $s)) (param $y i32) (param $z i32) (result i32) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acq_rel $s $i32 + ) + (func (;54;) (type 11) (param $x (ref null $s)) (param $y i64) (param $z i64) (result i64) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acq_rel $s $i64 + ) + (func (;55;) (type 12) (param $x (ref null $s)) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + struct.atomic.rmw.cmpxchg acq_rel $s $eqref + ) + (export "struct-atomic-get-i32-seq_cst" (func 0)) + (export "struct-atomic-get-i64-seq_cst" (func 1)) + (export "struct-atomic-get-anyref-seq_cst" (func 2)) + (export "struct-atomic-get-i32-acq_rel" (func 3)) + (export "struct-atomic-get-i64-acq_rel" (func 4)) + (export "struct-atomic-get-anyref-acq_rel" (func 5)) + (export "struct-atomic-get_s-i8-seq_cst" (func 6)) + (export "struct-atomic-get_s-i16-seq_cst" (func 7)) + (export "struct-atomic-get_s-i8-acq_rel" (func 8)) + (export "struct-atomic-get_s-i16-acq_rel" (func 9)) + (export "struct-atomic-get_u-i8-seq_cst" (func 10)) + (export "struct-atomic-get_u-i16-seq_cst" (func 11)) + (export "struct-atomic-get_u-i8-acq_rel" (func 12)) + (export "struct-atomic-get_u-i16-acq_rel" (func 13)) + (export "struct-atomic-set-i8-seq_cst" (func 14)) + (export "struct-atomic-set-i16-seq_cst" (func 15)) + (export "struct-atomic-set-i32-seq_cst" (func 16)) + (export "struct-atomic-set-i64-seq_cst" (func 17)) + (export "struct-atomic-set-anyref-seq_cst" (func 18)) + (export "struct-atomic-set-i8-acq_rel" (func 19)) + (export "struct-atomic-set-i16-acq_rel" (func 20)) + (export "struct-atomic-set-i32-acq_rel" (func 21)) + (export "struct-atomic-set-i64-acq_rel" (func 22)) + (export "struct-atomic-set-anyref-acq_rel" (func 23)) + (export "struct-atomic-rmw.add-i32-seq_cst" (func 24)) + (export "struct-atomic-rmw.add-i64-seq_cst" (func 25)) + (export "struct-atomic-rmw.add-i32-acq_rel" (func 26)) + (export "struct-atomic-rmw.add-i64-acq_rel" (func 27)) + (export "struct-atomic-rmw.sub-i32-seq_cst" (func 28)) + (export "struct-atomic-rmw.sub-i64-seq_cst" (func 29)) + (export "struct-atomic-rmw.sub-i32-acq_rel" (func 30)) + (export "struct-atomic-rmw.sub-i64-acq_rel" (func 31)) + (export "struct-atomic-rmw.and-i32-seq_cst" (func 32)) + (export "struct-atomic-rmw.and-i64-seq_cst" (func 33)) + (export "struct-atomic-rmw.and-i32-acq_rel" (func 34)) + (export "struct-atomic-rmw.and-i64-acq_rel" (func 35)) + (export "struct-atomic-rmw.or-i32-seq_cst" (func 36)) + (export "struct-atomic-rmw.or-i64-seq_cst" (func 37)) + (export "struct-atomic-rmw.or-i32-acq_rel" (func 38)) + (export "struct-atomic-rmw.or-i64-acq_rel" (func 39)) + (export "struct-atomic-rmw.xor-i32-seq_cst" (func 40)) + (export "struct-atomic-rmw.xor-i64-seq_cst" (func 41)) + (export "struct-atomic-rmw.xor-i32-acq_rel" (func 42)) + (export "struct-atomic-rmw.xor-i64-acq_rel" (func 43)) + (export "struct-atomic-rmw.xchg-i32-seq_cst" (func 44)) + (export "struct-atomic-rmw.xchg-i64-seq_cst" (func 45)) + (export "struct-atomic-rmw.xchg-anyref-seq_cst" (func 46)) + (export "struct-atomic-rmw.xchg-i32-acq_rel" (func 47)) + (export "struct-atomic-rmw.xchg-i64-acq_rel" (func 48)) + (export "struct-atomic-rmw.xchg-anyref-acq_rel" (func 49)) + (export "struct-atomic-rmw.cmpxchg-i32-seq_cst" (func 50)) + (export "struct-atomic-rmw.cmpxchg-i64-seq_cst" (func 51)) + (export "struct-atomic-rmw.cmpxchg-eqref-seq_cst" (func 52)) + (export "struct-atomic-rmw.cmpxchg-i32-acq_rel" (func 53)) + (export "struct-atomic-rmw.cmpxchg-i64-acq_rel" (func 54)) + (export "struct-atomic-rmw.cmpxchg-eqref-acq_rel" (func 55)) +) diff --git a/tests/snapshots/local/shared-everything-threads/table.wast.json b/tests/snapshots/local/shared-everything-threads/table.wast.json index 8c2bc6b10f..13a852f325 100644 --- a/tests/snapshots/local/shared-everything-threads/table.wast.json +++ b/tests/snapshots/local/shared-everything-threads/table.wast.json @@ -45,6 +45,23 @@ "filename": "table.6.wasm", "text": "shared tables must have a shared element type", "module_type": "binary" + }, + { + "type": "module", + "line": 46, + "filename": "table.7.wasm" + }, + { + "type": "module", + "line": 115, + "filename": "table.8.wasm" + }, + { + "type": "assert_invalid", + "line": 167, + "filename": "table.9.wasm", + "text": "invalid type", + "module_type": "binary" } ] } \ No newline at end of file diff --git a/tests/snapshots/local/shared-everything-threads/table.wast/7.print b/tests/snapshots/local/shared-everything-threads/table.wast/7.print new file mode 100644 index 0000000000..033089a8b9 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/table.wast/7.print @@ -0,0 +1,104 @@ +(module + (type (;0;) (func (param i32) (result (ref null (shared eq))))) + (type (;1;) (func (param i32 (ref null (shared eq))))) + (type (;2;) (func (param i32 (ref null (shared eq))) (result (ref null (shared eq))))) + (type (;3;) (func (param i32 (ref null (shared eq)) (ref null (shared eq))) (result (ref null (shared eq))))) + (import "spectest" "table_eq" (table $a (;0;) shared 1 (ref null (shared eq)))) + (func (;0;) (type 0) (param $x i32) (result (ref null (shared eq))) + local.get $x + table.atomic.get seq_cst $a + ) + (func (;1;) (type 0) (param $x i32) (result (ref null (shared eq))) + local.get $x + table.atomic.get seq_cst $b + ) + (func (;2;) (type 0) (param $x i32) (result (ref null (shared eq))) + local.get $x + table.atomic.get acq_rel $a + ) + (func (;3;) (type 0) (param $x i32) (result (ref null (shared eq))) + local.get $x + table.atomic.get acq_rel $b + ) + (func (;4;) (type 1) (param $x i32) (param $y (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.set seq_cst $a + ) + (func (;5;) (type 1) (param $x i32) (param $y (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.set seq_cst $b + ) + (func (;6;) (type 1) (param $x i32) (param $y (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.set acq_rel $a + ) + (func (;7;) (type 1) (param $x i32) (param $y (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.set acq_rel $b + ) + (func (;8;) (type 2) (param $x i32) (param $y (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.rmw.xchg seq_cst $a + ) + (func (;9;) (type 2) (param $x i32) (param $y (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.rmw.xchg seq_cst $b + ) + (func (;10;) (type 2) (param $x i32) (param $y (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.rmw.xchg acq_rel $a + ) + (func (;11;) (type 2) (param $x i32) (param $y (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + table.atomic.rmw.xchg acq_rel $b + ) + (func (;12;) (type 3) (param $x i32) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg seq_cst $a + ) + (func (;13;) (type 3) (param $x i32) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg seq_cst $b + ) + (func (;14;) (type 3) (param $x i32) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg acq_rel $a + ) + (func (;15;) (type 3) (param $x i32) (param $y (ref null (shared eq))) (param $z (ref null (shared eq))) (result (ref null (shared eq))) + local.get $x + local.get $y + local.get $z + table.atomic.rmw.cmpxchg acq_rel $b + ) + (table $b (;1;) shared 1 (ref null (shared eq))) + (export "table-atomic-get-eq-seq_cst-$a" (func 0)) + (export "table-atomic-get-eq-seq_cst-$b" (func 1)) + (export "table-atomic-get-eq-acq_rel-$a" (func 2)) + (export "table-atomic-get-eq-acq_rel-$b" (func 3)) + (export "table-atomic-set-eq-seq_cst-$a" (func 4)) + (export "table-atomic-set-eq-seq_cst-$b" (func 5)) + (export "table-atomic-set-eq-acq_rel-$a" (func 6)) + (export "table-atomic-set-eq-acq_rel-$b" (func 7)) + (export "table-atomic-rmw.xchg-eq-seq_cst-$a" (func 8)) + (export "table-atomic-rmw.xchg-eq-seq_cst-$b" (func 9)) + (export "table-atomic-rmw.xchg-eq-acq_rel-$a" (func 10)) + (export "table-atomic-rmw.xchg-eq-acq_rel-$b" (func 11)) + (export "table-atomic-rmw.cmpxchg-eq-seq_cst-$a" (func 12)) + (export "table-atomic-rmw.cmpxchg-eq-seq_cst-$b" (func 13)) + (export "table-atomic-rmw.cmpxchg-eq-acq_rel-$a" (func 14)) + (export "table-atomic-rmw.cmpxchg-eq-acq_rel-$b" (func 15)) +) diff --git a/tests/snapshots/local/shared-everything-threads/table.wast/8.print b/tests/snapshots/local/shared-everything-threads/table.wast/8.print new file mode 100644 index 0000000000..e921f4e848 --- /dev/null +++ b/tests/snapshots/local/shared-everything-threads/table.wast/8.print @@ -0,0 +1,75 @@ +(module + (type (;0;) (func (param i32) (result (ref null (shared any))))) + (type (;1;) (func (param i32 (ref null (shared any))))) + (type (;2;) (func (param i32 (ref null (shared any))) (result (ref null (shared any))))) + (import "spectest" "table_any" (table $a (;0;) shared 1 (ref null (shared any)))) + (func (;0;) (type 0) (param $x i32) (result (ref null (shared any))) + local.get $x + table.atomic.get seq_cst $a + ) + (func (;1;) (type 0) (param $x i32) (result (ref null (shared any))) + local.get $x + table.atomic.get seq_cst $b + ) + (func (;2;) (type 0) (param $x i32) (result (ref null (shared any))) + local.get $x + table.atomic.get acq_rel $a + ) + (func (;3;) (type 0) (param $x i32) (result (ref null (shared any))) + local.get $x + table.atomic.get acq_rel $b + ) + (func (;4;) (type 1) (param $x i32) (param $y (ref null (shared any))) + local.get $x + local.get $y + table.atomic.set seq_cst $a + ) + (func (;5;) (type 1) (param $x i32) (param $y (ref null (shared any))) + local.get $x + local.get $y + table.atomic.set seq_cst $b + ) + (func (;6;) (type 1) (param $x i32) (param $y (ref null (shared any))) + local.get $x + local.get $y + table.atomic.set acq_rel $a + ) + (func (;7;) (type 1) (param $x i32) (param $y (ref null (shared any))) + local.get $x + local.get $y + table.atomic.set acq_rel $b + ) + (func (;8;) (type 2) (param $x i32) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + table.atomic.rmw.xchg seq_cst $a + ) + (func (;9;) (type 2) (param $x i32) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + table.atomic.rmw.xchg seq_cst $b + ) + (func (;10;) (type 2) (param $x i32) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + table.atomic.rmw.xchg acq_rel $a + ) + (func (;11;) (type 2) (param $x i32) (param $y (ref null (shared any))) (result (ref null (shared any))) + local.get $x + local.get $y + table.atomic.rmw.xchg acq_rel $b + ) + (table $b (;1;) shared 1 (ref null (shared any))) + (export "table-atomic-get-any-seq_cst-$a" (func 0)) + (export "table-atomic-get-any-seq_cst-$b" (func 1)) + (export "table-atomic-get-any-acq_rel-$a" (func 2)) + (export "table-atomic-get-any-acq_rel-$b" (func 3)) + (export "table-atomic-set-any-seq_cst-$a" (func 4)) + (export "table-atomic-set-any-seq_cst-$b" (func 5)) + (export "table-atomic-set-any-acq_rel-$a" (func 6)) + (export "table-atomic-set-any-acq_rel-$b" (func 7)) + (export "table-atomic-rmw.xchg-any-seq_cst-$a" (func 8)) + (export "table-atomic-rmw.xchg-any-seq_cst-$b" (func 9)) + (export "table-atomic-rmw.xchg-any-acq_rel-$a" (func 10)) + (export "table-atomic-rmw.xchg-any-acq_rel-$b" (func 11)) +) From 1cdafa768a961ad489d901d984c779fa67e0d0d1 Mon Sep 17 00:00:00 2001 From: Keith Winstein <208955+keithw@users.noreply.github.com> Date: Mon, 15 Jul 2024 07:46:12 -0700 Subject: [PATCH 58/58] Add unfolded versions of the legacy (v3) exception-handling tests (#1672) --- .../legacy-exceptions.wat | 0 tests/local/legacy-exceptions/rethrow.wast | 124 ++++ tests/local/legacy-exceptions/throw.wast | 56 ++ tests/local/legacy-exceptions/try_catch.wast | 309 +++++++++ .../local/legacy-exceptions/try_delegate.wast | 275 ++++++++ tests/roundtrip.rs | 2 +- .../legacy-exceptions.wat.print | 0 .../local/legacy-exceptions/rethrow.wast.json | 214 +++++++ .../legacy-exceptions/rethrow.wast/0.print | 98 +++ .../local/legacy-exceptions/throw.wast.json | 131 ++++ .../legacy-exceptions/throw.wast/0.print | 62 ++ .../legacy-exceptions/try_catch.wast.json | 599 ++++++++++++++++++ .../legacy-exceptions/try_catch.wast/0.print | 9 + .../legacy-exceptions/try_catch.wast/2.print | 229 +++++++ .../legacy-exceptions/try_catch.wast/33.print | 19 + .../legacy-exceptions/try_delegate.wast.json | 325 ++++++++++ .../try_delegate.wast/0.print | 227 +++++++ 17 files changed, 2678 insertions(+), 1 deletion(-) rename tests/local/{ => legacy-exceptions}/legacy-exceptions.wat (100%) create mode 100644 tests/local/legacy-exceptions/rethrow.wast create mode 100644 tests/local/legacy-exceptions/throw.wast create mode 100644 tests/local/legacy-exceptions/try_catch.wast create mode 100644 tests/local/legacy-exceptions/try_delegate.wast rename tests/snapshots/local/{ => legacy-exceptions}/legacy-exceptions.wat.print (100%) create mode 100644 tests/snapshots/local/legacy-exceptions/rethrow.wast.json create mode 100644 tests/snapshots/local/legacy-exceptions/rethrow.wast/0.print create mode 100644 tests/snapshots/local/legacy-exceptions/throw.wast.json create mode 100644 tests/snapshots/local/legacy-exceptions/throw.wast/0.print create mode 100644 tests/snapshots/local/legacy-exceptions/try_catch.wast.json create mode 100644 tests/snapshots/local/legacy-exceptions/try_catch.wast/0.print create mode 100644 tests/snapshots/local/legacy-exceptions/try_catch.wast/2.print create mode 100644 tests/snapshots/local/legacy-exceptions/try_catch.wast/33.print create mode 100644 tests/snapshots/local/legacy-exceptions/try_delegate.wast.json create mode 100644 tests/snapshots/local/legacy-exceptions/try_delegate.wast/0.print diff --git a/tests/local/legacy-exceptions.wat b/tests/local/legacy-exceptions/legacy-exceptions.wat similarity index 100% rename from tests/local/legacy-exceptions.wat rename to tests/local/legacy-exceptions/legacy-exceptions.wat diff --git a/tests/local/legacy-exceptions/rethrow.wast b/tests/local/legacy-exceptions/rethrow.wast new file mode 100644 index 0000000000..5a7b320c5d --- /dev/null +++ b/tests/local/legacy-exceptions/rethrow.wast @@ -0,0 +1,124 @@ +;; Test rethrow instruction. + +(module + (tag $e0) + (tag $e1) + + (func (export "catch-rethrow-0") + try + throw $e0 + catch $e0 + rethrow 0 + end + ) + + (func (export "catch-rethrow-1") (param i32) (result i32) + try (result i32) + throw $e0 + catch $e0 + local.get 0 + i32.eqz + if + rethrow 1 + end + i32.const 23 + end + ) + + (func (export "catchall-rethrow-0") + try + throw $e0 + catch_all + rethrow 0 + end + ) + + (func (export "catchall-rethrow-1") (param i32) (result i32) + try (result i32) + throw $e0 + catch_all + local.get 0 + i32.eqz + if + rethrow 1 + end + i32.const 23 + end + ) + + (func (export "rethrow-nested") (param i32) (result i32) + try (result i32) + throw $e1 + catch $e1 + try (result i32) + throw $e0 + catch $e0 + local.get 0 + i32.const 0 + i32.eq + if + rethrow 1 + end + local.get 0 + i32.const 1 + i32.eq + if + rethrow 2 + end + i32.const 23 + end + end + ) + + (func (export "rethrow-recatch") (param i32) (result i32) + try (result i32) + throw $e0 + catch $e0 + try (result i32) + local.get 0 + i32.eqz + if + rethrow 2 + end + i32.const 42 + catch $e0 + i32.const 23 + end + end + ) + + (func (export "rethrow-stack-polymorphism") + try + throw $e0 + catch $e0 + i32.const 1 + rethrow 0 + end + ) +) + +(assert_exception (invoke "catch-rethrow-0")) + +(assert_exception (invoke "catch-rethrow-1" (i32.const 0))) +(assert_return (invoke "catch-rethrow-1" (i32.const 1)) (i32.const 23)) + +(assert_exception (invoke "catchall-rethrow-0")) + +(assert_exception (invoke "catchall-rethrow-1" (i32.const 0))) +(assert_return (invoke "catchall-rethrow-1" (i32.const 1)) (i32.const 23)) +(assert_exception (invoke "rethrow-nested" (i32.const 0))) +(assert_exception (invoke "rethrow-nested" (i32.const 1))) +(assert_return (invoke "rethrow-nested" (i32.const 2)) (i32.const 23)) + +(assert_return (invoke "rethrow-recatch" (i32.const 0)) (i32.const 23)) +(assert_return (invoke "rethrow-recatch" (i32.const 1)) (i32.const 42)) + +(assert_exception (invoke "rethrow-stack-polymorphism")) + +(assert_invalid (module (func (rethrow 0))) "invalid rethrow label") +(assert_invalid (module (func (block (rethrow 0)))) "invalid rethrow label") +(assert_invalid (module (func + try + rethrow 0 + delegate 0)) + "invalid rethrow label") diff --git a/tests/local/legacy-exceptions/throw.wast b/tests/local/legacy-exceptions/throw.wast new file mode 100644 index 0000000000..2ab9f2f330 --- /dev/null +++ b/tests/local/legacy-exceptions/throw.wast @@ -0,0 +1,56 @@ +;; Test throw instruction. + +(module + (tag $e0) + (tag $e-i32 (param i32)) + (tag $e-f32 (param f32)) + (tag $e-i64 (param i64)) + (tag $e-f64 (param f64)) + (tag $e-i32-i32 (param i32 i32)) + + (func $throw-if (export "throw-if") (param i32) (result i32) + (local.get 0) + (i32.const 0) (if (i32.ne) (then (throw $e0))) + (i32.const 0) + ) + + (func (export "throw-param-f32") (param f32) (local.get 0) (throw $e-f32)) + + (func (export "throw-param-i64") (param i64) (local.get 0) (throw $e-i64)) + + (func (export "throw-param-f64") (param f64) (local.get 0) (throw $e-f64)) + + (func $throw-1-2 (i32.const 1) (i32.const 2) (throw $e-i32-i32)) + (func (export "test-throw-1-2") + try + call $throw-1-2 + catch $e-i32-i32 + i32.const 2 + i32.ne + if + unreachable + end + i32.const 1 + i32.ne + if + unreachable + end + end + ) +) + +(assert_return (invoke "throw-if" (i32.const 0)) (i32.const 0)) +(assert_exception (invoke "throw-if" (i32.const 10))) +(assert_exception (invoke "throw-if" (i32.const -1))) + +(assert_exception (invoke "throw-param-f32" (f32.const 5.0))) +(assert_exception (invoke "throw-param-i64" (i64.const 5))) +(assert_exception (invoke "throw-param-f64" (f64.const 5.0))) + +(assert_return (invoke "test-throw-1-2")) + +(assert_invalid (module (func (throw 0))) "unknown tag 0") +(assert_invalid (module (tag (param i32)) (func (throw 0))) + "type mismatch: instruction requires [i32] but stack has []") +(assert_invalid (module (tag (param i32)) (func (i64.const 5) (throw 0))) + "type mismatch: instruction requires [i32] but stack has [i64]") diff --git a/tests/local/legacy-exceptions/try_catch.wast b/tests/local/legacy-exceptions/try_catch.wast new file mode 100644 index 0000000000..8819e82487 --- /dev/null +++ b/tests/local/legacy-exceptions/try_catch.wast @@ -0,0 +1,309 @@ +;; Test try-catch blocks. + +(module + (tag $e0 (export "e0")) + (func (export "throw") (throw $e0)) +) + +(register "test") + +(module + (tag $imported-e0 (import "test" "e0")) + (func $imported-throw (import "test" "throw")) + (tag $e0) + (tag $e1) + (tag $e2) + (tag $e-i32 (param i32)) + (tag $e-f32 (param f32)) + (tag $e-i64 (param i64)) + (tag $e-f64 (param f64)) + + (func $throw-if (param i32) (result i32) + (local.get 0) + (i32.const 0) (if (i32.ne) (then (throw $e0))) + (i32.const 0) + ) + + (func (export "empty-catch") + try + catch $e0 + end + ) + + (func (export "simple-throw-catch") (param i32) (result i32) + try (result i32) + local.get 0 + i32.eqz + if + throw 1 + end + i32.const 42 + catch 1 + i32.const 23 + end + ) + + (func (export "unreachable-not-caught") try unreachable catch_all end) + + (func $div (param i32 i32) (result i32) + (local.get 0) (local.get 1) (i32.div_u) + ) + (func (export "trap-in-callee") (param i32 i32) (result i32) + try (result i32) + local.get 0 + local.get 1 + call 5 + catch_all + i32.const 11 + end + ) + + (func (export "catch-complex-1") (param i32) (result i32) + try (result i32) + try (result i32) + local.get 0 + i32.eqz + if + throw 1 + else + local.get 0 + i32.const 1 + i32.eq + if + throw 2 + else + throw 3 + end + end + i32.const 2 + catch 1 + i32.const 3 + end + catch 2 + i32.const 4 + end + ) + + (func (export "catch-complex-2") (param i32) (result i32) + try (result i32) + local.get 0 + i32.eqz + if + throw 1 + else + local.get 0 + i32.const 1 + i32.eq + if + throw 2 + else + throw 3 + end + end + i32.const 2 + catch 1 + i32.const 3 + catch 2 + i32.const 4 + end + ) + + (func (export "throw-catch-param-i32") (param i32) (result i32) + try (result i32) + local.get 0 + throw 4 + i32.const 2 + catch 4 + return + end + ) + + (func (export "throw-catch-param-f32") (param f32) (result f32) + try (result f32) + local.get 0 + throw 5 + f32.const 0 + catch 5 + return + end + ) + + (func (export "throw-catch-param-i64") (param i64) (result i64) + try (result i64) + local.get 0 + throw 6 + i64.const 2 + catch 6 + return + end + ) + + (func (export "throw-catch-param-f64") (param f64) (result f64) + try (result f64) + local.get 0 + throw 7 + f64.const 0 + catch 7 + return + end + ) + + (func $throw-param-i32 (param i32) (local.get 0) (throw $e-i32)) + (func (export "catch-param-i32") (param i32) (result i32) + try (result i32) + i32.const 0 + local.get 0 + call 13 + catch 4 + end + ) + + (func (export "catch-imported") (result i32) + try (result i32) + i32.const 1 + call 0 + catch 0 + i32.const 2 + end + ) + + (func (export "catchless-try") (param i32) (result i32) + try (result i32) + try (result i32) + local.get 0 + call 1 + end + catch 1 + i32.const 1 + end + ) + + (func $throw-void (throw $e0)) + (func (export "return-call-in-try-catch") + try + return_call 17 + catch 1 + end + ) + + (table funcref (elem $throw-void)) + (func (export "return-call-indirect-in-try-catch") + try + i32.const 0 + return_call_indirect (type 0) + catch 1 + end + ) + + (func (export "break-try-catch") + try + br 0 + catch 1 + end + ) + + (func (export "break-try-catch_all") + try + br 0 + catch_all + end + ) +) + +(assert_return (invoke "empty-catch")) + +(assert_return (invoke "simple-throw-catch" (i32.const 0)) (i32.const 23)) +(assert_return (invoke "simple-throw-catch" (i32.const 1)) (i32.const 42)) + +(assert_trap (invoke "unreachable-not-caught") "unreachable") + +(assert_return (invoke "trap-in-callee" (i32.const 7) (i32.const 2)) (i32.const 3)) +(assert_trap (invoke "trap-in-callee" (i32.const 1) (i32.const 0)) "integer divide by zero") + +(assert_return (invoke "catch-complex-1" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "catch-complex-1" (i32.const 1)) (i32.const 4)) +(assert_exception (invoke "catch-complex-1" (i32.const 2))) + +(assert_return (invoke "catch-complex-2" (i32.const 0)) (i32.const 3)) +(assert_return (invoke "catch-complex-2" (i32.const 1)) (i32.const 4)) +(assert_exception (invoke "catch-complex-2" (i32.const 2))) + +(assert_return (invoke "throw-catch-param-i32" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "throw-catch-param-i32" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "throw-catch-param-i32" (i32.const 10)) (i32.const 10)) + +(assert_return (invoke "throw-catch-param-f32" (f32.const 5.0)) (f32.const 5.0)) +(assert_return (invoke "throw-catch-param-f32" (f32.const 10.5)) (f32.const 10.5)) + +(assert_return (invoke "throw-catch-param-i64" (i64.const 5)) (i64.const 5)) +(assert_return (invoke "throw-catch-param-i64" (i64.const 0)) (i64.const 0)) +(assert_return (invoke "throw-catch-param-i64" (i64.const -1)) (i64.const -1)) + +(assert_return (invoke "throw-catch-param-f64" (f64.const 5.0)) (f64.const 5.0)) +(assert_return (invoke "throw-catch-param-f64" (f64.const 10.5)) (f64.const 10.5)) + +(assert_return (invoke "catch-param-i32" (i32.const 5)) (i32.const 5)) + +(assert_return (invoke "catch-imported") (i32.const 2)) + +(assert_return (invoke "catchless-try" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "catchless-try" (i32.const 1)) (i32.const 1)) + +(assert_exception (invoke "return-call-in-try-catch")) +(assert_exception (invoke "return-call-indirect-in-try-catch")) + +(assert_return (invoke "break-try-catch")) +(assert_return (invoke "break-try-catch_all")) + +(module + (func $imported-throw (import "test" "throw")) + (tag $e0) + + (func (export "imported-mismatch") (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + i32.const 1 + call 0 + catch 0 + i32.const 2 + end + catch_all + i32.const 3 + end + ) +) + +(assert_return (invoke "imported-mismatch") (i32.const 3)) + +;; Ignore per https://github.com/bytecodealliance/wasm-tools/issues/1671 +;; +;; (assert_malformed +;; (module quote "(module (func (catch_all)))") +;; "unexpected token" +;; ) + +;; (assert_malformed +;; (module quote "(module (tag $e) (func (catch $e)))") +;; "unexpected token" +;; ) + +;; (assert_malformed +;; (module quote +;; "(module (func try catch_all catch_all end))" +;; ) +;; "unexpected token" +;; ) + +(assert_invalid (module (func (result i32) try (result i32) end)) + "type mismatch: instruction requires [i32] but stack has []") +(assert_invalid (module (func (result i32) try (result i32) i64.const 42 end)) + "type mismatch: instruction requires [i32] but stack has [i64]") +(assert_invalid (module (tag) (func try catch 0 i32.const 42 end)) + "type mismatch: block requires [] but stack has [i32]") +(assert_invalid (module + (tag (param i64)) + (func (result i32) + try (result i32) i32.const 42 catch 0 end)) + "type mismatch: instruction requires [i32] but stack has [i64]") +(assert_invalid (module (func try catch_all i32.const 32 end)) + "type mismatch: block requires [] but stack has [i32]") diff --git a/tests/local/legacy-exceptions/try_delegate.wast b/tests/local/legacy-exceptions/try_delegate.wast new file mode 100644 index 0000000000..8f115268d9 --- /dev/null +++ b/tests/local/legacy-exceptions/try_delegate.wast @@ -0,0 +1,275 @@ +;; Test try-delegate blocks. + +(module + (tag $e0) + (tag $e1) + + (func (export "delegate-no-throw") (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + i32.const 1 + delegate 0 + catch 0 + i32.const 2 + end + ) + + (func $throw-if (param i32) + (local.get 0) + (if (then (throw $e0)) (else)) + ) + + (func (export "delegate-throw") (param i32) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + local.get 0 + call 1 + i32.const 1 + delegate 0 + catch 0 + i32.const 2 + end + ) + + (func (export "delegate-skip") (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + try (result i32) ;; label = @3 + throw 0 + i32.const 1 + delegate 1 + catch 0 + i32.const 2 + end + catch 0 + i32.const 3 + end + ) + + (func (export "delegate-to-block") (result i32) + try (result i32) ;; label = @1 + block ;; label = @2 + try ;; label = @3 + throw 0 + delegate 0 + end + i32.const 0 + catch_all + i32.const 1 + end + ) + + (func (export "delegate-to-catch") (result i32) + try (result i32) ;; label = @1 + try ;; label = @2 + throw 0 + catch 0 + try ;; label = @3 + rethrow 1 (;@2;) + delegate 0 + end + i32.const 0 + catch_all + i32.const 1 + end + ) + + (func (export "delegate-to-caller-trivial") + try ;; label = @1 + throw 0 + delegate 0) + + (func (export "delegate-to-caller-skipping") + try ;; label = @1 + try ;; label = @2 + throw 0 + delegate 1 + catch_all + end + ) + + (func $select-tag (param i32) + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 0 + br_table 0 (;@3;) 1 (;@2;) 2 (;@1;) + end + return + end + throw 0 + end + throw 1 + ) + + (func (export "delegate-merge") (param i32 i32) (result i32) + try (result i32) ;; label = @1 + local.get 0 + call 8 + try (result i32) ;; label = @2 + local.get 1 + call 8 + i32.const 1 + delegate 0 + catch 0 + i32.const 2 + end + ) + + (func (export "delegate-throw-no-catch") (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + throw 0 + i32.const 1 + delegate 0 + catch 1 + i32.const 2 + end + ) + + (func (export "delegate-correct-targets") (result i32) + try (result i32) ;; label = @1 + try ;; label = @2 + try ;; label = @3 + try ;; label = @4 + try ;; label = @5 + try ;; label = @6 + throw 0 + delegate 1 + catch_all + unreachable + end + delegate 1 + catch_all + unreachable + end + catch_all + try ;; label = @3 + throw 0 + delegate 0 + end + unreachable + catch_all + i32.const 1 + end) + + (func $throw-void (throw $e0)) + (func (export "return-call-in-try-delegate") + try ;; label = @1 + try ;; label = @2 + return_call 12 + delegate 0 + catch 0 + end + ) + + (table funcref (elem $throw-void)) + (func (export "return-call-indirect-in-try-delegate") + try ;; label = @1 + try ;; label = @2 + i32.const 0 + return_call_indirect (type 0) + delegate 0 + catch 0 + end + ) + + (func (export "break-try-delegate") + try ;; label = @1 + br 0 (;@1;) + delegate 0) + + (func (export "break-and-call-throw") (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + block ;; label = @3 + try ;; label = @4 + br 1 (;@3;) + delegate 2 + end + call 12 + i32.const 0 + catch 0 + i32.const 1 + end + catch 0 + i32.const 2 + end + ) + + (func (export "break-and-throw") (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + block ;; label = @3 + try ;; label = @4 + br 1 (;@3;) + delegate 2 + end + throw 0 + i32.const 0 + catch 0 + i32.const 1 + end + catch 0 + i32.const 2 + end + ) +) + +(assert_return (invoke "delegate-no-throw") (i32.const 1)) + +(assert_return (invoke "delegate-throw" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "delegate-throw" (i32.const 1)) (i32.const 2)) + +(assert_exception (invoke "delegate-throw-no-catch")) + +(assert_return (invoke "delegate-merge" (i32.const 1) (i32.const 0)) (i32.const 2)) +(assert_exception (invoke "delegate-merge" (i32.const 2) (i32.const 0))) +(assert_return (invoke "delegate-merge" (i32.const 0) (i32.const 1)) (i32.const 2)) +(assert_exception (invoke "delegate-merge" (i32.const 0) (i32.const 2))) +(assert_return (invoke "delegate-merge" (i32.const 0) (i32.const 0)) (i32.const 1)) + +(assert_return (invoke "delegate-skip") (i32.const 3)) + +(assert_return (invoke "delegate-to-block") (i32.const 1)) +(assert_return (invoke "delegate-to-catch") (i32.const 1)) + +(assert_exception (invoke "delegate-to-caller-trivial")) +(assert_exception (invoke "delegate-to-caller-skipping")) + +(assert_return (invoke "delegate-correct-targets") (i32.const 1)) + +(assert_exception (invoke "return-call-in-try-delegate")) +(assert_exception (invoke "return-call-indirect-in-try-delegate")) + +(assert_return (invoke "break-try-delegate")) + +(assert_return (invoke "break-and-call-throw") (i32.const 1)) +(assert_return (invoke "break-and-throw") (i32.const 1)) + +;; Ignore per https://github.com/bytecodealliance/wasm-tools/issues/1671 +;; +;; (assert_malformed +;; (module quote "(module (func (delegate 0)))") +;; "unexpected token" +;; ) + +;; (assert_malformed +;; (module quote "(module (tag $e) (func try catch $e delegate 0))") +;; "unexpected token" +;; ) + +;; (assert_malformed +;; (module quote "(module (func try catch_all delegate 0))") +;; "unexpected token" +;; ) + +;; (assert_malformed +;; (module quote "(module (func try delegate delegate 0))") +;; "unexpected token" +;; ) + +(assert_invalid + (module (func try delegate 1)) + "unknown label" +) diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 4a8e4f2b6a..b788a81f3f 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -605,7 +605,7 @@ impl TestState { } "simd" => features.insert(WasmFeatures::SIMD), "exception-handling" => features.insert(WasmFeatures::EXCEPTIONS), - "legacy-exceptions.wat" => features.insert(WasmFeatures::LEGACY_EXCEPTIONS), + "legacy-exceptions" => features.insert(WasmFeatures::LEGACY_EXCEPTIONS), "tail-call" => features.insert(WasmFeatures::TAIL_CALL), "memory64" => features.insert(WasmFeatures::MEMORY64), "component-model" => features.insert(WasmFeatures::COMPONENT_MODEL), diff --git a/tests/snapshots/local/legacy-exceptions.wat.print b/tests/snapshots/local/legacy-exceptions/legacy-exceptions.wat.print similarity index 100% rename from tests/snapshots/local/legacy-exceptions.wat.print rename to tests/snapshots/local/legacy-exceptions/legacy-exceptions.wat.print diff --git a/tests/snapshots/local/legacy-exceptions/rethrow.wast.json b/tests/snapshots/local/legacy-exceptions/rethrow.wast.json new file mode 100644 index 0000000000..40a67982c1 --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/rethrow.wast.json @@ -0,0 +1,214 @@ +{ + "source_filename": "tests/local/legacy-exceptions/rethrow.wast", + "commands": [ + { + "type": "module", + "line": 3, + "filename": "rethrow.0.wasm" + }, + { + "type": "assert_exception", + "line": 100, + "action": { + "type": "invoke", + "field": "catch-rethrow-0", + "args": [] + } + }, + { + "type": "assert_exception", + "line": 102, + "action": { + "type": "invoke", + "field": "catch-rethrow-1", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + } + }, + { + "type": "assert_return", + "line": 103, + "action": { + "type": "invoke", + "field": "catch-rethrow-1", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "23" + } + ] + }, + { + "type": "assert_exception", + "line": 105, + "action": { + "type": "invoke", + "field": "catchall-rethrow-0", + "args": [] + } + }, + { + "type": "assert_exception", + "line": 107, + "action": { + "type": "invoke", + "field": "catchall-rethrow-1", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + } + }, + { + "type": "assert_return", + "line": 108, + "action": { + "type": "invoke", + "field": "catchall-rethrow-1", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "23" + } + ] + }, + { + "type": "assert_exception", + "line": 109, + "action": { + "type": "invoke", + "field": "rethrow-nested", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + } + }, + { + "type": "assert_exception", + "line": 110, + "action": { + "type": "invoke", + "field": "rethrow-nested", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + } + }, + { + "type": "assert_return", + "line": 111, + "action": { + "type": "invoke", + "field": "rethrow-nested", + "args": [ + { + "type": "i32", + "value": "2" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "23" + } + ] + }, + { + "type": "assert_return", + "line": 113, + "action": { + "type": "invoke", + "field": "rethrow-recatch", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "23" + } + ] + }, + { + "type": "assert_return", + "line": 114, + "action": { + "type": "invoke", + "field": "rethrow-recatch", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "42" + } + ] + }, + { + "type": "assert_exception", + "line": 116, + "action": { + "type": "invoke", + "field": "rethrow-stack-polymorphism", + "args": [] + } + }, + { + "type": "assert_invalid", + "line": 118, + "filename": "rethrow.1.wasm", + "text": "invalid rethrow label", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 119, + "filename": "rethrow.2.wasm", + "text": "invalid rethrow label", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 120, + "filename": "rethrow.3.wasm", + "text": "invalid rethrow label", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/legacy-exceptions/rethrow.wast/0.print b/tests/snapshots/local/legacy-exceptions/rethrow.wast/0.print new file mode 100644 index 0000000000..998341854e --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/rethrow.wast/0.print @@ -0,0 +1,98 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32) (result i32))) + (func (;0;) (type 0) + try ;; label = @1 + throw $e0 + catch $e0 + rethrow 0 (;@1;) + end + ) + (func (;1;) (type 1) (param i32) (result i32) + try (result i32) ;; label = @1 + throw $e0 + catch $e0 + local.get 0 + i32.eqz + if ;; label = @2 + rethrow 1 (;@1;) + end + i32.const 23 + end + ) + (func (;2;) (type 0) + try ;; label = @1 + throw $e0 + catch_all + rethrow 0 (;@1;) + end + ) + (func (;3;) (type 1) (param i32) (result i32) + try (result i32) ;; label = @1 + throw $e0 + catch_all + local.get 0 + i32.eqz + if ;; label = @2 + rethrow 1 (;@1;) + end + i32.const 23 + end + ) + (func (;4;) (type 1) (param i32) (result i32) + try (result i32) ;; label = @1 + throw $e1 + catch $e1 + try (result i32) ;; label = @2 + throw $e0 + catch $e0 + local.get 0 + i32.const 0 + i32.eq + if ;; label = @3 + rethrow 1 (;@2;) + end + local.get 0 + i32.const 1 + i32.eq + if ;; label = @3 + rethrow 2 (;@1;) + end + i32.const 23 + end + end + ) + (func (;5;) (type 1) (param i32) (result i32) + try (result i32) ;; label = @1 + throw $e0 + catch $e0 + try (result i32) ;; label = @2 + local.get 0 + i32.eqz + if ;; label = @3 + rethrow 2 (;@1;) + end + i32.const 42 + catch $e0 + i32.const 23 + end + end + ) + (func (;6;) (type 0) + try ;; label = @1 + throw $e0 + catch $e0 + i32.const 1 + rethrow 0 (;@1;) + end + ) + (tag $e0 (;0;) (type 0)) + (tag $e1 (;1;) (type 0)) + (export "catch-rethrow-0" (func 0)) + (export "catch-rethrow-1" (func 1)) + (export "catchall-rethrow-0" (func 2)) + (export "catchall-rethrow-1" (func 3)) + (export "rethrow-nested" (func 4)) + (export "rethrow-recatch" (func 5)) + (export "rethrow-stack-polymorphism" (func 6)) +) diff --git a/tests/snapshots/local/legacy-exceptions/throw.wast.json b/tests/snapshots/local/legacy-exceptions/throw.wast.json new file mode 100644 index 0000000000..bbffce788a --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/throw.wast.json @@ -0,0 +1,131 @@ +{ + "source_filename": "tests/local/legacy-exceptions/throw.wast", + "commands": [ + { + "type": "module", + "line": 3, + "filename": "throw.0.wasm" + }, + { + "type": "assert_return", + "line": 42, + "action": { + "type": "invoke", + "field": "throw-if", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_exception", + "line": 43, + "action": { + "type": "invoke", + "field": "throw-if", + "args": [ + { + "type": "i32", + "value": "10" + } + ] + } + }, + { + "type": "assert_exception", + "line": 44, + "action": { + "type": "invoke", + "field": "throw-if", + "args": [ + { + "type": "i32", + "value": "-1" + } + ] + } + }, + { + "type": "assert_exception", + "line": 46, + "action": { + "type": "invoke", + "field": "throw-param-f32", + "args": [ + { + "type": "f32", + "value": "1084227584" + } + ] + } + }, + { + "type": "assert_exception", + "line": 47, + "action": { + "type": "invoke", + "field": "throw-param-i64", + "args": [ + { + "type": "i64", + "value": "5" + } + ] + } + }, + { + "type": "assert_exception", + "line": 48, + "action": { + "type": "invoke", + "field": "throw-param-f64", + "args": [ + { + "type": "f64", + "value": "4617315517961601024" + } + ] + } + }, + { + "type": "assert_return", + "line": 50, + "action": { + "type": "invoke", + "field": "test-throw-1-2", + "args": [] + }, + "expected": [] + }, + { + "type": "assert_invalid", + "line": 52, + "filename": "throw.1.wasm", + "text": "unknown tag 0", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 53, + "filename": "throw.2.wasm", + "text": "type mismatch: instruction requires [i32] but stack has []", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 55, + "filename": "throw.3.wasm", + "text": "type mismatch: instruction requires [i32] but stack has [i64]", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/legacy-exceptions/throw.wast/0.print b/tests/snapshots/local/legacy-exceptions/throw.wast/0.print new file mode 100644 index 0000000000..afaf56a81e --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/throw.wast/0.print @@ -0,0 +1,62 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (type (;2;) (func (param f32))) + (type (;3;) (func (param i64))) + (type (;4;) (func (param f64))) + (type (;5;) (func (param i32 i32))) + (type (;6;) (func (param i32) (result i32))) + (func $throw-if (;0;) (type 6) (param i32) (result i32) + local.get 0 + i32.const 0 + i32.ne + if ;; label = @1 + throw $e0 + end + i32.const 0 + ) + (func (;1;) (type 2) (param f32) + local.get 0 + throw $e-f32 + ) + (func (;2;) (type 3) (param i64) + local.get 0 + throw $e-i64 + ) + (func (;3;) (type 4) (param f64) + local.get 0 + throw $e-f64 + ) + (func $throw-1-2 (;4;) (type 0) + i32.const 1 + i32.const 2 + throw $e-i32-i32 + ) + (func (;5;) (type 0) + try ;; label = @1 + call $throw-1-2 + catch $e-i32-i32 + i32.const 2 + i32.ne + if ;; label = @2 + unreachable + end + i32.const 1 + i32.ne + if ;; label = @2 + unreachable + end + end + ) + (tag $e0 (;0;) (type 0)) + (tag $e-i32 (;1;) (type 1) (param i32)) + (tag $e-f32 (;2;) (type 2) (param f32)) + (tag $e-i64 (;3;) (type 3) (param i64)) + (tag $e-f64 (;4;) (type 4) (param f64)) + (tag $e-i32-i32 (;5;) (type 5) (param i32 i32)) + (export "throw-if" (func $throw-if)) + (export "throw-param-f32" (func 1)) + (export "throw-param-i64" (func 2)) + (export "throw-param-f64" (func 3)) + (export "test-throw-1-2" (func 5)) +) diff --git a/tests/snapshots/local/legacy-exceptions/try_catch.wast.json b/tests/snapshots/local/legacy-exceptions/try_catch.wast.json new file mode 100644 index 0000000000..8b256d189b --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/try_catch.wast.json @@ -0,0 +1,599 @@ +{ + "source_filename": "tests/local/legacy-exceptions/try_catch.wast", + "commands": [ + { + "type": "module", + "line": 3, + "filename": "try_catch.0.wasm" + }, + { + "type": "register", + "line": 8, + "as": "test" + }, + { + "type": "module", + "line": 10, + "filename": "try_catch.1.wasm" + }, + { + "type": "assert_return", + "line": 213, + "action": { + "type": "invoke", + "field": "empty-catch", + "args": [] + }, + "expected": [] + }, + { + "type": "assert_return", + "line": 215, + "action": { + "type": "invoke", + "field": "simple-throw-catch", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "23" + } + ] + }, + { + "type": "assert_return", + "line": 216, + "action": { + "type": "invoke", + "field": "simple-throw-catch", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "42" + } + ] + }, + { + "type": "assert_trap", + "line": 218, + "action": { + "type": "invoke", + "field": "unreachable-not-caught", + "args": [] + }, + "text": "unreachable" + }, + { + "type": "assert_return", + "line": 220, + "action": { + "type": "invoke", + "field": "trap-in-callee", + "args": [ + { + "type": "i32", + "value": "7" + }, + { + "type": "i32", + "value": "2" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "3" + } + ] + }, + { + "type": "assert_trap", + "line": 221, + "action": { + "type": "invoke", + "field": "trap-in-callee", + "args": [ + { + "type": "i32", + "value": "1" + }, + { + "type": "i32", + "value": "0" + } + ] + }, + "text": "integer divide by zero" + }, + { + "type": "assert_return", + "line": 223, + "action": { + "type": "invoke", + "field": "catch-complex-1", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "3" + } + ] + }, + { + "type": "assert_return", + "line": 224, + "action": { + "type": "invoke", + "field": "catch-complex-1", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "4" + } + ] + }, + { + "type": "assert_exception", + "line": 225, + "action": { + "type": "invoke", + "field": "catch-complex-1", + "args": [ + { + "type": "i32", + "value": "2" + } + ] + } + }, + { + "type": "assert_return", + "line": 227, + "action": { + "type": "invoke", + "field": "catch-complex-2", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "3" + } + ] + }, + { + "type": "assert_return", + "line": 228, + "action": { + "type": "invoke", + "field": "catch-complex-2", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "4" + } + ] + }, + { + "type": "assert_exception", + "line": 229, + "action": { + "type": "invoke", + "field": "catch-complex-2", + "args": [ + { + "type": "i32", + "value": "2" + } + ] + } + }, + { + "type": "assert_return", + "line": 231, + "action": { + "type": "invoke", + "field": "throw-catch-param-i32", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 232, + "action": { + "type": "invoke", + "field": "throw-catch-param-i32", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_return", + "line": 233, + "action": { + "type": "invoke", + "field": "throw-catch-param-i32", + "args": [ + { + "type": "i32", + "value": "10" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "10" + } + ] + }, + { + "type": "assert_return", + "line": 235, + "action": { + "type": "invoke", + "field": "throw-catch-param-f32", + "args": [ + { + "type": "f32", + "value": "1084227584" + } + ] + }, + "expected": [ + { + "type": "f32", + "value": "1084227584" + } + ] + }, + { + "type": "assert_return", + "line": 236, + "action": { + "type": "invoke", + "field": "throw-catch-param-f32", + "args": [ + { + "type": "f32", + "value": "1093140480" + } + ] + }, + "expected": [ + { + "type": "f32", + "value": "1093140480" + } + ] + }, + { + "type": "assert_return", + "line": 238, + "action": { + "type": "invoke", + "field": "throw-catch-param-i64", + "args": [ + { + "type": "i64", + "value": "5" + } + ] + }, + "expected": [ + { + "type": "i64", + "value": "5" + } + ] + }, + { + "type": "assert_return", + "line": 239, + "action": { + "type": "invoke", + "field": "throw-catch-param-i64", + "args": [ + { + "type": "i64", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i64", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 240, + "action": { + "type": "invoke", + "field": "throw-catch-param-i64", + "args": [ + { + "type": "i64", + "value": "-1" + } + ] + }, + "expected": [ + { + "type": "i64", + "value": "-1" + } + ] + }, + { + "type": "assert_return", + "line": 242, + "action": { + "type": "invoke", + "field": "throw-catch-param-f64", + "args": [ + { + "type": "f64", + "value": "4617315517961601024" + } + ] + }, + "expected": [ + { + "type": "f64", + "value": "4617315517961601024" + } + ] + }, + { + "type": "assert_return", + "line": 243, + "action": { + "type": "invoke", + "field": "throw-catch-param-f64", + "args": [ + { + "type": "f64", + "value": "4622100592565682176" + } + ] + }, + "expected": [ + { + "type": "f64", + "value": "4622100592565682176" + } + ] + }, + { + "type": "assert_return", + "line": 245, + "action": { + "type": "invoke", + "field": "catch-param-i32", + "args": [ + { + "type": "i32", + "value": "5" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "5" + } + ] + }, + { + "type": "assert_return", + "line": 247, + "action": { + "type": "invoke", + "field": "catch-imported", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "2" + } + ] + }, + { + "type": "assert_return", + "line": 249, + "action": { + "type": "invoke", + "field": "catchless-try", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "0" + } + ] + }, + { + "type": "assert_return", + "line": 250, + "action": { + "type": "invoke", + "field": "catchless-try", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_exception", + "line": 252, + "action": { + "type": "invoke", + "field": "return-call-in-try-catch", + "args": [] + } + }, + { + "type": "assert_exception", + "line": 253, + "action": { + "type": "invoke", + "field": "return-call-indirect-in-try-catch", + "args": [] + } + }, + { + "type": "assert_return", + "line": 255, + "action": { + "type": "invoke", + "field": "break-try-catch", + "args": [] + }, + "expected": [] + }, + { + "type": "assert_return", + "line": 256, + "action": { + "type": "invoke", + "field": "break-try-catch_all", + "args": [] + }, + "expected": [] + }, + { + "type": "module", + "line": 258, + "filename": "try_catch.2.wasm" + }, + { + "type": "assert_return", + "line": 276, + "action": { + "type": "invoke", + "field": "imported-mismatch", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "3" + } + ] + }, + { + "type": "assert_invalid", + "line": 297, + "filename": "try_catch.3.wasm", + "text": "type mismatch: instruction requires [i32] but stack has []", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 299, + "filename": "try_catch.4.wasm", + "text": "type mismatch: instruction requires [i32] but stack has [i64]", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 301, + "filename": "try_catch.5.wasm", + "text": "type mismatch: block requires [] but stack has [i32]", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 303, + "filename": "try_catch.6.wasm", + "text": "type mismatch: instruction requires [i32] but stack has [i64]", + "module_type": "binary" + }, + { + "type": "assert_invalid", + "line": 308, + "filename": "try_catch.7.wasm", + "text": "type mismatch: block requires [] but stack has [i32]", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/legacy-exceptions/try_catch.wast/0.print b/tests/snapshots/local/legacy-exceptions/try_catch.wast/0.print new file mode 100644 index 0000000000..121840d85f --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/try_catch.wast/0.print @@ -0,0 +1,9 @@ +(module + (type (;0;) (func)) + (func (;0;) (type 0) + throw $e0 + ) + (tag $e0 (;0;) (type 0)) + (export "e0" (tag 0)) + (export "throw" (func 0)) +) diff --git a/tests/snapshots/local/legacy-exceptions/try_catch.wast/2.print b/tests/snapshots/local/legacy-exceptions/try_catch.wast/2.print new file mode 100644 index 0000000000..dc973cca06 --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/try_catch.wast/2.print @@ -0,0 +1,229 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (type (;2;) (func (param f32))) + (type (;3;) (func (param i64))) + (type (;4;) (func (param f64))) + (type (;5;) (func (param i32) (result i32))) + (type (;6;) (func (param i32 i32) (result i32))) + (type (;7;) (func (param f32) (result f32))) + (type (;8;) (func (param i64) (result i64))) + (type (;9;) (func (param f64) (result f64))) + (type (;10;) (func (result i32))) + (import "test" "e0" (tag $imported-e0 (;0;) (type 0))) + (import "test" "throw" (func $imported-throw (;0;) (type 0))) + (func $throw-if (;1;) (type 5) (param i32) (result i32) + local.get 0 + i32.const 0 + i32.ne + if ;; label = @1 + throw $e0 + end + i32.const 0 + ) + (func (;2;) (type 0) + try ;; label = @1 + catch $e0 + end + ) + (func (;3;) (type 5) (param i32) (result i32) + try (result i32) ;; label = @1 + local.get 0 + i32.eqz + if ;; label = @2 + throw $e0 + end + i32.const 42 + catch $e0 + i32.const 23 + end + ) + (func (;4;) (type 0) + try ;; label = @1 + unreachable + catch_all + end + ) + (func $div (;5;) (type 6) (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.div_u + ) + (func (;6;) (type 6) (param i32 i32) (result i32) + try (result i32) ;; label = @1 + local.get 0 + local.get 1 + call $div + catch_all + i32.const 11 + end + ) + (func (;7;) (type 5) (param i32) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + local.get 0 + i32.eqz + if ;; label = @3 + throw $e0 + else + local.get 0 + i32.const 1 + i32.eq + if ;; label = @4 + throw $e1 + else + throw $e2 + end + end + i32.const 2 + catch $e0 + i32.const 3 + end + catch $e1 + i32.const 4 + end + ) + (func (;8;) (type 5) (param i32) (result i32) + try (result i32) ;; label = @1 + local.get 0 + i32.eqz + if ;; label = @2 + throw $e0 + else + local.get 0 + i32.const 1 + i32.eq + if ;; label = @3 + throw $e1 + else + throw $e2 + end + end + i32.const 2 + catch $e0 + i32.const 3 + catch $e1 + i32.const 4 + end + ) + (func (;9;) (type 5) (param i32) (result i32) + try (result i32) ;; label = @1 + local.get 0 + throw $e-i32 + i32.const 2 + catch $e-i32 + return + end + ) + (func (;10;) (type 7) (param f32) (result f32) + try (result f32) ;; label = @1 + local.get 0 + throw $e-f32 + f32.const 0x0p+0 (;=0;) + catch $e-f32 + return + end + ) + (func (;11;) (type 8) (param i64) (result i64) + try (result i64) ;; label = @1 + local.get 0 + throw $e-i64 + i64.const 2 + catch $e-i64 + return + end + ) + (func (;12;) (type 9) (param f64) (result f64) + try (result f64) ;; label = @1 + local.get 0 + throw $e-f64 + f64.const 0x0p+0 (;=0;) + catch $e-f64 + return + end + ) + (func $throw-param-i32 (;13;) (type 1) (param i32) + local.get 0 + throw $e-i32 + ) + (func (;14;) (type 5) (param i32) (result i32) + try (result i32) ;; label = @1 + i32.const 0 + local.get 0 + call $throw-param-i32 + catch $e-i32 + end + ) + (func (;15;) (type 10) (result i32) + try (result i32) ;; label = @1 + i32.const 1 + call $imported-throw + catch $imported-e0 + i32.const 2 + end + ) + (func (;16;) (type 5) (param i32) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + local.get 0 + call $throw-if + end + catch $e0 + i32.const 1 + end + ) + (func $throw-void (;17;) (type 0) + throw $e0 + ) + (func (;18;) (type 0) + try ;; label = @1 + return_call $throw-void + catch $e0 + end + ) + (func (;19;) (type 0) + try ;; label = @1 + i32.const 0 + return_call_indirect (type 0) + catch $e0 + end + ) + (func (;20;) (type 0) + try ;; label = @1 + br 0 (;@1;) + catch $e0 + end + ) + (func (;21;) (type 0) + try ;; label = @1 + br 0 (;@1;) + catch_all + end + ) + (table (;0;) 1 1 funcref) + (tag $e0 (;1;) (type 0)) + (tag $e1 (;2;) (type 0)) + (tag $e2 (;3;) (type 0)) + (tag $e-i32 (;4;) (type 1) (param i32)) + (tag $e-f32 (;5;) (type 2) (param f32)) + (tag $e-i64 (;6;) (type 3) (param i64)) + (tag $e-f64 (;7;) (type 4) (param f64)) + (export "empty-catch" (func 2)) + (export "simple-throw-catch" (func 3)) + (export "unreachable-not-caught" (func 4)) + (export "trap-in-callee" (func 6)) + (export "catch-complex-1" (func 7)) + (export "catch-complex-2" (func 8)) + (export "throw-catch-param-i32" (func 9)) + (export "throw-catch-param-f32" (func 10)) + (export "throw-catch-param-i64" (func 11)) + (export "throw-catch-param-f64" (func 12)) + (export "catch-param-i32" (func 14)) + (export "catch-imported" (func 15)) + (export "catchless-try" (func 16)) + (export "return-call-in-try-catch" (func 18)) + (export "return-call-indirect-in-try-catch" (func 19)) + (export "break-try-catch" (func 20)) + (export "break-try-catch_all" (func 21)) + (elem (;0;) (i32.const 0) func $throw-void) +) diff --git a/tests/snapshots/local/legacy-exceptions/try_catch.wast/33.print b/tests/snapshots/local/legacy-exceptions/try_catch.wast/33.print new file mode 100644 index 0000000000..8210510b56 --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/try_catch.wast/33.print @@ -0,0 +1,19 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (import "test" "throw" (func $imported-throw (;0;) (type 0))) + (func (;1;) (type 1) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + i32.const 1 + call $imported-throw + catch $e0 + i32.const 2 + end + catch_all + i32.const 3 + end + ) + (tag $e0 (;0;) (type 0)) + (export "imported-mismatch" (func 1)) +) diff --git a/tests/snapshots/local/legacy-exceptions/try_delegate.wast.json b/tests/snapshots/local/legacy-exceptions/try_delegate.wast.json new file mode 100644 index 0000000000..2ba6f88788 --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/try_delegate.wast.json @@ -0,0 +1,325 @@ +{ + "source_filename": "tests/local/legacy-exceptions/try_delegate.wast", + "commands": [ + { + "type": "module", + "line": 3, + "filename": "try_delegate.0.wasm" + }, + { + "type": "assert_return", + "line": 219, + "action": { + "type": "invoke", + "field": "delegate-no-throw", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_return", + "line": 221, + "action": { + "type": "invoke", + "field": "delegate-throw", + "args": [ + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_return", + "line": 222, + "action": { + "type": "invoke", + "field": "delegate-throw", + "args": [ + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "2" + } + ] + }, + { + "type": "assert_exception", + "line": 224, + "action": { + "type": "invoke", + "field": "delegate-throw-no-catch", + "args": [] + } + }, + { + "type": "assert_return", + "line": 226, + "action": { + "type": "invoke", + "field": "delegate-merge", + "args": [ + { + "type": "i32", + "value": "1" + }, + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "2" + } + ] + }, + { + "type": "assert_exception", + "line": 227, + "action": { + "type": "invoke", + "field": "delegate-merge", + "args": [ + { + "type": "i32", + "value": "2" + }, + { + "type": "i32", + "value": "0" + } + ] + } + }, + { + "type": "assert_return", + "line": 228, + "action": { + "type": "invoke", + "field": "delegate-merge", + "args": [ + { + "type": "i32", + "value": "0" + }, + { + "type": "i32", + "value": "1" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "2" + } + ] + }, + { + "type": "assert_exception", + "line": 229, + "action": { + "type": "invoke", + "field": "delegate-merge", + "args": [ + { + "type": "i32", + "value": "0" + }, + { + "type": "i32", + "value": "2" + } + ] + } + }, + { + "type": "assert_return", + "line": 230, + "action": { + "type": "invoke", + "field": "delegate-merge", + "args": [ + { + "type": "i32", + "value": "0" + }, + { + "type": "i32", + "value": "0" + } + ] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_return", + "line": 232, + "action": { + "type": "invoke", + "field": "delegate-skip", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "3" + } + ] + }, + { + "type": "assert_return", + "line": 234, + "action": { + "type": "invoke", + "field": "delegate-to-block", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_return", + "line": 235, + "action": { + "type": "invoke", + "field": "delegate-to-catch", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_exception", + "line": 237, + "action": { + "type": "invoke", + "field": "delegate-to-caller-trivial", + "args": [] + } + }, + { + "type": "assert_exception", + "line": 238, + "action": { + "type": "invoke", + "field": "delegate-to-caller-skipping", + "args": [] + } + }, + { + "type": "assert_return", + "line": 240, + "action": { + "type": "invoke", + "field": "delegate-correct-targets", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_exception", + "line": 242, + "action": { + "type": "invoke", + "field": "return-call-in-try-delegate", + "args": [] + } + }, + { + "type": "assert_exception", + "line": 243, + "action": { + "type": "invoke", + "field": "return-call-indirect-in-try-delegate", + "args": [] + } + }, + { + "type": "assert_return", + "line": 245, + "action": { + "type": "invoke", + "field": "break-try-delegate", + "args": [] + }, + "expected": [] + }, + { + "type": "assert_return", + "line": 247, + "action": { + "type": "invoke", + "field": "break-and-call-throw", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_return", + "line": 248, + "action": { + "type": "invoke", + "field": "break-and-throw", + "args": [] + }, + "expected": [ + { + "type": "i32", + "value": "1" + } + ] + }, + { + "type": "assert_invalid", + "line": 273, + "filename": "try_delegate.1.wasm", + "text": "unknown label", + "module_type": "binary" + } + ] +} \ No newline at end of file diff --git a/tests/snapshots/local/legacy-exceptions/try_delegate.wast/0.print b/tests/snapshots/local/legacy-exceptions/try_delegate.wast/0.print new file mode 100644 index 0000000000..7144255ea7 --- /dev/null +++ b/tests/snapshots/local/legacy-exceptions/try_delegate.wast/0.print @@ -0,0 +1,227 @@ +(module + (type (;0;) (func)) + (type (;1;) (func (result i32))) + (type (;2;) (func (param i32))) + (type (;3;) (func (param i32) (result i32))) + (type (;4;) (func (param i32 i32) (result i32))) + (func (;0;) (type 1) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + i32.const 1 + delegate 0 (;@1;) + catch $e0 + i32.const 2 + end + ) + (func $throw-if (;1;) (type 2) (param i32) + local.get 0 + if ;; label = @1 + throw $e0 + else + end + ) + (func (;2;) (type 3) (param i32) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + local.get 0 + call $throw-if + i32.const 1 + delegate 0 (;@1;) + catch $e0 + i32.const 2 + end + ) + (func (;3;) (type 1) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + try (result i32) ;; label = @3 + throw $e0 + i32.const 1 + delegate 1 (;@1;) + catch $e0 + i32.const 2 + end + catch $e0 + i32.const 3 + end + ) + (func (;4;) (type 1) (result i32) + try (result i32) ;; label = @1 + block ;; label = @2 + try ;; label = @3 + throw $e0 + delegate 0 (;@2;) + end + i32.const 0 + catch_all + i32.const 1 + end + ) + (func (;5;) (type 1) (result i32) + try (result i32) ;; label = @1 + try ;; label = @2 + throw $e0 + catch $e0 + try ;; label = @3 + rethrow 1 (;@2;) + delegate 0 (;@2;) + end + i32.const 0 + catch_all + i32.const 1 + end + ) + (func (;6;) (type 0) + try ;; label = @1 + throw $e0 + delegate 0 + ) + (func (;7;) (type 0) + try ;; label = @1 + try ;; label = @2 + throw $e0 + delegate 1 + catch_all + end + ) + (func $select-tag (;8;) (type 2) (param i32) + block ;; label = @1 + block ;; label = @2 + block ;; label = @3 + local.get 0 + br_table 0 (;@3;) 1 (;@2;) 2 (;@1;) + end + return + end + throw $e0 + end + throw $e1 + ) + (func (;9;) (type 4) (param i32 i32) (result i32) + try (result i32) ;; label = @1 + local.get 0 + call $select-tag + try (result i32) ;; label = @2 + local.get 1 + call $select-tag + i32.const 1 + delegate 0 (;@1;) + catch $e0 + i32.const 2 + end + ) + (func (;10;) (type 1) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + throw $e0 + i32.const 1 + delegate 0 (;@1;) + catch $e1 + i32.const 2 + end + ) + (func (;11;) (type 1) (result i32) + try (result i32) ;; label = @1 + try ;; label = @2 + try ;; label = @3 + try ;; label = @4 + try ;; label = @5 + try ;; label = @6 + throw $e0 + delegate 1 (;@4;) + catch_all + unreachable + end + delegate 1 (;@2;) + catch_all + unreachable + end + catch_all + try ;; label = @3 + throw $e0 + delegate 0 (;@2;) + end + unreachable + catch_all + i32.const 1 + end + ) + (func $throw-void (;12;) (type 0) + throw $e0 + ) + (func (;13;) (type 0) + try ;; label = @1 + try ;; label = @2 + return_call $throw-void + delegate 0 (;@1;) + catch $e0 + end + ) + (func (;14;) (type 0) + try ;; label = @1 + try ;; label = @2 + i32.const 0 + return_call_indirect (type 0) + delegate 0 (;@1;) + catch $e0 + end + ) + (func (;15;) (type 0) + try ;; label = @1 + br 0 (;@1;) + delegate 0 + ) + (func (;16;) (type 1) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + block ;; label = @3 + try ;; label = @4 + br 1 (;@3;) + delegate 2 (;@1;) + end + call $throw-void + i32.const 0 + catch $e0 + i32.const 1 + end + catch $e0 + i32.const 2 + end + ) + (func (;17;) (type 1) (result i32) + try (result i32) ;; label = @1 + try (result i32) ;; label = @2 + block ;; label = @3 + try ;; label = @4 + br 1 (;@3;) + delegate 2 (;@1;) + end + throw $e0 + i32.const 0 + catch $e0 + i32.const 1 + end + catch $e0 + i32.const 2 + end + ) + (table (;0;) 1 1 funcref) + (tag $e0 (;0;) (type 0)) + (tag $e1 (;1;) (type 0)) + (export "delegate-no-throw" (func 0)) + (export "delegate-throw" (func 2)) + (export "delegate-skip" (func 3)) + (export "delegate-to-block" (func 4)) + (export "delegate-to-catch" (func 5)) + (export "delegate-to-caller-trivial" (func 6)) + (export "delegate-to-caller-skipping" (func 7)) + (export "delegate-merge" (func 9)) + (export "delegate-throw-no-catch" (func 10)) + (export "delegate-correct-targets" (func 11)) + (export "return-call-in-try-delegate" (func 13)) + (export "return-call-indirect-in-try-delegate" (func 14)) + (export "break-try-delegate" (func 15)) + (export "break-and-call-throw" (func 16)) + (export "break-and-throw" (func 17)) + (elem (;0;) (i32.const 0) func $throw-void) +)