From b6141c9dc5ee16496390cc0796c96360e6aedc83 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 10:18:50 -0800 Subject: [PATCH 01/17] Refactor root `Cargo.toml` a bit * Update some wasm-tools dependencies * Use a `[patch]` for Wasmtime to reflect how this will eventually be a crates.io dependency * Use `workspace = true` to avoid duplicating path names. --- Cargo.lock | 47 ++++++++++++++--------------------------------- Cargo.toml | 26 ++++++++++++++------------ 2 files changed, 28 insertions(+), 45 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 17414241a..db779978d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -117,7 +117,7 @@ dependencies = [ [[package]] name = "cranelift-entity" version = "0.93.0" -source = "git+https://github.com/bytecodealliance/wasmtime#b58a197d33f044193c3d608010f5e6ec394ac07e" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" dependencies = [ "serde", ] @@ -596,7 +596,7 @@ version = "0.3.0" dependencies = [ "codegen-macro", "runtime-macro", - "wasm-encoder 0.21.0", + "wasm-encoder", "wat", "wit-bindgen-core", "wit-component", @@ -745,15 +745,6 @@ dependencies = [ "wit-bindgen-guest-rust", ] -[[package]] -name = "wasm-encoder" -version = "0.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29ab2fe77b325731603297debb4573e002d06ae0aa1f4dc108585c81961e0609" -dependencies = [ - "leb128", -] - [[package]] name = "wasm-encoder" version = "0.22.0" @@ -763,16 +754,6 @@ dependencies = [ "leb128", ] -[[package]] -name = "wasmparser" -version = "0.97.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98123a0d2bacf9286239231b116cbd66c65d9b89793f7c9bba3a3ae7f1b15f3" -dependencies = [ - "indexmap", - "url", -] - [[package]] name = "wasmparser" version = "0.99.0" @@ -790,18 +771,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c13dff901f9354fa9a6a877152d9c5642513645985635c9b83bcca99e40ea1" dependencies = [ "anyhow", - "wasmparser 0.99.0", + "wasmparser", ] [[package]] name = "wasmtime-component-util" version = "6.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#b58a197d33f044193c3d608010f5e6ec394ac07e" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" [[package]] name = "wasmtime-environ" version = "6.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#b58a197d33f044193c3d608010f5e6ec394ac07e" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" dependencies = [ "anyhow", "cranelift-entity", @@ -812,8 +793,8 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasm-encoder 0.21.0", - "wasmparser 0.97.0", + "wasm-encoder", + "wasmparser", "wasmprinter", "wasmtime-component-util", "wasmtime-types", @@ -822,12 +803,12 @@ dependencies = [ [[package]] name = "wasmtime-types" version = "6.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#b58a197d33f044193c3d608010f5e6ec394ac07e" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser 0.97.0", + "wasmparser", ] [[package]] @@ -839,7 +820,7 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.22.0", + "wasm-encoder", ] [[package]] @@ -971,7 +952,7 @@ version = "0.3.0" dependencies = [ "anyhow", "test-helpers", - "wasm-encoder 0.21.0", + "wasm-encoder", "wasmprinter", "wit-bindgen-core", "wit-bindgen-gen-guest-c", @@ -991,7 +972,7 @@ dependencies = [ "clap", "heck", "test-helpers", - "wasm-encoder 0.21.0", + "wasm-encoder", "wit-bindgen-core", "wit-component", ] @@ -1085,8 +1066,8 @@ dependencies = [ "indexmap", "log", "url", - "wasm-encoder 0.22.0", - "wasmparser 0.99.0", + "wasm-encoder", + "wasmparser", "wat", "wit-parser", ] diff --git a/Cargo.toml b/Cargo.toml index f400c7012..f4237e980 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -25,13 +25,12 @@ clap = { version = "4.0.9", features = ["derive"] } env_logger = "0.9.1" indexmap = "1.9.1" -wasmtime-environ = { git = 'https://github.com/bytecodealliance/wasmtime' } +wasmtime-environ = "6.0.0" wasmprinter = "0.2.46" -wasmparser = "0.97.0" -wasm-encoder = "0.21.0" -wat = "1.0.53" -wit-parser = "0.4.0" -wit-component = "0.4.1" +wasm-encoder = "0.22.0" +wat = "1.0.56" +wit-parser = "0.4.1" +wit-component = "0.4.3" wit-bindgen-core = { path = 'crates/bindgen-core', version = '0.3.0' } wit-bindgen-gen-guest-c = { path = 'crates/gen-guest-c', version = '0.3.0' } @@ -50,11 +49,14 @@ test = false [dependencies] anyhow = { workspace = true } clap = { workspace = true } -wit-bindgen-core = { path = 'crates/bindgen-core' } -wit-bindgen-gen-guest-rust = { path = 'crates/gen-guest-rust', features = ['clap'] } -wit-bindgen-gen-host-js = { path = 'crates/gen-host-js', features = ['clap'] } -wit-bindgen-gen-guest-c = { path = 'crates/gen-guest-c', features = ['clap'] } -wit-bindgen-gen-markdown = { path = 'crates/gen-markdown', features = ['clap'] } -wit-bindgen-gen-guest-teavm-java = { path = 'crates/gen-guest-teavm-java', features = ['clap'] } +wit-bindgen-core = { workspace = true } +wit-bindgen-gen-guest-rust = { workspace = true, features = ['clap'] } +wit-bindgen-gen-host-js = { workspace = true, features = ['clap'] } +wit-bindgen-gen-guest-c = { workspace = true, features = ['clap'] } +wit-bindgen-gen-markdown = { workspace = true, features = ['clap'] } +wit-bindgen-gen-guest-teavm-java = { workspace = true, features = ['clap'] } wat = { workspace = true } wit-component = { workspace = true } + +[patch.crates-io] +wasmtime-environ = { git = 'https://github.com/bytecodealliance/wasmtime' } From 7b817ce313ae00c443f47ebc98dd5fd0842fc22a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 10:21:46 -0800 Subject: [PATCH 02/17] Remove an unneeded futures-util dep --- Cargo.lock | 59 -------------------------------- crates/test-rust-wasm/Cargo.toml | 1 - 2 files changed, 60 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index db779978d..d693bd2f5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -173,43 +173,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "futures-core" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" - -[[package]] -name = "futures-macro" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-task" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" - -[[package]] -name = "futures-util" -version = "0.3.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" -dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", -] - [[package]] name = "getrandom" version = "0.2.8" @@ -406,18 +369,6 @@ version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" -[[package]] -name = "pin-project-lite" -version = "0.2.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - [[package]] name = "proc-macro-error" version = "1.0.4" @@ -543,15 +494,6 @@ dependencies = [ "syn", ] -[[package]] -name = "slab" -version = "0.4.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" -dependencies = [ - "autocfg", -] - [[package]] name = "stable_deref_trait" version = "1.2.0" @@ -607,7 +549,6 @@ dependencies = [ name = "test-rust-wasm" version = "0.3.0" dependencies = [ - "futures-util", "wit-bindgen-guest-rust", ] diff --git a/crates/test-rust-wasm/Cargo.toml b/crates/test-rust-wasm/Cargo.toml index b09bbda45..eda5649f7 100644 --- a/crates/test-rust-wasm/Cargo.toml +++ b/crates/test-rust-wasm/Cargo.toml @@ -6,7 +6,6 @@ edition.workspace = true publish = false [dependencies] -futures-util = { version = "0.3.17", default-features = true } wit-bindgen-guest-rust = { path = "../guest-rust" } [features] From 6c25e407b552ac15cfb8992b9ccd452fb959f1c0 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 10:36:36 -0800 Subject: [PATCH 03/17] Remove the JS host generator This commit removes the JS host generator from this repository as it now lives at bytecodealliance/js-component-tools. The runtime tests will be updated in a subsequent commit to use Wasmtime as a host for executing components. This additionally removes the demo as it's no longer possible to build it in just this repository. The demo has slowly been getting less useful over time as generators are removed from this repository. Additionally it's always been a demo and not much more so it's more-or-less served its purpose and doesn't seem to have a great place to fit any more. This can possibly exist out-of-tree eventually in a separate location that pulls together all known generators, but until that time it seems best to just remove it for now. Closes #395 --- .github/workflows/main.yml | 37 - Cargo.lock | 205 -- Cargo.toml | 8 - crates/bindgen-core/Cargo.toml | 4 - crates/bindgen-core/src/component.rs | 115 - crates/gen-host-js/.eslintrc.js | 18 - crates/gen-host-js/Cargo.toml | 30 - crates/gen-host-js/package.json | 9 - crates/gen-host-js/src/lib.rs | 2895 -------------------------- crates/gen-host-js/tests/codegen.rs | 44 - crates/gen-host-js/tests/helpers.ts | 26 - crates/gen-host-js/tests/runtime.rs | 100 - crates/wit-bindgen-demo/Cargo.toml | 25 - crates/wit-bindgen-demo/build.sh | 29 - crates/wit-bindgen-demo/console.js | 7 - crates/wit-bindgen-demo/index.html | 107 - crates/wit-bindgen-demo/main.ts | 165 -- crates/wit-bindgen-demo/package.json | 6 - crates/wit-bindgen-demo/src/lib.rs | 112 - crates/wit-bindgen-demo/wit/demo.wit | 26 - src/bin/wit-bindgen.rs | 39 - 21 files changed, 4007 deletions(-) delete mode 100644 crates/bindgen-core/src/component.rs delete mode 100644 crates/gen-host-js/.eslintrc.js delete mode 100644 crates/gen-host-js/Cargo.toml delete mode 100644 crates/gen-host-js/package.json delete mode 100644 crates/gen-host-js/src/lib.rs delete mode 100644 crates/gen-host-js/tests/codegen.rs delete mode 100644 crates/gen-host-js/tests/helpers.ts delete mode 100644 crates/gen-host-js/tests/runtime.rs delete mode 100644 crates/wit-bindgen-demo/Cargo.toml delete mode 100755 crates/wit-bindgen-demo/build.sh delete mode 100644 crates/wit-bindgen-demo/console.js delete mode 100644 crates/wit-bindgen-demo/index.html delete mode 100644 crates/wit-bindgen-demo/main.ts delete mode 100644 crates/wit-bindgen-demo/package.json delete mode 100644 crates/wit-bindgen-demo/src/lib.rs delete mode 100644 crates/wit-bindgen-demo/wit/demo.wit diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9573b8c63..24064168d 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,15 +48,11 @@ jobs: - uses: actions/setup-node@v2 with: node-version: '16' - - name: Install NPM packages - run: npm install - working-directory: crates/gen-host-js - uses: actions/setup-java@v3 with: java-version: '18' distribution: 'adopt' - run: cargo test --workspace - - run: cargo test -p wit-bindgen-gen-host-js --test runtime --features runtime-tests rustfmt: name: Rustfmt @@ -67,36 +63,3 @@ jobs: run: rustup update stable && rustup default stable && rustup component add rustfmt - name: Format source code run: cargo fmt -- --check - - demo: - name: Build wit-bindgen demo - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - run: rustup update stable --no-self-update && rustup default stable - - run: rustup target add wasm32-unknown-unknown - - run: npm install - working-directory: crates/wit-bindgen-demo - - # Install the `wasm-tools` binary with the `component` subcommand that is all - # that's needed here. - - uses: actions/cache@v3 - with: - path: ${{ runner.tool_cache }}/wasm-tools - key: wasm-tools-bin-1.0.17-${{ runner.os }} - - run: echo '${{ runner.tool_cache }}/wasm-tools/bin' >> $GITHUB_PATH - - run: | - cargo install \ - wasm-tools@1.0.17 \ - --root '${{ runner.tool_cache }}/wasm-tools' \ - --locked \ - --no-default-features \ - --features component - - - run: ./crates/wit-bindgen-demo/build.sh - - uses: JamesIves/github-pages-deploy-action@4.1.4 - with: - branch: gh-pages - folder: static - single-commit: true - if: github.event_name == 'push' && github.ref == 'refs/heads/main' diff --git a/Cargo.lock b/Cargo.lock index d693bd2f5..f73a37e5d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,17 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "ahash" -version = "0.7.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" -dependencies = [ - "getrandom", - "once_cell", - "version_check", -] - [[package]] name = "aho-corasick" version = "0.7.20" @@ -34,12 +23,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" -[[package]] -name = "base64" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" - [[package]] name = "bitflags" version = "1.3.2" @@ -114,23 +97,6 @@ dependencies = [ "quote", ] -[[package]] -name = "cranelift-entity" -version = "0.93.0" -source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" -dependencies = [ - "serde", -] - -[[package]] -name = "crc32fast" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" -dependencies = [ - "cfg-if", -] - [[package]] name = "errno" version = "0.2.8" @@ -152,12 +118,6 @@ dependencies = [ "libc", ] -[[package]] -name = "fallible-iterator" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" - [[package]] name = "fnv" version = "1.0.7" @@ -173,27 +133,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "getrandom" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gimli" -version = "0.26.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" -dependencies = [ - "fallible-iterator", - "stable_deref_trait", -] - [[package]] name = "globset" version = "0.4.10" @@ -212,9 +151,6 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" -dependencies = [ - "ahash", -] [[package]] name = "heck" @@ -275,7 +211,6 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", - "serde", ] [[package]] @@ -339,18 +274,6 @@ version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" -[[package]] -name = "object" -version = "0.29.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" -dependencies = [ - "crc32fast", - "hashbrown", - "indexmap", - "memchr", -] - [[package]] name = "once_cell" version = "1.17.0" @@ -479,26 +402,6 @@ name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.152" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "stable_deref_trait" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" @@ -517,12 +420,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "target-lexicon" -version = "0.12.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" - [[package]] name = "termcolor" version = "1.2.0" @@ -552,26 +449,6 @@ dependencies = [ "wit-bindgen-guest-rust", ] -[[package]] -name = "thiserror" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.38" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thread_local" version = "1.1.4" @@ -705,53 +582,6 @@ dependencies = [ "url", ] -[[package]] -name = "wasmprinter" -version = "0.2.49" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27c13dff901f9354fa9a6a877152d9c5642513645985635c9b83bcca99e40ea1" -dependencies = [ - "anyhow", - "wasmparser", -] - -[[package]] -name = "wasmtime-component-util" -version = "6.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" - -[[package]] -name = "wasmtime-environ" -version = "6.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" -dependencies = [ - "anyhow", - "cranelift-entity", - "gimli", - "indexmap", - "log", - "object", - "serde", - "target-lexicon", - "thiserror", - "wasm-encoder", - "wasmparser", - "wasmprinter", - "wasmtime-component-util", - "wasmtime-types", -] - -[[package]] -name = "wasmtime-types" -version = "6.0.0" -source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" -dependencies = [ - "cranelift-entity", - "serde", - "thiserror", - "wasmparser", -] - [[package]] name = "wast" version = "52.0.2" @@ -872,7 +702,6 @@ dependencies = [ "wit-bindgen-gen-guest-c", "wit-bindgen-gen-guest-rust", "wit-bindgen-gen-guest-teavm-java", - "wit-bindgen-gen-host-js", "wit-bindgen-gen-markdown", "wit-component", ] @@ -882,29 +711,10 @@ name = "wit-bindgen-core" version = "0.3.0" dependencies = [ "anyhow", - "wasmtime-environ", "wit-component", "wit-parser", ] -[[package]] -name = "wit-bindgen-demo" -version = "0.3.0" -dependencies = [ - "anyhow", - "test-helpers", - "wasm-encoder", - "wasmprinter", - "wit-bindgen-core", - "wit-bindgen-gen-guest-c", - "wit-bindgen-gen-guest-rust", - "wit-bindgen-gen-guest-teavm-java", - "wit-bindgen-gen-host-js", - "wit-bindgen-gen-markdown", - "wit-bindgen-guest-rust", - "wit-component", -] - [[package]] name = "wit-bindgen-gen-guest-c" version = "0.3.0" @@ -942,21 +752,6 @@ dependencies = [ "wit-component", ] -[[package]] -name = "wit-bindgen-gen-host-js" -version = "0.3.0" -dependencies = [ - "anyhow", - "base64", - "clap", - "heck", - "indexmap", - "test-helpers", - "wasmtime-environ", - "wit-bindgen-core", - "wit-component", -] - [[package]] name = "wit-bindgen-gen-markdown" version = "0.3.0" diff --git a/Cargo.toml b/Cargo.toml index f4237e980..79b1b8fac 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,7 +7,6 @@ edition.workspace = true [workspace] members = [ "crates/test-rust-wasm", - "crates/wit-bindgen-demo", "crates/wasi_snapshot_preview1", ] resolver = "2" @@ -25,8 +24,6 @@ clap = { version = "4.0.9", features = ["derive"] } env_logger = "0.9.1" indexmap = "1.9.1" -wasmtime-environ = "6.0.0" -wasmprinter = "0.2.46" wasm-encoder = "0.22.0" wat = "1.0.56" wit-parser = "0.4.1" @@ -36,7 +33,6 @@ wit-bindgen-core = { path = 'crates/bindgen-core', version = '0.3.0' } wit-bindgen-gen-guest-c = { path = 'crates/gen-guest-c', version = '0.3.0' } wit-bindgen-gen-guest-rust = { path = "crates/gen-guest-rust", version = "0.3.0" } wit-bindgen-gen-guest-teavm-java = { path = 'crates/gen-guest-teavm-java', version = '0.3.0' } -wit-bindgen-gen-host-js = { path = 'crates/gen-host-js', version = '0.3.0' } wit-bindgen-gen-markdown = { path = 'crates/gen-markdown', version = '0.3.0' } wit-bindgen-gen-rust-lib = { path = 'crates/gen-rust-lib', version = '0.3.0' } wit-bindgen-guest-rust = { path = 'crates/guest-rust', version = '0.3.0', default-features = false } @@ -51,12 +47,8 @@ anyhow = { workspace = true } clap = { workspace = true } wit-bindgen-core = { workspace = true } wit-bindgen-gen-guest-rust = { workspace = true, features = ['clap'] } -wit-bindgen-gen-host-js = { workspace = true, features = ['clap'] } wit-bindgen-gen-guest-c = { workspace = true, features = ['clap'] } wit-bindgen-gen-markdown = { workspace = true, features = ['clap'] } wit-bindgen-gen-guest-teavm-java = { workspace = true, features = ['clap'] } wat = { workspace = true } wit-component = { workspace = true } - -[patch.crates-io] -wasmtime-environ = { git = 'https://github.com/bytecodealliance/wasmtime' } diff --git a/crates/bindgen-core/Cargo.toml b/crates/bindgen-core/Cargo.toml index 0a9f28cac..90ce1d3a6 100644 --- a/crates/bindgen-core/Cargo.toml +++ b/crates/bindgen-core/Cargo.toml @@ -11,7 +11,3 @@ doctest = false wit-parser = { workspace = true } anyhow = { workspace = true } wit-component = { workspace = true } -wasmtime-environ = { workspace = true, features = ['component-model'], optional = true } - -[features] -component-generator = ['dep:wasmtime-environ'] diff --git a/crates/bindgen-core/src/component.rs b/crates/bindgen-core/src/component.rs deleted file mode 100644 index c18cb6e90..000000000 --- a/crates/bindgen-core/src/component.rs +++ /dev/null @@ -1,115 +0,0 @@ -//! Support to generate bindings for a host for a single component. -//! -//! This is currently used by the JS host generator and is planned to be used -//! for the Python host generator as well. This module is conditionally defined -//! since it depends on a few somewhat-heavyweight dependencies. -//! -//! The main definition here is the `ComponentGenerator` trait as well as the -//! `generate` function. - -use crate::{Files, WorldGenerator}; -use anyhow::{bail, Context, Result}; -use wasmtime_environ::component::{ - Component, ComponentTypesBuilder, StaticModuleIndex, Translator, -}; -use wasmtime_environ::wasmparser::{Validator, WasmFeatures}; -use wasmtime_environ::{ModuleTranslation, PrimaryMap, ScopeVec, Tunables}; -use wit_component::DecodedWasm; -use wit_parser::{Resolve, WorldId}; - -/// Generate bindings to load and instantiate the specific binary component -/// provided. -pub fn generate( - gen: &mut dyn ComponentGenerator, - name: &str, - binary: &[u8], - files: &mut Files, -) -> Result<()> { - // Use the `wit-component` crate here to parse `binary` and discover - // the type-level descriptions and `Resolve` corresponding to the - // component binary. This will synthesize a `Resolve` which has a top-level - // package which has a single document and `world` within it which describes - // the state of the component. This is then further used afterwards for - // bindings generation as-if a `*.wit` file was input. - let decoded = wit_component::decode(name, binary) - .context("failed to extract interface information from component")?; - let (resolve, world) = match decoded { - DecodedWasm::WitPackage(..) => bail!("unexpected wit package as input"), - DecodedWasm::Component(resolve, world) => (resolve, world), - }; - - // Components are complicated, there's no real way around that. To - // handle all the work of parsing a component and figuring out how to - // instantiate core wasm modules and such all the work is offloaded to - // Wasmtime itself. This crate generator is based on Wasmtime's - // low-level `wasmtime-environ` crate which is technically not a public - // dependency but the same author who worked on that in Wasmtime wrote - // this as well so... "seems fine". - // - // Note that we're not pulling in the entire Wasmtime engine here, - // moreso just the "spine" of validating a component. This enables using - // Wasmtime's internal `Component` representation as a much easier to - // process version of a component that has decompiled everything - // internal to a component to a straight linear list of initializers - // that need to be executed to instantiate a component. - let scope = ScopeVec::new(); - let tunables = Tunables::default(); - let mut types = ComponentTypesBuilder::default(); - let mut validator = Validator::new_with_features(WasmFeatures { - component_model: true, - ..WasmFeatures::default() - }); - let (component, modules) = Translator::new(&tunables, &mut validator, &mut types, &scope) - .translate(binary) - .context("failed to parse the input component")?; - - // Insert all core wasm modules into the generated `Files` which will - // end up getting used in the `generate_instantiate` method. - for (i, module) in modules.iter() { - files.push(&gen.core_file_name(name, i.as_u32()), module.wasm); - } - - // With all that prep work delegate to `WorldGenerator::generate` here - // to generate all the type-level descriptions for this component now - // that the interfaces in/out are understood. - gen.generate(&resolve, world, files); - - // And finally generate the code necessary to instantiate the given - // component to this method using the `Component` that - // `wasmtime-environ` parsed. - gen.instantiate(&component, &modules, &resolve, world); - - gen.finish_component(name, files); - - Ok(()) -} - -/// Trait for hosts that can execute a component by generating bindings for a -/// single component. -/// -/// This trait inherits from `WorldGenerator` to describe type-level bindings -/// for the host in question. This then additionally defines an `instantiate` -/// method which will generate code to perform the precise instantiation for -/// the component specified. -/// -/// This trait is used in conjunction with the [`generate`] method. -pub trait ComponentGenerator: WorldGenerator { - fn instantiate( - &mut self, - component: &Component, - modules: &PrimaryMap>, - resolve: &Resolve, - world: WorldId, - ); - - fn core_file_name(&mut self, name: &str, idx: u32) -> String { - let i_str = if idx == 0 { - String::from("") - } else { - (idx + 1).to_string() - }; - format!("{}.core{i_str}.wasm", name) - } - - fn finish_component(&mut self, name: &str, files: &mut Files); -} diff --git a/crates/gen-host-js/.eslintrc.js b/crates/gen-host-js/.eslintrc.js deleted file mode 100644 index ac972cfb4..000000000 --- a/crates/gen-host-js/.eslintrc.js +++ /dev/null @@ -1,18 +0,0 @@ -module.exports = { - "env": { - "browser": true, - "es2021": true, - "node": true - }, - "extends": "eslint:recommended", - "parserOptions": { - "ecmaVersion": 13, - "sourceType": "module" - }, - "rules": { - // allow this since we generate `const {} = e;` for empty structs - "no-empty-pattern": 0, - // TODO: we generate some unused functions by accident, let's fix that later - "no-unused-vars": 0, - } -}; diff --git a/crates/gen-host-js/Cargo.toml b/crates/gen-host-js/Cargo.toml deleted file mode 100644 index d907ce4e9..000000000 --- a/crates/gen-host-js/Cargo.toml +++ /dev/null @@ -1,30 +0,0 @@ -[package] -name = "wit-bindgen-gen-host-js" -authors = ["Alex Crichton "] -version.workspace = true -edition.workspace = true - -[lib] -doctest = false -test = false - -[dependencies] -wit-bindgen-core = { workspace = true, features = ['component-generator'] } -anyhow = { workspace = true } -heck = { workspace = true } -clap = { workspace = true, optional = true } -wasmtime-environ = { workspace = true, features = ['component-model'] } -wit-component = { workspace = true } -indexmap = "1.0" -base64 = "0.13.1" - -[dev-dependencies] -test-helpers = { path = '../test-helpers' } - -[[test]] -name = "runtime" -required-features = ["runtime-tests"] - -[features] -runtime-tests = ["clap", 'test-helpers/runtime-macro'] -clap = ["dep:clap"] diff --git a/crates/gen-host-js/package.json b/crates/gen-host-js/package.json deleted file mode 100644 index bacb6d532..000000000 --- a/crates/gen-host-js/package.json +++ /dev/null @@ -1,9 +0,0 @@ -{ - "devDependencies": { - "@types/node": "^15.12.2", - "@typescript-eslint/eslint-plugin": "^5.41.0", - "@typescript-eslint/parser": "^5.41.0", - "eslint": "^8.26.0", - "typescript": "^4.3.2" - } -} diff --git a/crates/gen-host-js/src/lib.rs b/crates/gen-host-js/src/lib.rs deleted file mode 100644 index 363d611cb..000000000 --- a/crates/gen-host-js/src/lib.rs +++ /dev/null @@ -1,2895 +0,0 @@ -#[cfg(feature = "clap")] -use anyhow::anyhow; -use anyhow::Result; -use heck::*; -use indexmap::IndexMap; -use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::fmt::Write; -use std::mem; -use wasmtime_environ::component::{ - CanonicalOptions, Component, CoreDef, CoreExport, Export, ExportItem, GlobalInitializer, - InstantiateModule, LowerImport, RuntimeInstanceIndex, StaticModuleIndex, StringEncoding, -}; -use wasmtime_environ::{EntityIndex, ModuleTranslation, PrimaryMap}; -use wit_bindgen_core::component::ComponentGenerator; -use wit_bindgen_core::wit_parser::abi::{ - AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType, -}; -use wit_bindgen_core::{ - uwrite, uwriteln, wit_parser::*, Files, InterfaceGenerator, WorldGenerator, -}; - -#[derive(Default)] -struct Js { - /// The source code for the "main" file that's going to be created for the - /// component we're generating bindings for. This is incrementally added to - /// over time and primarily contains the main `instantiate` function as well - /// as a type-description of the input/output interfaces. - src: Source, - - /// JS output imports map from imported specifier, to a list of bindings - imports: HashMap>, - - /// Type script definitions which will become the import object - import_object: wit_bindgen_core::Source, - /// Type script definitions which will become the export object - export_object: wit_bindgen_core::Source, - - /// Core module count - core_module_cnt: usize, - - /// Various options for code generation. - opts: Opts, - - /// List of all intrinsics emitted to `src` so far. - all_intrinsics: BTreeSet, -} - -#[derive(Debug, Clone, Default)] -#[cfg_attr(feature = "clap", derive(clap::Args))] -pub struct Opts { - /// Disables generation of `*.d.ts` files and instead only generates `*.js` - /// source files. - #[cfg_attr(feature = "clap", arg(long))] - pub no_typescript: bool, - /// Provide a custom JS instantiation API for the component instead - /// of the direct importable native ESM output. - #[cfg_attr( - feature = "clap", - arg( - long, - short = 'I', - conflicts_with = "compatibility", - conflicts_with = "no-compatibility", - conflicts_with = "compat" - ) - )] - pub instantiation: bool, - /// Comma-separated list of "from-specifier=./to-specifier.js" mappings of - /// component import specifiers to JS import specifiers. - #[cfg_attr(feature = "clap", arg(long), clap(value_parser = maps_str_to_map))] - pub map: Option>, - /// Enables all compat flags: --tla-compat. - #[cfg_attr(feature = "clap", arg(long, short = 'c'))] - pub compat: bool, - /// Disables compatibility in Node.js without a fetch global. - #[cfg_attr(feature = "clap", arg(long, group = "no-compatibility"))] - pub no_nodejs_compat: bool, - /// Set the cutoff byte size for base64 inlining core Wasm in instantiation mode - /// (set to 0 to disable all base64 inlining) - #[cfg_attr(feature = "clap", arg(long, short = 'b', default_value_t = 5000))] - pub base64_cutoff: usize, - /// Enables compatibility for JS environments without top-level await support - /// via an async $init promise export to wait for instead. - #[cfg_attr(feature = "clap", arg(long, group = "compatibility"))] - pub tla_compat: bool, - /// Disable verification of component Wasm data structures when - /// lifting as a production optimization - #[cfg_attr(feature = "clap", arg(long))] - pub valid_lifting_optimization: bool, -} - -impl Opts { - pub fn build(self) -> Result> { - let mut gen = Js::default(); - gen.opts = self; - if gen.opts.compat { - gen.opts.tla_compat = true; - } - Ok(Box::new(gen)) - } -} - -#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] -enum Intrinsic { - Base64Compile, - ClampGuest, - ComponentError, - DataView, - F32ToI32, - F64ToI64, - FetchCompile, - GetErrorPayload, - HasOwnProperty, - I32ToF32, - I64ToF64, - InstantiateCore, - IsLE, - ThrowInvalidBool, - ThrowUninitialized, - /// Implementation of https://tc39.es/ecma262/#sec-tobigint64. - ToBigInt64, - /// Implementation of https://tc39.es/ecma262/#sec-tobiguint64. - ToBigUint64, - /// Implementation of https://tc39.es/ecma262/#sec-toint16. - ToInt16, - /// Implementation of https://tc39.es/ecma262/#sec-toint32. - ToInt32, - /// Implementation of https://tc39.es/ecma262/#sec-toint8. - ToInt8, - /// Implementation of https://tc39.es/ecma262/#sec-tostring. - ToString, - /// Implementation of https://tc39.es/ecma262/#sec-touint16. - ToUint16, - /// Implementation of https://tc39.es/ecma262/#sec-touint32. - ToUint32, - /// Implementation of https://tc39.es/ecma262/#sec-touint8. - ToUint8, - Utf16Decoder, - Utf16Encode, - Utf8Decoder, - Utf8Encode, - Utf8EncodedLen, - ValidateGuestChar, - ValidateHostChar, -} - -impl Intrinsic { - fn name(&self) -> &'static str { - match self { - Intrinsic::Base64Compile => "base64Compile", - Intrinsic::ClampGuest => "clampGuest", - Intrinsic::ComponentError => "ComponentError", - Intrinsic::DataView => "dataView", - Intrinsic::F32ToI32 => "f32ToI32", - Intrinsic::F64ToI64 => "f64ToI64", - Intrinsic::GetErrorPayload => "getErrorPayload", - Intrinsic::HasOwnProperty => "hasOwnProperty", - Intrinsic::I32ToF32 => "i32ToF32", - Intrinsic::I64ToF64 => "i64ToF64", - Intrinsic::InstantiateCore => "instantiateCore", - Intrinsic::IsLE => "isLE", - Intrinsic::FetchCompile => "fetchCompile", - Intrinsic::ThrowInvalidBool => "throwInvalidBool", - Intrinsic::ThrowUninitialized => "throwUninitialized", - Intrinsic::ToBigInt64 => "toInt64", - Intrinsic::ToBigUint64 => "toUint64", - Intrinsic::ToInt16 => "toInt16", - Intrinsic::ToInt32 => "toInt32", - Intrinsic::ToInt8 => "toInt8", - Intrinsic::ToString => "toString", - Intrinsic::ToUint16 => "toUint16", - Intrinsic::ToUint32 => "toUint32", - Intrinsic::ToUint8 => "toUint8", - Intrinsic::Utf16Decoder => "utf16Decoder", - Intrinsic::Utf16Encode => "utf16Encode", - Intrinsic::Utf8Decoder => "utf8Decoder", - Intrinsic::Utf8Encode => "utf8Encode", - Intrinsic::Utf8EncodedLen => "utf8EncodedLen", - Intrinsic::ValidateGuestChar => "validateGuestChar", - Intrinsic::ValidateHostChar => "validateHostChar", - } - } -} - -/// Used to generate a `*.d.ts` file for each imported and exported interface for -/// a component. -/// -/// This generated source does not contain any actual JS runtime code, it's just -/// typescript definitions. -struct JsInterface<'a> { - src: Source, - gen: &'a mut Js, - resolve: &'a Resolve, - needs_ty_option: bool, - needs_ty_result: bool, -} - -impl ComponentGenerator for Js { - fn instantiate( - &mut self, - component: &Component, - modules: &PrimaryMap>, - resolve: &Resolve, - id: WorldId, - ) { - self.core_module_cnt = modules.len(); - let world = &resolve.worlds[id]; - - // Generate the TypeScript definition of the `instantiate` function - // which is the main workhorse of the generated bindings. - if self.opts.instantiation { - let camel = world.name.to_upper_camel_case(); - uwriteln!( - self.src.ts, - " - /** - * Instantiates this component with the provided imports and - * returns a map of all the exports of the component. - * - * This function is intended to be similar to the - * `WebAssembly.instantiate` function. The second `imports` - * argument is the \"import object\" for wasm, except here it - * uses component-model-layer types instead of core wasm - * integers/numbers/etc. - * - * The first argument to this function, `compileCore`, is - * used to compile core wasm modules within the component. - * Components are composed of core wasm modules and this callback - * will be invoked per core wasm module. The caller of this - * function is responsible for reading the core wasm module - * identified by `path` and returning its compiled - * WebAssembly.Module object. This would use `compileStreaming` - * on the web, for example. - */ - export function instantiate( - compileCore: (path: string, imports: Record) => Promise, - imports: typeof ImportObject, - instantiateCore?: (module: WebAssembly.Module, imports: Record) => Promise - ): Promise; - ", - ); - } - - // bindings is the actual `instantiate` method itself, created by this - // structure. - let mut instantiator = Instantiator { - src: Source::default(), - sizes: SizeAlign::default(), - gen: self, - modules, - instances: Default::default(), - resolve, - world: id, - component, - }; - instantiator.sizes.fill(resolve); - instantiator.instantiate(); - instantiator.gen.src.js(&instantiator.src.js); - instantiator.gen.src.js_init(&instantiator.src.js_init); - assert!(instantiator.src.ts.is_empty()); - } - - fn finish_component(&mut self, name: &str, files: &mut Files) { - let mut output = wit_bindgen_core::Source::default(); - let mut compilation_promises = wit_bindgen_core::Source::default(); - - // Setup the compilation data and compilation promises - let mut removed = BTreeSet::new(); - for i in 0..self.core_module_cnt { - let local_name = format!("module{}", i); - let mut name_idx = self.core_file_name(name, i as u32); - if self.opts.instantiation { - uwriteln!( - compilation_promises, - "const {local_name} = compileCore('{name_idx}');" - ); - } else { - if files.get_size(&name_idx).unwrap() < self.opts.base64_cutoff { - assert!(removed.insert(i)); - let data = files.remove(&name_idx).unwrap(); - uwriteln!( - compilation_promises, - "const {local_name} = {}('{}');", - self.intrinsic(Intrinsic::Base64Compile), - base64::encode(&data) - ); - } else { - // Maintain numerical file orderings when a previous file was - // inlined - if let Some(&replacement) = removed.iter().next() { - assert!(removed.remove(&replacement) && removed.insert(i)); - let data = files.remove(&name_idx).unwrap(); - name_idx = self.core_file_name(name, replacement as u32); - files.push(&name_idx, &data); - } - uwriteln!( - compilation_promises, - "const {local_name} = {}(new URL('./{name_idx}', import.meta.url));", - self.intrinsic(Intrinsic::FetchCompile) - ); - } - } - } - - if self.opts.instantiation { - uwrite!( - output, - "\ - {} - export async function instantiate(compileCore, imports, instantiateCore = WebAssembly.instantiate) {{ - {} - {}\ - {}; - }} - ", - &self.src.js_intrinsics as &str, - &compilation_promises as &str, - &self.src.js_init as &str, - &self.src.js as &str, - ); - } else { - // Import statements render first in JS instance mode - for (specifier, bindings) in &self.imports { - uwrite!(output, "import {{"); - let mut first = true; - for (external, local) in bindings { - if first { - output.push_str(" "); - } else { - output.push_str(", "); - } - uwrite!(output, "{} as {}", external, local); - first = false; - } - if !first { - output.push_str(" "); - } - uwrite!(output, "}} from '{}';\n", specifier); - } - - let (maybe_init_export, maybe_init) = if self.opts.tla_compat { - uwriteln!( - self.src.ts, - " - export const $init: Promise;" - ); - uwriteln!(self.src.js_init, "_initialized = true;"); - ( - "\ - let _initialized = false; - export ", - "", - ) - } else { - ( - "", - " - await $init; - ", - ) - }; - - uwrite!( - output, - "\ - {} - {} - {maybe_init_export}const $init = (async() => {{ - {}\ - {}\ - }})(); - {maybe_init}\ - ", - &self.src.js_intrinsics as &str, - &self.src.js as &str, - &compilation_promises as &str, - &self.src.js_init as &str, - ); - } - - let mut bytes = output.as_bytes(); - // strip leading newline - if bytes[0] == b'\n' { - bytes = &bytes[1..]; - } - files.push(&format!("{name}.js"), bytes); - if !self.opts.no_typescript { - files.push(&format!("{name}.d.ts"), self.src.ts.as_bytes()); - } - } -} - -impl WorldGenerator for Js { - fn import_interface( - &mut self, - resolve: &Resolve, - name: &str, - id: InterfaceId, - files: &mut Files, - ) { - self.generate_interface( - name, - resolve, - id, - "imports", - "Imports", - files, - AbiVariant::GuestImport, - ); - let camel = name.to_upper_camel_case(); - uwriteln!( - self.import_object, - "export const {name}: typeof {camel}Imports;" - ); - } - - fn import_funcs( - &mut self, - resolve: &Resolve, - _world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) { - let mut gen = self.js_interface(resolve); - for (_, func) in funcs { - gen.ts_func(func, AbiVariant::GuestImport); - } - gen.gen.import_object.push_str(&gen.src.ts); - assert!(gen.src.js.is_empty()); - } - - fn export_interface( - &mut self, - resolve: &Resolve, - name: &str, - id: InterfaceId, - files: &mut Files, - ) { - self.generate_interface( - name, - resolve, - id, - "exports", - "Exports", - files, - AbiVariant::GuestExport, - ); - let camel = name.to_upper_camel_case(); - uwriteln!( - self.export_object, - "export const {name}: typeof {camel}Exports;" - ); - } - - fn export_funcs( - &mut self, - resolve: &Resolve, - _world: WorldId, - funcs: &[(&str, &Function)], - _files: &mut Files, - ) { - let mut gen = self.js_interface(resolve); - for (_, func) in funcs { - gen.ts_func(func, AbiVariant::GuestExport); - } - gen.gen.export_object.push_str(&gen.src.ts); - assert!(gen.src.js.is_empty()); - } - - fn finish(&mut self, resolve: &Resolve, id: WorldId, _files: &mut Files) { - let world = &resolve.worlds[id]; - let camel = world.name.to_upper_camel_case(); - - // Generate a type definition for the import object to type-check - // all imports to the component. - // - // With the current representation of a "world" this is an import object - // per-imported-interface where the type of that field is defined by the - // interface itself. - if self.opts.instantiation { - uwriteln!(self.src.ts, "export namespace ImportObject {{"); - self.src.ts(&self.import_object); - uwriteln!(self.src.ts, "}}"); - } - - // Generate a type definition for the export object from instantiating - // the component. - if self.opts.instantiation { - uwriteln!(self.src.ts, "export namespace {camel} {{",); - self.src.ts(&self.export_object); - uwriteln!(self.src.ts, "}}"); - } else { - self.src.ts(&self.export_object); - } - } -} - -impl Js { - fn generate_interface( - &mut self, - name: &str, - resolve: &Resolve, - id: InterfaceId, - dir: &str, - extra: &str, - files: &mut Files, - abi: AbiVariant, - ) { - let camel = name.to_upper_camel_case(); - let mut gen = self.js_interface(resolve); - gen.types(id); - gen.post_types(); - - uwriteln!(gen.src.ts, "export namespace {camel} {{"); - for (_, func) in resolve.interfaces[id].functions.iter() { - gen.ts_func(func, abi); - } - uwriteln!(gen.src.ts, "}}"); - - assert!(gen.src.js.is_empty()); - if !gen.gen.opts.no_typescript { - files.push(&format!("{dir}/{name}.d.ts"), gen.src.ts.as_bytes()); - } - - uwriteln!( - self.src.ts, - "import {{ {camel} as {camel}{extra} }} from './{dir}/{name}';", - ); - } - - fn map_import(&self, impt: &str) -> String { - if let Some(map) = self.opts.map.as_ref() { - if let Some(mapping) = map.get(impt) { - return mapping.into(); - } - } - impt.into() - } - - fn js_interface<'a>(&'a mut self, resolve: &'a Resolve) -> JsInterface<'a> { - JsInterface { - src: Source::default(), - gen: self, - resolve, - needs_ty_option: false, - needs_ty_result: false, - } - } - - /// Emits the intrinsic `i` to this file and then returns the name of the - /// intrinsic. - fn intrinsic(&mut self, i: Intrinsic) -> String { - let name = i.name().to_string(); - if !self.all_intrinsics.insert(i) { - return name; - } - - if (i == Intrinsic::I32ToF32 && !self.all_intrinsics.contains(&Intrinsic::F32ToI32)) - || (i == Intrinsic::F32ToI32 && !self.all_intrinsics.contains(&Intrinsic::I32ToF32)) - { - self.src.js_intrinsics( - " - const i32ToF32I = new Int32Array(1); - const i32ToF32F = new Float32Array(i32ToF32I.buffer); - ", - ); - } - if (i == Intrinsic::I64ToF64 && !self.all_intrinsics.contains(&Intrinsic::F64ToI64)) - || (i == Intrinsic::F64ToI64 && !self.all_intrinsics.contains(&Intrinsic::I64ToF64)) - { - self.src.js_intrinsics( - " - const i64ToF64I = new BigInt64Array(1); - const i64ToF64F = new Float64Array(i64ToF64I.buffer); - ", - ); - } - - match i { - Intrinsic::ClampGuest => self.src.js_intrinsics(" - function clampGuest(i, min, max) { - if (i < min || i > max) \ - throw new TypeError(`must be between ${min} and ${max}`); - return i; - } - "), - - Intrinsic::HasOwnProperty => self.src.js_intrinsics(" - const hasOwnProperty = Object.prototype.hasOwnProperty; - "), - - Intrinsic::GetErrorPayload => { - let hop = self.intrinsic(Intrinsic::HasOwnProperty); - uwrite!(self.src.js_intrinsics, " - function getErrorPayload(e) {{ - if ({hop}.call(e, 'payload')) return e.payload; - if ({hop}.call(e, 'message')) return String(e.message); - return String(e); - }} - ") - }, - - Intrinsic::ComponentError => self.src.js_intrinsics(" - class ComponentError extends Error { - constructor (value) { - const enumerable = typeof value !== 'string'; - super(enumerable ? `${String(value)} (see error.payload)` : value); - Object.defineProperty(this, 'payload', { value, enumerable }); - } - } - "), - - Intrinsic::DataView => self.src.js_intrinsics(" - let dv = new DataView(new ArrayBuffer()); - const dataView = mem => dv.buffer === mem.buffer ? dv : dv = new DataView(mem.buffer); - "), - - Intrinsic::FetchCompile => if !self.opts.no_nodejs_compat { - self.src.js_intrinsics(" - const isNode = typeof process !== 'undefined' && process.versions && process.versions.node; - let _fs; - async function fetchCompile (url) { - if (isNode) { - _fs = _fs || await import('fs/promises'); - return WebAssembly.compile(await _fs.readFile(url)); - } - return fetch(url).then(WebAssembly.compileStreaming); - } - ") - } else { - self.src.js_intrinsics(" - const fetchCompile = url => fetch(url).then(WebAssembly.compileStreaming); - ") - }, - - Intrinsic::Base64Compile => if !self.opts.no_nodejs_compat { - self.src.js_intrinsics(" - const base64Compile = str => WebAssembly.compile(typeof Buffer !== 'undefined' ? Buffer.from(str, 'base64') : Uint8Array.from(atob(str), b => b.charCodeAt(0))); - ") - } else { - self.src.js_intrinsics(" - const base64Compile = str => WebAssembly.compile(Uint8Array.from(atob(str), b => b.charCodeAt(0))); - ") - }, - - Intrinsic::InstantiateCore => if !self.opts.instantiation { - self.src.js_intrinsics(" - const instantiateCore = WebAssembly.instantiate; - ") - }, - - Intrinsic::IsLE => self.src.js_intrinsics(" - const isLE = new Uint8Array(new Uint16Array([1]).buffer)[0] === 1; - "), - - Intrinsic::ValidateGuestChar => self.src.js_intrinsics(" - function validateGuestChar(i) { - if ((i > 0x10ffff) || (i >= 0xd800 && i <= 0xdfff)) \ - throw new TypeError(`not a valid char`); - return String.fromCodePoint(i); - } - "), - - // TODO: this is incorrect. It at least allows strings of length > 0 - // but it probably doesn't do the right thing for unicode or invalid - // utf16 strings either. - Intrinsic::ValidateHostChar => self.src.js_intrinsics(" - function validateHostChar(s) { - if (typeof s !== 'string') \ - throw new TypeError(`must be a string`); - return s.codePointAt(0); - } - "), - - - Intrinsic::ToInt32 => self.src.js_intrinsics(" - function toInt32(val) { - return val >> 0; - } - "), - Intrinsic::ToUint32 => self.src.js_intrinsics(" - function toUint32(val) { - return val >>> 0; - } - "), - - Intrinsic::ToInt16 => self.src.js_intrinsics(" - function toInt16(val) { - val >>>= 0; - val %= 2 ** 16; - if (val >= 2 ** 15) { - val -= 2 ** 16; - } - return val; - } - "), - Intrinsic::ToUint16 => self.src.js_intrinsics(" - function toUint16(val) { - val >>>= 0; - val %= 2 ** 16; - return val; - } - "), - Intrinsic::ToInt8 => self.src.js_intrinsics(" - function toInt8(val) { - val >>>= 0; - val %= 2 ** 8; - if (val >= 2 ** 7) { - val -= 2 ** 8; - } - return val; - } - "), - Intrinsic::ToUint8 => self.src.js_intrinsics(" - function toUint8(val) { - val >>>= 0; - val %= 2 ** 8; - return val; - } - "), - - Intrinsic::ToBigInt64 => self.src.js_intrinsics(" - const toInt64 = val => BigInt.asIntN(64, val); - "), - Intrinsic::ToBigUint64 => self.src.js_intrinsics(" - const toUint64 = val => BigInt.asUintN(64, val); - "), - - // Calling `String` almost directly calls `ToString`, except that it also allows symbols, - // which is why we have the symbol-rejecting branch above. - // - // Definition of `String`: https://tc39.es/ecma262/#sec-string-constructor-string-value - Intrinsic::ToString => self.src.js_intrinsics(" - function toString(val) { - if (typeof val === 'symbol') throw new TypeError('symbols cannot be converted to strings'); - return String(val); - } - "), - - Intrinsic::I32ToF32 => self.src.js_intrinsics(" - const i32ToF32 = i => (i32ToF32I[0] = i, i32ToF32F[0]); - "), - Intrinsic::F32ToI32 => self.src.js_intrinsics(" - const f32ToI32 = f => (i32ToF32F[0] = f, i32ToF32I[0]); - "), - Intrinsic::I64ToF64 => self.src.js_intrinsics(" - const i64ToF64 = i => (i64ToF64I[0] = i, i64ToF64F[0]); - "), - Intrinsic::F64ToI64 => self.src.js_intrinsics(" - const f64ToI64 = f => (i64ToF64F[0] = f, i64ToF64I[0]); - "), - - Intrinsic::Utf8Decoder => self.src.js_intrinsics(" - const utf8Decoder = new TextDecoder(); - "), - - Intrinsic::Utf16Decoder => self.src.js_intrinsics(" - const utf16Decoder = new TextDecoder('utf-16'); - "), - - Intrinsic::Utf8EncodedLen => {}, - - Intrinsic::Utf8Encode => self.src.js_intrinsics(" - const utf8Encoder = new TextEncoder(); - - let utf8EncodedLen = 0; - function utf8Encode(s, realloc, memory) { - if (typeof s !== 'string') \ - throw new TypeError('expected a string'); - if (s.length === 0) { - utf8EncodedLen = 0; - return 1; - } - let allocLen = 0; - let ptr = 0; - let writtenTotal = 0; - while (s.length > 0) { - ptr = realloc(ptr, allocLen, 1, allocLen + s.length); - allocLen += s.length; - const { read, written } = utf8Encoder.encodeInto( - s, - new Uint8Array(memory.buffer, ptr + writtenTotal, allocLen - writtenTotal), - ); - writtenTotal += written; - s = s.slice(read); - } - if (allocLen > writtenTotal) - ptr = realloc(ptr, allocLen, 1, writtenTotal); - utf8EncodedLen = writtenTotal; - return ptr; - } - "), - - Intrinsic::Utf16Encode => { - let is_le = self.intrinsic(Intrinsic::IsLE); - uwrite!(self.src.js_intrinsics, " - function utf16Encode (str, realloc, memory) {{ - const len = str.length, ptr = realloc(0, 0, 2, len * 2), out = new Uint16Array(memory.buffer, ptr, len); - let i = 0; - if ({is_le}) {{ - while (i < len) out[i] = str.charCodeAt(i++); - }} else {{ - while (i < len) {{ - const ch = str.charCodeAt(i); - out[i++] = (ch & 0xff) << 8 | ch >>> 8; - }} - }} - return ptr; - }} - "); - }, - - Intrinsic::ThrowInvalidBool => self.src.js_intrinsics(" - function throwInvalidBool() { - throw new TypeError('invalid variant discriminant for bool'); - } - "), - - Intrinsic::ThrowUninitialized => self.src.js_intrinsics(" - function throwUninitialized() { - throw new TypeError('Wasm uninitialized use `await $init` first'); - } - "), - } - - name - } - - fn array_ty(&self, resolve: &Resolve, ty: &Type) -> Option<&'static str> { - match ty { - Type::Bool => None, - Type::U8 => Some("Uint8Array"), - Type::S8 => Some("Int8Array"), - Type::U16 => Some("Uint16Array"), - Type::S16 => Some("Int16Array"), - Type::U32 => Some("Uint32Array"), - Type::S32 => Some("Int32Array"), - Type::U64 => Some("BigUint64Array"), - Type::S64 => Some("BigInt64Array"), - Type::Float32 => Some("Float32Array"), - Type::Float64 => Some("Float64Array"), - Type::Char => None, - Type::String => None, - Type::Id(id) => match &resolve.types[*id].kind { - TypeDefKind::Type(t) => self.array_ty(resolve, t), - _ => None, - }, - } - } - - /// Returns whether `null` is a valid value of type `ty` - fn maybe_null(&self, resolve: &Resolve, ty: &Type) -> bool { - self.as_nullable(resolve, ty).is_some() - } - - /// Tests whether `ty` can be represented with `null`, and if it can then - /// the "other type" is returned. If `Some` is returned that means that `ty` - /// is `null | `. If `None` is returned that means that `null` can't - /// be used to represent `ty`. - fn as_nullable<'a>(&self, resolve: &'a Resolve, ty: &'a Type) -> Option<&'a Type> { - let id = match ty { - Type::Id(id) => *id, - _ => return None, - }; - match &resolve.types[id].kind { - // If `ty` points to an `option`, then `ty` can be represented - // with `null` if `t` itself can't be represented with null. For - // example `option>` can't be represented with `null` - // since that's ambiguous if it's `none` or `some(none)`. - // - // Note, oddly enough, that `option>>` can be - // represented as `null` since: - // - // * `null` => `none` - // * `{ tag: "none" }` => `some(none)` - // * `{ tag: "some", val: null }` => `some(some(none))` - // * `{ tag: "some", val: 1 }` => `some(some(some(1)))` - // - // It's doubtful anyone would actually rely on that though due to - // how confusing it is. - TypeDefKind::Option(t) => { - if !self.maybe_null(resolve, t) { - Some(t) - } else { - None - } - } - TypeDefKind::Type(t) => self.as_nullable(resolve, t), - _ => None, - } - } -} - -/// Helper structure used to generate the `instantiate` method of a component. -/// -/// This is the main structure for parsing the output of Wasmtime. -struct Instantiator<'a> { - src: Source, - gen: &'a mut Js, - modules: &'a PrimaryMap>, - instances: PrimaryMap, - resolve: &'a Resolve, - world: WorldId, - sizes: SizeAlign, - component: &'a Component, -} - -impl Instantiator<'_> { - fn instantiate(&mut self) { - // To avoid uncaught promise rejection errors, we attach an intermediate - // Promise.all with a rejection handler, if there are multiple promises. - if self.modules.len() > 1 { - self.src.js_init.push_str("Promise.all(["); - for i in 0..self.modules.len() { - if i > 0 { - self.src.js_init.push_str(", "); - } - self.src.js_init.push_str(&format!("module{}", i)); - } - uwriteln!(self.src.js_init, "]).catch(() => {{}});"); - } - - for init in self.component.initializers.iter() { - self.instantiation_global_initializer(init); - } - - if self.gen.opts.instantiation { - let js_init = mem::take(&mut self.src.js_init); - self.src.js.push_str(&js_init); - self.src.js("return "); - } - - self.exports(&self.component.exports); - } - - fn instantiation_global_initializer(&mut self, init: &GlobalInitializer) { - match init { - GlobalInitializer::InstantiateModule(m) => match m { - InstantiateModule::Static(idx, args) => self.instantiate_static_module(*idx, args), - // This is only needed when instantiating an imported core wasm - // module which while easy to implement here is not possible to - // test at this time so it's left unimplemented. - InstantiateModule::Import(..) => unimplemented!(), - }, - GlobalInitializer::LowerImport(i) => { - self.lower_import(i); - } - GlobalInitializer::ExtractMemory(m) => { - let def = self.core_export(&m.export); - let idx = m.index.as_u32(); - uwriteln!(self.src.js, "let memory{idx};"); - uwriteln!(self.src.js_init, "memory{idx} = {def};"); - } - GlobalInitializer::ExtractRealloc(r) => { - let def = self.core_def(&r.def); - let idx = r.index.as_u32(); - uwriteln!(self.src.js, "let realloc{idx};"); - uwriteln!(self.src.js_init, "realloc{idx} = {def};",); - } - GlobalInitializer::ExtractPostReturn(p) => { - let def = self.core_def(&p.def); - let idx = p.index.as_u32(); - uwriteln!(self.src.js, "let postReturn{idx};"); - uwriteln!(self.src.js_init, "postReturn{idx} = {def};"); - } - - // This is only used for a "degenerate component" which internally - // has a function that always traps. While this should be trivial to - // implement (generate a JS function that always throws) there's no - // way to test this at this time so leave this unimplemented. - GlobalInitializer::AlwaysTrap(_) => unimplemented!(), - - // This is only used when the component exports core wasm modules, - // but that's not possible to test right now so leave these as - // unimplemented. - GlobalInitializer::SaveStaticModule(_) => unimplemented!(), - GlobalInitializer::SaveModuleImport(_) => unimplemented!(), - - // This is required when strings pass between components within a - // component and may change encodings. This is left unimplemented - // for now since it can't be tested and additionally JS doesn't - // support multi-memory which transcoders rely on anyway. - GlobalInitializer::Transcoder(_) => unimplemented!(), - } - } - - fn instantiate_static_module(&mut self, idx: StaticModuleIndex, args: &[CoreDef]) { - let module = &self.modules[idx].module; - - // Build a JS "import object" which represents `args`. The `args` is a - // flat representation which needs to be zip'd with the list of names to - // correspond to the JS wasm embedding API. This is one of the major - // differences between Wasmtime's and JS's embedding API. - let mut import_obj = BTreeMap::new(); - assert_eq!(module.imports().len(), args.len()); - for ((module, name, _), arg) in module.imports().zip(args) { - let def = self.core_def(arg); - let dst = import_obj.entry(module).or_insert(BTreeMap::new()); - let prev = dst.insert(name, def); - assert!(prev.is_none()); - } - let mut imports = String::new(); - if !import_obj.is_empty() { - imports.push_str(", {\n"); - for (module, names) in import_obj { - if is_js_identifier(module) { - imports.push_str(module); - } else { - uwrite!(imports, "'{module}'"); - } - imports.push_str(": {\n"); - for (name, val) in names { - if is_js_identifier(name) { - imports.push_str(name); - } else { - uwrite!(imports, "'{name}'"); - } - uwriteln!(imports, ": {val},"); - } - imports.push_str("},\n"); - } - imports.push_str("}"); - } - - let i = self.instances.push(idx); - let iu32 = i.as_u32(); - let instantiate = self.gen.intrinsic(Intrinsic::InstantiateCore); - uwriteln!(self.src.js, "let exports{iu32};"); - uwriteln!( - self.src.js_init, - "({{ exports: exports{iu32} }} = await {instantiate}(await module{}{imports}));", - idx.as_u32() - ); - } - - fn lower_import(&mut self, import: &LowerImport) { - // Determine the `Interface` that this import corresponds to. At this - // time `wit-component` only supports root-level imports of instances - // where instances export functions. - let (import_index, path) = &self.component.imports[import.import]; - let (import_name, _import_ty) = &self.component.import_types[*import_index]; - let func = match &self.resolve.worlds[self.world].imports[import_name.as_str()] { - WorldItem::Function(f) => { - assert_eq!(path.len(), 0); - f - } - WorldItem::Interface(i) => { - assert_eq!(path.len(), 1); - &self.resolve.interfaces[*i].functions[&path[0]] - } - }; - - let index = import.index.as_u32(); - let callee = format!("lowering{index}Callee"); - - let import_specifier = self.gen.map_import(import_name); - - let id = func.name.to_lower_camel_case(); - - // instance imports are otherwise hoisted - if self.gen.opts.instantiation { - uwriteln!( - self.src.js, - "const {callee} = imports{}.{};", - if is_js_identifier(&import_specifier) { - format!(".{}", import_specifier) - } else { - format!("['{}']", import_specifier) - }, - id - ); - } else { - let imports_vec = self - .gen - .imports - .entry(import_specifier) - .or_insert(Vec::new()); - imports_vec.push((id, callee.clone())); - } - - uwrite!(self.src.js_init, "\nfunction lowering{index}"); - let nparams = self - .resolve - .wasm_signature(AbiVariant::GuestImport, func) - .params - .len(); - let prev = mem::take(&mut self.src); - self.bindgen( - nparams, - callee, - &import.options, - func, - AbiVariant::GuestImport, - ); - let latest = mem::replace(&mut self.src, prev); - assert!(latest.ts.is_empty()); - assert!(latest.js_init.is_empty()); - self.src.js_intrinsics(&latest.js_intrinsics); - self.src.js_init(&latest.js); - uwriteln!(self.src.js_init, ""); - } - - fn bindgen( - &mut self, - nparams: usize, - callee: String, - opts: &CanonicalOptions, - func: &Function, - abi: AbiVariant, - ) { - let memory = match opts.memory { - Some(idx) => Some(format!("memory{}", idx.as_u32())), - None => None, - }; - let realloc = match opts.realloc { - Some(idx) => Some(format!("realloc{}", idx.as_u32())), - None => None, - }; - let post_return = match opts.post_return { - Some(idx) => Some(format!("postReturn{}", idx.as_u32())), - None => None, - }; - - self.src.js("("); - let mut params = Vec::new(); - for i in 0..nparams { - if i > 0 { - self.src.js(", "); - } - let param = format!("arg{i}"); - self.src.js(¶m); - params.push(param); - } - uwriteln!(self.src.js, ") {{"); - - if self.gen.opts.tla_compat && matches!(abi, AbiVariant::GuestExport) { - let throw_uninitialized = self.gen.intrinsic(Intrinsic::ThrowUninitialized); - uwrite!( - self.src.js, - "\ - if (!_initialized) {throw_uninitialized}(); - " - ); - } - - let mut f = FunctionBindgen { - sizes: &self.sizes, - gen: self.gen, - err: if func.results.throws(self.resolve).is_some() { - match abi { - AbiVariant::GuestExport => ErrHandling::ThrowResultErr, - AbiVariant::GuestImport => ErrHandling::ResultCatchHandler, - } - } else { - ErrHandling::None - }, - block_storage: Vec::new(), - blocks: Vec::new(), - callee, - memory, - realloc, - tmp: 0, - params, - post_return, - encoding: opts.string_encoding, - src: Source::default(), - }; - self.resolve.call( - abi, - match abi { - AbiVariant::GuestImport => LiftLower::LiftArgsLowerResults, - AbiVariant::GuestExport => LiftLower::LowerArgsLiftResults, - }, - func, - &mut f, - ); - let FunctionBindgen { src, .. } = f; - - self.src.js(&src.js); - assert!(src.ts.is_empty()); - self.src.js("}"); - } - - fn core_def(&self, def: &CoreDef) -> String { - match def { - CoreDef::Export(e) => self.core_export(e), - CoreDef::Lowered(i) => format!("lowering{}", i.as_u32()), - CoreDef::AlwaysTrap(_) => unimplemented!(), - CoreDef::InstanceFlags(_) => unimplemented!(), - CoreDef::Transcoder(_) => unimplemented!(), - } - } - - fn core_export(&self, export: &CoreExport) -> String - where - T: Into + Copy, - { - let name = match &export.item { - ExportItem::Index(idx) => { - let module = &self.modules[self.instances[export.instance]].module; - let idx = (*idx).into(); - module - .exports - .iter() - .filter_map(|(name, i)| if *i == idx { Some(name) } else { None }) - .next() - .unwrap() - } - ExportItem::Name(s) => s, - }; - let i = export.instance.as_u32() as usize; - if is_js_identifier(name) { - format!("exports{i}.{name}") - } else { - format!("exports{i}['{name}']") - } - } - - fn exports(&mut self, exports: &IndexMap) { - if exports.is_empty() { - if self.gen.opts.instantiation { - self.src.js("{}"); - } - return; - } - - if self.gen.opts.instantiation { - uwriteln!(self.src.js, "{{"); - } - - for (name, export) in exports { - let item = &self.resolve.worlds[self.world].exports[name]; - let camel = name.to_lower_camel_case(); - match export { - Export::LiftedFunction { - ty: _, - func, - options, - } => { - self.export_bindgen( - name, - None, - func, - options, - match item { - WorldItem::Function(f) => f, - WorldItem::Interface(_) => unreachable!(), - }, - ); - } - Export::Instance(exports) => { - let id = match item { - WorldItem::Interface(id) => *id, - WorldItem::Function(_) => unreachable!(), - }; - if self.gen.opts.instantiation { - uwriteln!(self.src.js, "{camel}: {{"); - } else { - uwriteln!(self.src.js, "export const {camel} = {{"); - } - for (func_name, export) in exports { - let (func, options) = match export { - Export::LiftedFunction { func, options, .. } => (func, options), - Export::Type(_) => continue, // ignored - _ => unreachable!(), - }; - self.export_bindgen( - func_name, - Some(name), - func, - options, - &self.resolve.interfaces[id].functions[func_name], - ); - } - self.src.js("\n}"); - if self.gen.opts.instantiation { - self.src.js(",\n"); - } else { - self.src.js(";\n"); - } - } - - // ignore type exports for now - Export::Type(_) => {} - - // This can't be tested at this time so leave it unimplemented - Export::Module(_) => unimplemented!(), - } - } - if self.gen.opts.instantiation { - self.src.js("}"); - } - } - - fn export_bindgen( - &mut self, - name: &str, - instance_name: Option<&str>, - def: &CoreDef, - options: &CanonicalOptions, - func: &Function, - ) { - let name = name.to_lower_camel_case(); - if self.gen.opts.instantiation || instance_name.is_some() { - self.src.js.push_str(&name); - } else { - uwrite!(self.src.js, "\nexport function {name}"); - } - let callee = self.core_def(def); - self.bindgen( - func.params.len(), - callee, - options, - func, - AbiVariant::GuestExport, - ); - if self.gen.opts.instantiation || instance_name.is_some() { - self.src.js(",\n"); - } else { - self.src.js("\n"); - } - } -} - -#[derive(Copy, Clone)] -enum Mode { - Lift, - Lower, -} - -impl<'a> JsInterface<'a> { - fn docs_raw(&mut self, docs: &str) { - self.src.ts("/**\n"); - for line in docs.lines() { - self.src.ts(&format!(" * {}\n", line)); - } - self.src.ts(" */\n"); - } - - fn docs(&mut self, docs: &Docs) { - match &docs.contents { - Some(docs) => self.docs_raw(docs), - None => return, - } - } - - fn array_ty(&self, ty: &Type) -> Option<&'static str> { - self.gen.array_ty(self.resolve, ty) - } - - fn print_ty(&mut self, ty: &Type, mode: Mode) { - match ty { - Type::Bool => self.src.ts("boolean"), - Type::U8 - | Type::S8 - | Type::U16 - | Type::S16 - | Type::U32 - | Type::S32 - | Type::Float32 - | Type::Float64 => self.src.ts("number"), - Type::U64 | Type::S64 => self.src.ts("bigint"), - Type::Char => self.src.ts("string"), - Type::String => self.src.ts("string"), - Type::Id(id) => { - let ty = &self.resolve.types[*id]; - if let Some(name) = &ty.name { - return self.src.ts(&name.to_upper_camel_case()); - } - match &ty.kind { - TypeDefKind::Type(t) => self.print_ty(t, mode), - TypeDefKind::Tuple(t) => self.print_tuple(t, mode), - TypeDefKind::Record(_) => panic!("anonymous record"), - TypeDefKind::Flags(_) => panic!("anonymous flags"), - TypeDefKind::Enum(_) => panic!("anonymous enum"), - TypeDefKind::Union(_) => panic!("anonymous union"), - TypeDefKind::Option(t) => { - if self.maybe_null(t) { - self.needs_ty_option = true; - self.src.ts("Option<"); - self.print_ty(t, mode); - self.src.ts(">"); - } else { - self.print_ty(t, mode); - self.src.ts(" | null"); - } - } - TypeDefKind::Result(r) => { - self.needs_ty_result = true; - self.src.ts("Result<"); - self.print_optional_ty(r.ok.as_ref(), mode); - self.src.ts(", "); - self.print_optional_ty(r.err.as_ref(), mode); - self.src.ts(">"); - } - TypeDefKind::Variant(_) => panic!("anonymous variant"), - TypeDefKind::List(v) => self.print_list(v, mode), - TypeDefKind::Future(_) => todo!("anonymous future"), - TypeDefKind::Stream(_) => todo!("anonymous stream"), - TypeDefKind::Unknown => unreachable!(), - } - } - } - } - - fn print_optional_ty(&mut self, ty: Option<&Type>, mode: Mode) { - match ty { - Some(ty) => self.print_ty(ty, mode), - None => self.src.ts("void"), - } - } - - fn print_list(&mut self, ty: &Type, mode: Mode) { - match self.array_ty(ty) { - Some("Uint8Array") => match mode { - Mode::Lift => self.src.ts("Uint8Array"), - Mode::Lower => self.src.ts("Uint8Array | ArrayBuffer"), - }, - Some(ty) => self.src.ts(ty), - None => { - self.print_ty(ty, mode); - self.src.ts("[]"); - } - } - } - - fn print_tuple(&mut self, tuple: &Tuple, mode: Mode) { - self.src.ts("["); - for (i, ty) in tuple.types.iter().enumerate() { - if i > 0 { - self.src.ts(", "); - } - self.print_ty(ty, mode); - } - self.src.ts("]"); - } - - fn ts_func(&mut self, func: &Function, abi: AbiVariant) { - self.docs(&func.docs); - - self.src.ts("export function "); - self.src.ts(&func.item_name().to_lower_camel_case()); - self.src.ts("("); - - let param_start = match &func.kind { - FunctionKind::Freestanding => 0, - }; - - for (i, (name, ty)) in func.params[param_start..].iter().enumerate() { - if i > 0 { - self.src.ts(", "); - } - self.src.ts(to_js_ident(&name.to_lower_camel_case())); - self.src.ts(": "); - self.print_ty( - ty, - match abi { - AbiVariant::GuestExport => Mode::Lower, - AbiVariant::GuestImport => Mode::Lift, - }, - ); - } - self.src.ts("): "); - let result_mode = match abi { - AbiVariant::GuestExport => Mode::Lift, - AbiVariant::GuestImport => Mode::Lower, - }; - if let Some((ok_ty, _)) = func.results.throws(self.resolve) { - self.print_optional_ty(ok_ty, result_mode); - } else { - match func.results.len() { - 0 => self.src.ts("void"), - 1 => self.print_ty(func.results.iter_types().next().unwrap(), result_mode), - _ => { - self.src.ts("["); - for (i, ty) in func.results.iter_types().enumerate() { - if i != 0 { - self.src.ts(", "); - } - self.print_ty(ty, result_mode); - } - self.src.ts("]"); - } - } - } - self.src.ts(";\n"); - } - - fn maybe_null(&self, ty: &Type) -> bool { - self.gen.maybe_null(self.resolve, ty) - } - - fn as_nullable<'b>(&self, ty: &'b Type) -> Option<&'b Type> - where - 'a: 'b, - { - self.gen.as_nullable(self.resolve, ty) - } - - fn post_types(&mut self) { - if mem::take(&mut self.needs_ty_option) { - self.src - .ts("export type Option = { tag: 'none' } | { tag: 'some', val; T };\n"); - } - if mem::take(&mut self.needs_ty_result) { - self.src - .ts("export type Result = { tag: 'ok', val: T } | { tag: 'err', val: E };\n"); - } - } -} - -impl<'a> InterfaceGenerator<'a> for JsInterface<'a> { - fn resolve(&self) -> &'a Resolve { - self.resolve - } - - fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) { - self.docs(docs); - self.src.ts(&format!( - "export interface {} {{\n", - name.to_upper_camel_case() - )); - for field in record.fields.iter() { - self.docs(&field.docs); - let (option_str, ty) = self - .as_nullable(&field.ty) - .map_or(("", &field.ty), |ty| ("?", ty)); - self.src.ts(&format!( - "{}{}: ", - field.name.to_lower_camel_case(), - option_str - )); - self.print_ty(ty, Mode::Lift); - self.src.ts(",\n"); - } - self.src.ts("}\n"); - } - - fn type_tuple(&mut self, _id: TypeId, name: &str, tuple: &Tuple, docs: &Docs) { - self.docs(docs); - self.src - .ts(&format!("export type {} = ", name.to_upper_camel_case())); - self.print_tuple(tuple, Mode::Lift); - self.src.ts(";\n"); - } - - fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) { - self.docs(docs); - self.src.ts(&format!( - "export interface {} {{\n", - name.to_upper_camel_case() - )); - for flag in flags.flags.iter() { - self.docs(&flag.docs); - let name = flag.name.to_lower_camel_case(); - self.src.ts(&format!("{name}?: boolean,\n")); - } - self.src.ts("}\n"); - } - - fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, docs: &Docs) { - self.docs(docs); - self.src - .ts(&format!("export type {} = ", name.to_upper_camel_case())); - for (i, case) in variant.cases.iter().enumerate() { - if i > 0 { - self.src.ts(" | "); - } - self.src - .ts(&format!("{}_{}", name, case.name).to_upper_camel_case()); - } - self.src.ts(";\n"); - for case in variant.cases.iter() { - self.docs(&case.docs); - self.src.ts(&format!( - "export interface {} {{\n", - format!("{}_{}", name, case.name).to_upper_camel_case() - )); - self.src.ts("tag: '"); - self.src.ts(&case.name); - self.src.ts("',\n"); - if let Some(ty) = case.ty { - self.src.ts("val: "); - self.print_ty(&ty, Mode::Lift); - self.src.ts(",\n"); - } - self.src.ts("}\n"); - } - } - - fn type_union(&mut self, _id: TypeId, name: &str, union: &Union, docs: &Docs) { - self.docs(docs); - let name = name.to_upper_camel_case(); - self.src.ts(&format!("export type {name} = ")); - for i in 0..union.cases.len() { - if i > 0 { - self.src.ts(" | "); - } - self.src.ts(&format!("{name}{i}")); - } - self.src.ts(";\n"); - for (i, case) in union.cases.iter().enumerate() { - self.docs(&case.docs); - self.src.ts(&format!("export interface {name}{i} {{\n")); - self.src.ts(&format!("tag: {i},\n")); - self.src.ts("val: "); - self.print_ty(&case.ty, Mode::Lift); - self.src.ts(",\n"); - self.src.ts("}\n"); - } - } - - fn type_option(&mut self, _id: TypeId, name: &str, payload: &Type, docs: &Docs) { - self.docs(docs); - let name = name.to_upper_camel_case(); - self.src.ts(&format!("export type {name} = ")); - if self.maybe_null(payload) { - self.needs_ty_option = true; - self.src.ts("Option<"); - self.print_ty(payload, Mode::Lift); - self.src.ts(">"); - } else { - self.print_ty(payload, Mode::Lift); - self.src.ts(" | null"); - } - self.src.ts(";\n"); - } - - fn type_result(&mut self, _id: TypeId, name: &str, result: &Result_, docs: &Docs) { - self.docs(docs); - let name = name.to_upper_camel_case(); - self.needs_ty_result = true; - self.src.ts(&format!("export type {name} = Result<")); - self.print_optional_ty(result.ok.as_ref(), Mode::Lift); - self.src.ts(", "); - self.print_optional_ty(result.err.as_ref(), Mode::Lift); - self.src.ts(">;\n"); - } - - fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { - // The complete documentation for this enum, including documentation for variants. - let mut complete_docs = String::new(); - - if let Some(docs) = &docs.contents { - complete_docs.push_str(docs); - // Add a gap before the `# Variants` section. - complete_docs.push('\n'); - } - - writeln!(complete_docs, "# Variants").unwrap(); - - for case in enum_.cases.iter() { - writeln!(complete_docs).unwrap(); - writeln!(complete_docs, "## `\"{}\"`", case.name).unwrap(); - - if let Some(docs) = &case.docs.contents { - writeln!(complete_docs).unwrap(); - complete_docs.push_str(docs); - } - } - - self.docs_raw(&complete_docs); - - self.src - .ts(&format!("export type {} = ", name.to_upper_camel_case())); - for (i, case) in enum_.cases.iter().enumerate() { - if i != 0 { - self.src.ts(" | "); - } - self.src.ts(&format!("'{}'", case.name)); - } - self.src.ts(";\n"); - } - - fn type_alias(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - self.docs(docs); - self.src - .ts(&format!("export type {} = ", name.to_upper_camel_case())); - self.print_ty(ty, Mode::Lift); - self.src.ts(";\n"); - } - - fn type_list(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - self.docs(docs); - self.src - .ts(&format!("export type {} = ", name.to_upper_camel_case())); - self.print_list(ty, Mode::Lift); - self.src.ts(";\n"); - } - - fn type_builtin(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - drop((_id, name, ty, docs)); - } -} - -#[derive(PartialEq)] -enum ErrHandling { - None, - ThrowResultErr, - ResultCatchHandler, -} - -struct FunctionBindgen<'a> { - gen: &'a mut Js, - sizes: &'a SizeAlign, - err: ErrHandling, - tmp: usize, - src: Source, - block_storage: Vec, - blocks: Vec<(String, Vec)>, - params: Vec, - memory: Option, - realloc: Option, - post_return: Option, - encoding: StringEncoding, - callee: String, -} - -impl FunctionBindgen<'_> { - fn tmp(&mut self) -> usize { - let ret = self.tmp; - self.tmp += 1; - ret - } - - fn clamp_guest(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) - where - T: std::fmt::Display, - { - let clamp = self.gen.intrinsic(Intrinsic::ClampGuest); - results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max)); - } - - fn load(&mut self, method: &str, offset: i32, operands: &[String], results: &mut Vec) { - let memory = self.memory.as_ref().unwrap(); - let view = self.gen.intrinsic(Intrinsic::DataView); - results.push(format!( - "{view}({memory}).{method}({} + {offset}, true)", - operands[0], - )); - } - - fn store(&mut self, method: &str, offset: i32, operands: &[String]) { - let memory = self.memory.as_ref().unwrap(); - let view = self.gen.intrinsic(Intrinsic::DataView); - uwriteln!( - self.src.js, - "{view}({memory}).{method}({} + {offset}, {}, true);", - operands[1], - operands[0] - ); - } - - fn bind_results(&mut self, amt: usize, results: &mut Vec) { - match amt { - 0 => {} - 1 => { - self.src.js("const ret = "); - results.push("ret".to_string()); - } - n => { - self.src.js("const ["); - for i in 0..n { - if i > 0 { - self.src.js(", "); - } - self.src.js(&format!("ret{}", i)); - results.push(format!("ret{}", i)); - } - self.src.js("] = "); - } - } - } -} - -impl Bindgen for FunctionBindgen<'_> { - type Operand = String; - - fn sizes(&self) -> &SizeAlign { - &self.sizes - } - - fn push_block(&mut self) { - let prev = mem::take(&mut self.src.js); - self.block_storage.push(prev); - } - - fn finish_block(&mut self, operands: &mut Vec) { - let to_restore = self.block_storage.pop().unwrap(); - let src = mem::replace(&mut self.src.js, to_restore); - self.blocks.push((src.into(), mem::take(operands))); - } - - fn return_pointer(&mut self, _size: usize, _align: usize) -> String { - unimplemented!() - } - - fn is_list_canonical(&self, resolve: &Resolve, ty: &Type) -> bool { - self.gen.array_ty(resolve, ty).is_some() - } - - fn emit( - &mut self, - resolve: &Resolve, - inst: &Instruction<'_>, - operands: &mut Vec, - results: &mut Vec, - ) { - match inst { - Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), - Instruction::I32Const { val } => results.push(val.to_string()), - Instruction::ConstZero { tys } => { - for t in tys.iter() { - match t { - WasmType::I64 => results.push("0n".to_string()), - WasmType::I32 | WasmType::F32 | WasmType::F64 => { - results.push("0".to_string()); - } - } - } - } - - // The representation of i32 in JS is a number, so 8/16-bit values - // get further clamped to ensure that the upper bits aren't set when - // we pass the value, ensuring that only the right number of bits - // are transferred. - Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX), - Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX), - Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX), - Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX), - // Use `>>>0` to ensure the bits of the number are treated as - // unsigned. - Instruction::U32FromI32 => { - results.push(format!("{} >>> 0", operands[0])); - } - // All bigints coming from wasm are treated as signed, so convert - // it to ensure it's treated as unsigned. - Instruction::U64FromI64 => results.push(format!("BigInt.asUintN(64, {})", operands[0])), - // Nothing to do signed->signed where the representations are the - // same. - Instruction::S32FromI32 | Instruction::S64FromI64 => { - results.push(operands.pop().unwrap()) - } - - // All values coming from the host and going to wasm need to have - // their ranges validated, since the host could give us any value. - Instruction::I32FromU8 => { - let conv = self.gen.intrinsic(Intrinsic::ToUint8); - results.push(format!("{conv}({op})", op = operands[0])) - } - Instruction::I32FromS8 => { - let conv = self.gen.intrinsic(Intrinsic::ToInt8); - results.push(format!("{conv}({op})", op = operands[0])) - } - Instruction::I32FromU16 => { - let conv = self.gen.intrinsic(Intrinsic::ToUint16); - results.push(format!("{conv}({op})", op = operands[0])) - } - Instruction::I32FromS16 => { - let conv = self.gen.intrinsic(Intrinsic::ToInt16); - results.push(format!("{conv}({op})", op = operands[0])) - } - Instruction::I32FromU32 => { - let conv = self.gen.intrinsic(Intrinsic::ToUint32); - results.push(format!("{conv}({op})", op = operands[0])) - } - Instruction::I32FromS32 => { - let conv = self.gen.intrinsic(Intrinsic::ToInt32); - results.push(format!("{conv}({op})", op = operands[0])) - } - Instruction::I64FromU64 => { - let conv = self.gen.intrinsic(Intrinsic::ToBigUint64); - results.push(format!("{conv}({op})", op = operands[0])) - } - Instruction::I64FromS64 => { - let conv = self.gen.intrinsic(Intrinsic::ToBigInt64); - results.push(format!("{conv}({op})", op = operands[0])) - } - - // The native representation in JS of f32 and f64 is just a number, - // so there's nothing to do here. Everything wasm gives us is - // representable in JS. - Instruction::Float32FromF32 | Instruction::Float64FromF64 => { - results.push(operands.pop().unwrap()) - } - - Instruction::F32FromFloat32 | Instruction::F64FromFloat64 => { - // Use a unary `+` to cast to a float. - results.push(format!("+{}", operands[0])); - } - - // Validate that i32 values coming from wasm are indeed valid code - // points. - Instruction::CharFromI32 => { - let validate = self.gen.intrinsic(Intrinsic::ValidateGuestChar); - results.push(format!("{}({})", validate, operands[0])); - } - - // Validate that strings are indeed 1 character long and valid - // unicode. - Instruction::I32FromChar => { - let validate = self.gen.intrinsic(Intrinsic::ValidateHostChar); - results.push(format!("{}({})", validate, operands[0])); - } - - Instruction::Bitcasts { casts } => { - for (cast, op) in casts.iter().zip(operands) { - match cast { - Bitcast::I32ToF32 => { - let cvt = self.gen.intrinsic(Intrinsic::I32ToF32); - results.push(format!("{}({})", cvt, op)); - } - Bitcast::F32ToI32 => { - let cvt = self.gen.intrinsic(Intrinsic::F32ToI32); - results.push(format!("{}({})", cvt, op)); - } - Bitcast::I64ToF64 => { - let cvt = self.gen.intrinsic(Intrinsic::I64ToF64); - results.push(format!("{}({})", cvt, op)); - } - Bitcast::F64ToI64 => { - let cvt = self.gen.intrinsic(Intrinsic::F64ToI64); - results.push(format!("{}({})", cvt, op)); - } - Bitcast::I32ToI64 => results.push(format!("BigInt({})", op)), - Bitcast::I64ToI32 => results.push(format!("Number({})", op)), - Bitcast::I64ToF32 => { - let cvt = self.gen.intrinsic(Intrinsic::I32ToF32); - results.push(format!("{}(Number({}))", cvt, op)); - } - Bitcast::F32ToI64 => { - let cvt = self.gen.intrinsic(Intrinsic::F32ToI32); - results.push(format!("BigInt({}({}))", cvt, op)); - } - Bitcast::None => results.push(op.clone()), - } - } - } - - Instruction::BoolFromI32 => { - let tmp = self.tmp(); - self.src - .js(&format!("const bool{} = {};\n", tmp, operands[0])); - if self.gen.opts.valid_lifting_optimization { - results.push(format!("!!bool{tmp}")); - } else { - let throw = self.gen.intrinsic(Intrinsic::ThrowInvalidBool); - results.push(format!( - "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())" - )); - } - } - Instruction::I32FromBool => { - results.push(format!("{} ? 1 : 0", operands[0])); - } - - Instruction::RecordLower { record, .. } => { - // use destructuring field access to get each - // field individually. - let tmp = self.tmp(); - let mut expr = "const {".to_string(); - for (i, field) in record.fields.iter().enumerate() { - if i > 0 { - expr.push_str(", "); - } - let name = format!("v{}_{}", tmp, i); - expr.push_str(&field.name.to_lower_camel_case()); - expr.push_str(": "); - expr.push_str(&name); - results.push(name); - } - self.src.js(&format!("{} }} = {};\n", expr, operands[0])); - } - - Instruction::RecordLift { record, .. } => { - // records are represented as plain objects, so we - // make a new object and set all the fields with an object - // literal. - let mut result = "{\n".to_string(); - for (field, op) in record.fields.iter().zip(operands) { - result.push_str(&format!("{}: {},\n", field.name.to_lower_camel_case(), op)); - } - result.push_str("}"); - results.push(result); - } - - Instruction::TupleLower { tuple, .. } => { - // Tuples are represented as an array, sowe can use - // destructuring assignment to lower the tuple into its - // components. - let tmp = self.tmp(); - let mut expr = "const [".to_string(); - for i in 0..tuple.types.len() { - if i > 0 { - expr.push_str(", "); - } - let name = format!("tuple{}_{}", tmp, i); - expr.push_str(&name); - results.push(name); - } - self.src.js(&format!("{}] = {};\n", expr, operands[0])); - } - - Instruction::TupleLift { .. } => { - // Tuples are represented as an array, so we just shove all - // the operands into an array. - results.push(format!("[{}]", operands.join(", "))); - } - - // This lowers flags from a dictionary of booleans in accordance with https://webidl.spec.whatwg.org/#es-dictionary. - Instruction::FlagsLower { flags, .. } => { - let op0 = &operands[0]; - - // Generate the result names. - for _ in 0..flags.repr().count() { - let tmp = self.tmp(); - let name = format!("flags{tmp}"); - // Default to 0 so that in the null/undefined case, everything is false by - // default. - self.src.js(&format!("let {name} = 0;\n")); - results.push(name); - } - - self.src.js(&format!( - "if (typeof {op0} === 'object' && {op0} !== null) {{\n" - )); - - for (i, chunk) in flags.flags.chunks(32).enumerate() { - let result_name = &results[i]; - - self.src.js(&format!("{result_name} = ")); - for (i, flag) in chunk.iter().enumerate() { - if i != 0 { - self.src.js(" | "); - } - - let flag = flag.name.to_lower_camel_case(); - self.src.js(&format!("Boolean({op0}.{flag}) << {i}")); - } - self.src.js(";\n"); - } - - self.src.js(&format!("\ - }} else if ({op0} !== null && {op0} !== undefined) {{ - throw new TypeError('only an object, undefined or null can be converted to flags'); - }} - ")); - - // We don't need to do anything else for the null/undefined - // case, since that's interpreted as everything false, and we - // already defaulted everyting to 0. - } - - Instruction::FlagsLift { flags, .. } => { - let tmp = self.tmp(); - results.push(format!("flags{tmp}")); - - if let Some(op) = operands.last() { - // We only need an extraneous bits check if the number of flags isn't a multiple - // of 32, because if it is then all the bits are used and there are no - // extraneous bits. - if flags.flags.len() % 32 != 0 && !self.gen.opts.valid_lifting_optimization { - let mask: u32 = 0xffffffff << (flags.flags.len() % 32); - uwriteln!( - self.src.js, - "if (({op} & {mask}) !== 0) {{ - throw new TypeError('flags have extraneous bits set'); - }}" - ); - } - } - - uwriteln!(self.src.js, "const flags{tmp} = {{"); - - for (i, flag) in flags.flags.iter().enumerate() { - let flag = flag.name.to_lower_camel_case(); - let op = &operands[i / 32]; - let mask: u32 = 1 << (i % 32); - uwriteln!(self.src.js, "{flag}: Boolean({op} & {mask}),"); - } - - uwriteln!(self.src.js, "}};"); - } - - Instruction::VariantPayloadName => results.push("e".to_string()), - - Instruction::VariantLower { - variant, - results: result_types, - name, - .. - } => { - let blocks = self - .blocks - .drain(self.blocks.len() - variant.cases.len()..) - .collect::>(); - let tmp = self.tmp(); - let operand = &operands[0]; - uwriteln!(self.src.js, "const variant{tmp} = {operand};"); - - for i in 0..result_types.len() { - uwriteln!(self.src.js, "let variant{tmp}_{i};"); - results.push(format!("variant{}_{}", tmp, i)); - } - - let expr_to_match = format!("variant{}.tag", tmp); - - uwriteln!(self.src.js, "switch ({expr_to_match}) {{"); - for (case, (block, block_results)) in variant.cases.iter().zip(blocks) { - uwriteln!(self.src.js, "case '{}': {{", case.name.as_str()); - if case.ty.is_some() { - uwriteln!(self.src.js, "const e = variant{tmp}.val;"); - } - self.src.js(&block); - - for (i, result) in block_results.iter().enumerate() { - uwriteln!(self.src.js, "variant{tmp}_{i} = {result};"); - } - uwriteln!( - self.src.js, - "break; - }}" - ); - } - let variant_name = name.to_upper_camel_case(); - uwriteln!( - self.src.js, - "default: {{ - throw new TypeError('invalid variant specified for {variant_name}'); - }}" - ); - uwriteln!(self.src.js, "}}"); - } - - Instruction::VariantLift { variant, name, .. } => { - let blocks = self - .blocks - .drain(self.blocks.len() - variant.cases.len()..) - .collect::>(); - - let tmp = self.tmp(); - let operand = &operands[0]; - - uwriteln!( - self.src.js, - "let variant{tmp}; - switch ({operand}) {{" - ); - - for (i, (case, (block, block_results))) in - variant.cases.iter().zip(blocks).enumerate() - { - let tag = case.name.as_str(); - uwriteln!( - self.src.js, - "case {i}: {{ - {block}\ - variant{tmp} = {{ - tag: '{tag}'," - ); - if case.ty.is_some() { - assert!(block_results.len() == 1); - uwriteln!(self.src.js, " val: {}", block_results[0]); - } else { - assert!(block_results.len() == 0); - } - uwriteln!( - self.src.js, - " }}; - break; - }}" - ); - } - let variant_name = name.to_upper_camel_case(); - if !self.gen.opts.valid_lifting_optimization { - uwriteln!( - self.src.js, - "default: {{ - throw new TypeError('invalid variant discriminant for {variant_name}'); - }}" - ); - } - uwriteln!(self.src.js, "}}"); - results.push(format!("variant{}", tmp)); - } - - Instruction::UnionLower { - union, - results: result_types, - name, - .. - } => { - let blocks = self - .blocks - .drain(self.blocks.len() - union.cases.len()..) - .collect::>(); - let tmp = self.tmp(); - let op0 = &operands[0]; - uwriteln!(self.src.js, "const union{tmp} = {op0};"); - - for i in 0..result_types.len() { - uwriteln!(self.src.js, "let union{tmp}_{i};"); - results.push(format!("union{tmp}_{i}")); - } - - uwriteln!(self.src.js, "switch (union{tmp}.tag) {{"); - for (i, (_case, (block, block_results))) in - union.cases.iter().zip(blocks).enumerate() - { - uwriteln!( - self.src.js, - "case {i}: {{ - const e = union{tmp}.val; - {block}" - ); - for (i, result) in block_results.iter().enumerate() { - uwriteln!(self.src.js, "union{tmp}_{i} = {result};"); - } - uwriteln!( - self.src.js, - "break; - }}" - ); - } - let name = name.to_upper_camel_case(); - uwriteln!( - self.src.js, - "default: {{ - throw new TypeError('invalid union specified for {name}'); - }}" - ); - uwriteln!(self.src.js, "}}"); - } - - Instruction::UnionLift { union, name, .. } => { - let blocks = self - .blocks - .drain(self.blocks.len() - union.cases.len()..) - .collect::>(); - - let tmp = self.tmp(); - let operand = &operands[0]; - - uwriteln!( - self.src.js, - "let union{tmp}; - switch ({operand}) {{" - ); - for (i, (_case, (block, block_results))) in - union.cases.iter().zip(blocks).enumerate() - { - assert!(block_results.len() == 1); - let block_result = &block_results[0]; - uwriteln!( - self.src.js, - "case {i}: {{ - {block}\ - union{tmp} = {{ - tag: {i}, - val: {block_result}, - }}; - break; - }}" - ); - } - let name = name.to_upper_camel_case(); - if !self.gen.opts.valid_lifting_optimization { - uwriteln!( - self.src.js, - "default: {{ - throw new TypeError('invalid union discriminant for {name}'); - }}" - ); - } - uwriteln!(self.src.js, "}}"); - results.push(format!("union{tmp}")); - } - - Instruction::OptionLower { - payload, - results: result_types, - .. - } => { - let (mut some, some_results) = self.blocks.pop().unwrap(); - let (mut none, none_results) = self.blocks.pop().unwrap(); - - let tmp = self.tmp(); - let operand = &operands[0]; - uwriteln!(self.src.js, "const variant{tmp} = {operand};"); - - for i in 0..result_types.len() { - uwriteln!(self.src.js, "let variant{tmp}_{i};"); - results.push(format!("variant{tmp}_{i}")); - - let some_result = &some_results[i]; - let none_result = &none_results[i]; - uwriteln!(some, "variant{tmp}_{i} = {some_result};"); - uwriteln!(none, "variant{tmp}_{i} = {none_result};"); - } - - if self.gen.maybe_null(resolve, payload) { - uwriteln!( - self.src.js, - "switch (variant{tmp}.tag) {{ - case 'none': {{ - {none}\ - break; - }} - case 'some': {{ - const e = variant{tmp}.val; - {some}\ - break; - }} - default: {{ - throw new TypeError('invalid variant specified for option'); - }} - }}" - ); - } else { - uwriteln!( - self.src.js, - "if (variant{tmp} === null || variant{tmp} === undefined) {{ - {none}\ - }} else {{ - const e = variant{tmp}; - {some}\ - }}" - ); - } - } - - Instruction::OptionLift { payload, .. } => { - let (some, some_results) = self.blocks.pop().unwrap(); - let (none, none_results) = self.blocks.pop().unwrap(); - assert!(none_results.len() == 0); - assert!(some_results.len() == 1); - let some_result = &some_results[0]; - - let tmp = self.tmp(); - let operand = &operands[0]; - - let (v_none, v_some) = if self.gen.maybe_null(resolve, payload) { - ( - "{ tag: 'none' }", - format!( - "{{ - tag: 'some', - val: {some_result} - }}" - ), - ) - } else { - ("null", some_result.into()) - }; - - if !self.gen.opts.valid_lifting_optimization { - uwriteln!( - self.src.js, - "let variant{tmp}; - switch ({operand}) {{ - case 0: {{ - {none}\ - variant{tmp} = {v_none}; - break; - }} - case 1: {{ - {some}\ - variant{tmp} = {v_some}; - break; - }} - default: {{ - throw new TypeError('invalid variant discriminant for option'); - }} - }}" - ); - } else { - uwriteln!( - self.src.js, - "let variant{tmp}; - if ({operand}) {{ - {some}\ - variant{tmp} = {v_some}; - }} else {{ - {none}\ - variant{tmp} = {v_none}; - }}" - ); - } - - results.push(format!("variant{tmp}")); - } - - Instruction::ResultLower { - results: result_types, - .. - } => { - let (mut err, err_results) = self.blocks.pop().unwrap(); - let (mut ok, ok_results) = self.blocks.pop().unwrap(); - - let tmp = self.tmp(); - let operand = &operands[0]; - uwriteln!(self.src.js, "const variant{tmp} = {operand};"); - - for i in 0..result_types.len() { - uwriteln!(self.src.js, "let variant{tmp}_{i};"); - results.push(format!("variant{tmp}_{i}")); - - let ok_result = &ok_results[i]; - let err_result = &err_results[i]; - uwriteln!(ok, "variant{tmp}_{i} = {ok_result};"); - uwriteln!(err, "variant{tmp}_{i} = {err_result};"); - } - - uwriteln!( - self.src.js, - "switch (variant{tmp}.tag) {{ - case 'ok': {{ - const e = variant{tmp}.val; - {ok}\ - break; - }} - case 'err': {{ - const e = variant{tmp}.val; - {err}\ - break; - }} - default: {{ - throw new TypeError('invalid variant specified for result'); - }} - }}" - ); - } - - Instruction::ResultLift { result, .. } => { - let (err, err_results) = self.blocks.pop().unwrap(); - let (ok, ok_results) = self.blocks.pop().unwrap(); - let ok_result = if result.ok.is_some() { - assert_eq!(ok_results.len(), 1); - format!("{}", ok_results[0]) - } else { - assert_eq!(ok_results.len(), 0); - String::from("undefined") - }; - let err_result = if result.err.is_some() { - assert_eq!(err_results.len(), 1); - format!("{}", err_results[0]) - } else { - assert_eq!(err_results.len(), 0); - String::from("undefined") - }; - let tmp = self.tmp(); - let op0 = &operands[0]; - - if !self.gen.opts.valid_lifting_optimization { - uwriteln!( - self.src.js, - "let variant{tmp}; - switch ({op0}) {{ - case 0: {{ - {ok}\ - variant{tmp} = {{ - tag: 'ok', - val: {ok_result} - }}; - break; - }} - case 1: {{ - {err}\ - variant{tmp} = {{ - tag: 'err', - val: {err_result} - }}; - break; - }} - default: {{ - throw new TypeError('invalid variant discriminant for expected'); - }} - }}" - ); - } else { - uwriteln!( - self.src.js, - "let variant{tmp}; - if ({op0}) {{ - {err}\ - variant{tmp} = {{ - tag: 'err', - val: {err_result} - }}; - }} else {{ - {ok}\ - variant{tmp} = {{ - tag: 'ok', - val: {ok_result} - }}; - }}" - ); - } - results.push(format!("variant{tmp}")); - } - - // Lowers an enum in accordance with https://webidl.spec.whatwg.org/#es-enumeration. - Instruction::EnumLower { name, enum_, .. } => { - let tmp = self.tmp(); - - let to_string = self.gen.intrinsic(Intrinsic::ToString); - let operand = &operands[0]; - uwriteln!(self.src.js, "const val{tmp} = {to_string}({operand});"); - - // Declare a variable to hold the result. - uwriteln!( - self.src.js, - "let enum{tmp}; - switch (val{tmp}) {{" - ); - for (i, case) in enum_.cases.iter().enumerate() { - uwriteln!( - self.src.js, - "case '{case}': {{ - enum{tmp} = {i}; - break; - }}", - case = case.name - ); - } - uwriteln!( - self.src.js, - "default: {{ - throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`); - }}" - ); - uwriteln!(self.src.js, "}}"); - - results.push(format!("enum{tmp}")); - } - - Instruction::EnumLift { name, enum_, .. } => { - let tmp = self.tmp(); - - uwriteln!( - self.src.js, - "let enum{tmp}; - switch ({}) {{", - operands[0] - ); - for (i, case) in enum_.cases.iter().enumerate() { - uwriteln!( - self.src.js, - "case {i}: {{ - enum{tmp} = '{case}'; - break; - }}", - case = case.name - ); - } - if !self.gen.opts.valid_lifting_optimization { - let name = name.to_upper_camel_case(); - uwriteln!( - self.src.js, - "default: {{ - throw new TypeError('invalid discriminant specified for {name}'); - }}", - ); - } - uwriteln!(self.src.js, "}}"); - - results.push(format!("enum{tmp}")); - } - - Instruction::ListCanonLower { element, .. } => { - let tmp = self.tmp(); - let memory = self.memory.as_ref().unwrap(); - let realloc = self.realloc.as_ref().unwrap(); - - let size = self.sizes.size(element); - let align = self.sizes.align(element); - uwriteln!(self.src.js, "const val{tmp} = {};", operands[0]); - if matches!(element, Type::U8) { - uwriteln!(self.src.js, "const len{tmp} = val{tmp}.byteLength;"); - } else { - uwriteln!(self.src.js, "const len{tmp} = val{tmp}.length;"); - } - uwriteln!( - self.src.js, - "const ptr{tmp} = {realloc}(0, 0, {align}, len{tmp} * {size});" - ); - // TODO: this is the wrong endianness - if matches!(element, Type::U8) { - uwriteln!( - self.src.js, - "const src{tmp} = new Uint8Array(val{tmp}.buffer || val{tmp}, val{tmp}.byteOffset, len{tmp} * {size});", - ); - } else { - uwriteln!( - self.src.js, - "const src{tmp} = new Uint8Array(val{tmp}.buffer, val{tmp}.byteOffset, len{tmp} * {size});", - ); - } - uwriteln!( - self.src.js, - "(new Uint8Array({memory}.buffer, ptr{tmp}, len{tmp} * {size})).set(src{tmp});", - ); - results.push(format!("ptr{}", tmp)); - results.push(format!("len{}", tmp)); - } - Instruction::ListCanonLift { element, .. } => { - let tmp = self.tmp(); - let memory = self.memory.as_ref().unwrap(); - uwriteln!(self.src.js, "const ptr{tmp} = {};", operands[0]); - uwriteln!(self.src.js, "const len{tmp} = {};", operands[1]); - // TODO: this is the wrong endianness - let array_ty = self.gen.array_ty(resolve, element).unwrap(); - uwriteln!( - self.src.js, - "const result{tmp} = new {array_ty}({memory}.buffer.slice(ptr{tmp}, ptr{tmp} + len{tmp} * {}));", - self.sizes.size(element), - ); - results.push(format!("result{tmp}")); - } - Instruction::StringLower { .. } => { - // Only Utf8 and Utf16 supported for now - assert!(matches!( - self.encoding, - StringEncoding::Utf8 | StringEncoding::Utf16 - )); - let tmp = self.tmp(); - let memory = self.memory.as_ref().unwrap(); - let realloc = self.realloc.as_ref().unwrap(); - - let intrinsic = if self.encoding == StringEncoding::Utf16 { - Intrinsic::Utf16Encode - } else { - Intrinsic::Utf8Encode - }; - let encode = self.gen.intrinsic(intrinsic); - uwriteln!( - self.src.js, - "const ptr{tmp} = {encode}({}, {realloc}, {memory});", - operands[0], - ); - if self.encoding == StringEncoding::Utf8 { - let encoded_len = self.gen.intrinsic(Intrinsic::Utf8EncodedLen); - uwriteln!(self.src.js, "const len{tmp} = {encoded_len};"); - } else { - uwriteln!(self.src.js, "const len{tmp} = {}.length;", operands[0]); - } - results.push(format!("ptr{}", tmp)); - results.push(format!("len{}", tmp)); - } - Instruction::StringLift => { - // Only Utf8 and Utf16 supported for now - assert!(matches!( - self.encoding, - StringEncoding::Utf8 | StringEncoding::Utf16 - )); - let tmp = self.tmp(); - let memory = self.memory.as_ref().unwrap(); - uwriteln!(self.src.js, "const ptr{tmp} = {};", operands[0]); - uwriteln!(self.src.js, "const len{tmp} = {};", operands[1]); - let intrinsic = if self.encoding == StringEncoding::Utf16 { - Intrinsic::Utf16Decoder - } else { - Intrinsic::Utf8Decoder - }; - let decoder = self.gen.intrinsic(intrinsic); - uwriteln!( - self.src.js, - "const result{tmp} = {decoder}.decode(new Uint{}Array({memory}.buffer, ptr{tmp}, len{tmp}));", - if self.encoding == StringEncoding::Utf16 { "16" } else { "8" } - ); - results.push(format!("result{tmp}")); - } - - Instruction::ListLower { element, .. } => { - let (body, body_results) = self.blocks.pop().unwrap(); - assert!(body_results.is_empty()); - let tmp = self.tmp(); - let vec = format!("vec{}", tmp); - let result = format!("result{}", tmp); - let len = format!("len{}", tmp); - let size = self.sizes.size(element); - let align = self.sizes.align(element); - - // first store our vec-to-lower in a temporary since we'll - // reference it multiple times. - uwriteln!(self.src.js, "const {vec} = {};", operands[0]); - uwriteln!(self.src.js, "const {len} = {vec}.length;"); - - // ... then realloc space for the result in the guest module - let realloc = self.realloc.as_ref().unwrap(); - uwriteln!( - self.src.js, - "const {result} = {realloc}(0, 0, {align}, {len} * {size});" - ); - - // ... then consume the vector and use the block to lower the - // result. - uwriteln!(self.src.js, "for (let i = 0; i < {vec}.length; i++) {{"); - uwriteln!(self.src.js, "const e = {vec}[i];"); - uwrite!(self.src.js, "const base = {result} + i * {size};"); - self.src.js(&body); - self.src.js("}\n"); - - results.push(result); - results.push(len); - } - - Instruction::ListLift { element, .. } => { - let (body, body_results) = self.blocks.pop().unwrap(); - let tmp = self.tmp(); - let size = self.sizes.size(element); - let len = format!("len{tmp}"); - uwriteln!(self.src.js, "const {len} = {};", operands[1]); - let base = format!("base{tmp}"); - uwriteln!(self.src.js, "const {base} = {};", operands[0]); - let result = format!("result{tmp}"); - uwriteln!(self.src.js, "const {result} = [];"); - results.push(result.clone()); - - uwriteln!(self.src.js, "for (let i = 0; i < {len}; i++) {{"); - uwriteln!(self.src.js, "const base = {base} + i * {size};"); - self.src.js(&body); - assert_eq!(body_results.len(), 1); - uwriteln!(self.src.js, "{result}.push({});", body_results[0]); - self.src.js("}\n"); - } - - Instruction::IterElem { .. } => results.push("e".to_string()), - - Instruction::IterBasePointer => results.push("base".to_string()), - - Instruction::CallWasm { sig, .. } => { - self.bind_results(sig.results.len(), results); - uwriteln!(self.src.js, "{}({});", self.callee, operands.join(", ")); - } - - Instruction::CallInterface { func } => { - if self.err == ErrHandling::ResultCatchHandler { - uwriteln!( - self.src.js, - "let ret; - try {{ - ret = {{ tag: 'ok', val: {}({}) }}; - }} catch (e) {{ - ret = {{ tag: 'err', val: {}(e) }}; - }}", - self.callee, - operands.join(", "), - self.gen.intrinsic(Intrinsic::GetErrorPayload), - ); - results.push("ret".to_string()); - } else { - self.bind_results(func.results.len(), results); - uwriteln!(self.src.js, "{}({});", self.callee, operands.join(", ")); - } - } - - Instruction::Return { amt, .. } => { - if let Some(f) = &self.post_return { - uwriteln!(self.src.js, "{f}(ret);"); - } - - if self.err == ErrHandling::ThrowResultErr { - let component_err = self.gen.intrinsic(Intrinsic::ComponentError); - let operand = &operands[0]; - uwriteln!( - self.src.js, - "if ({operand}.tag === 'err') {{ - throw new {component_err}({operand}.val); - }} - return {operand}.val;" - ); - } else { - match amt { - 0 => {} - 1 => uwriteln!(self.src.js, "return {};", operands[0]), - _ => uwriteln!(self.src.js, "return [{}];", operands.join(", ")), - } - } - } - - Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results), - Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results), - Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results), - Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results), - Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results), - Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results), - Instruction::I32Load16U { offset } => { - self.load("getUint16", *offset, operands, results) - } - Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results), - Instruction::I32Store { offset } => self.store("setInt32", *offset, operands), - Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands), - Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands), - Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands), - Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands), - Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands), - - Instruction::Malloc { size, align, .. } => { - let tmp = self.tmp(); - let realloc = self.realloc.as_ref().unwrap(); - let ptr = format!("ptr{tmp}"); - uwriteln!( - self.src.js, - "const {ptr} = {realloc}(0, 0, {align}, {size});", - ); - results.push(ptr); - } - - i => unimplemented!("{:?}", i), - } - } -} - -fn to_js_ident(name: &str) -> &str { - match name { - "in" => "in_", - "import" => "import_", - s => s, - } -} - -#[cfg(feature = "clap")] -fn maps_str_to_map(maps: &str) -> Result> { - let mut map_hash = HashMap::::new(); - for mapping in maps.split(",") { - match mapping.split_once('=') { - Some((left, right)) => { - map_hash.insert(left.into(), right.into()); - } - None => return Err(anyhow!(format!("Invalid mapping entry \"{}\"", &mapping))), - }; - } - Ok(map_hash) -} - -// https://tc39.es/ecma262/#prod-IdentifierStartChar -// Unicode ID_Start | "$" | "_" -fn is_js_identifier_start(code: char) -> bool { - return match code { - 'A'..='Z' | 'a'..='z' | '$' | '_' => true, - // leaving out non-ascii for now... - _ => false, - }; -} - -// https://tc39.es/ecma262/#prod-IdentifierPartChar -// Unicode ID_Continue | "$" | U+200C | U+200D -fn is_js_identifier_char(code: char) -> bool { - return match code { - '0'..='9' | 'A'..='Z' | 'a'..='z' | '$' | '_' => true, - // leaving out non-ascii for now... - _ => false, - }; -} - -fn is_js_identifier(s: &str) -> bool { - let mut chars = s.chars(); - if let Some(char) = chars.next() { - if !is_js_identifier_start(char) { - return false; - } - } else { - return false; - } - while let Some(char) = chars.next() { - if !is_js_identifier_char(char) { - return false; - } - } - return true; -} - -#[derive(Default)] -struct Source { - js: wit_bindgen_core::Source, - js_intrinsics: wit_bindgen_core::Source, - js_init: wit_bindgen_core::Source, - ts: wit_bindgen_core::Source, -} - -impl Source { - fn js(&mut self, s: &str) { - self.js.push_str(s); - } - fn js_intrinsics(&mut self, s: &str) { - self.js_intrinsics.push_str(s); - } - fn js_init(&mut self, s: &str) { - self.js_init.push_str(s); - } - fn ts(&mut self, s: &str) { - self.ts.push_str(s); - } -} diff --git a/crates/gen-host-js/tests/codegen.rs b/crates/gen-host-js/tests/codegen.rs deleted file mode 100644 index f12fe641d..000000000 --- a/crates/gen-host-js/tests/codegen.rs +++ /dev/null @@ -1,44 +0,0 @@ -use std::path::Path; -use std::process::Command; - -macro_rules! codegen_test { - ($id:ident $name:tt $test:tt) => { - #[test] - fn $id() { - drop(include_str!($test)); - test_helpers::run_component_codegen_test( - "js", - $test.as_ref(), - |name, component, files| { - wit_bindgen_core::component::generate( - &mut *wit_bindgen_gen_host_js::Opts::default().build().unwrap(), - name, - component, - files, - ) - .unwrap() - }, - verify, - ) - } - }; -} - -test_helpers::codegen_tests!("*.wit"); - -fn verify(dir: &Path, name: &str) { - let (cmd, args) = if cfg!(windows) { - ("cmd.exe", &["/c", "npx.cmd"] as &[&str]) - } else { - ("npx", &[] as &[&str]) - }; - - test_helpers::run_command( - Command::new(cmd) - .args(args) - .arg("eslint") - .arg("-c") - .arg(".eslintrc.js") - .arg(dir.join(&format!("{}.js", name))), - ); -} diff --git a/crates/gen-host-js/tests/helpers.ts b/crates/gen-host-js/tests/helpers.ts deleted file mode 100644 index 6bdd3097a..000000000 --- a/crates/gen-host-js/tests/helpers.ts +++ /dev/null @@ -1,26 +0,0 @@ -// @ts-ignore -import { readFile } from 'node:fs/promises'; -// @ts-ignore -import { argv, stdout, stderr } from 'node:process'; -// @ts-ignore -import { Buffer } from 'node:buffer'; - -// This is a helper function used from `host.ts` test in the `tests/runtime/*` -// directory to pass as the `instantiateCore` argument to the `instantiate` -// function generated by `wit-bindgen`. -// -// This function loads the module named by `path` and instantiates it with the -// `imports` object provided. The `path` is a relative path to a wasm file -// within the generated directory which for tests is passed as argv 2. -export async function loadWasm(path: string) { - const root = argv[2]; - return await WebAssembly.compile(await readFile(root + '/' + path)) -} - -// Export a WASI interface directly for instance imports -export function log (bytes: Uint8Array | ArrayBuffer) { - stdout.write(Buffer.from(bytes)); -} -export function logErr (bytes: Uint8Array | ArrayBuffer) { - stderr.write(Buffer.from(bytes)); -} diff --git a/crates/gen-host-js/tests/runtime.rs b/crates/gen-host-js/tests/runtime.rs deleted file mode 100644 index 2e0dcee06..000000000 --- a/crates/gen-host-js/tests/runtime.rs +++ /dev/null @@ -1,100 +0,0 @@ -use clap::Parser; -use std::env; -use std::fs; -use std::path::Path; -use std::process::Command; -use wit_bindgen_gen_host_js; - -test_helpers::runtime_component_tests!("ts"); - -#[derive(Debug, Parser)] -struct Args { - #[clap(flatten)] - opts: wit_bindgen_gen_host_js::Opts, -} - -fn execute(name: &str, lang: &str, wasm: &Path, ts: &Path) { - let dir = test_helpers::test_directory("runtime", "js", &format!("{name}-{lang}")); - let wasm = std::fs::read(wasm).unwrap(); - - println!("OUT_DIR = {:?}", dir); - println!("Generating bindings..."); - let mut files = Default::default(); - - // Generation flags taken from first line comment - // of the test file. - let src_str = fs::read_to_string(ts).unwrap(); - let flags = get_first_line_flag_comment(&src_str); - let flag_vec: Vec<&str> = flags.split(" ").collect(); - let opts = Args::try_parse_from(flag_vec).unwrap(); - - wit_bindgen_core::component::generate( - &mut *opts.opts.build().unwrap(), - name, - &wasm, - &mut files, - ) - .unwrap(); - for (file, contents) in files.iter() { - let dst = dir.join(file); - std::fs::create_dir_all(dst.parent().unwrap()).unwrap(); - std::fs::write(&dst, contents).unwrap(); - } - - let (cmd, args) = if cfg!(windows) { - ("cmd.exe", &["/c", "npx.cmd"] as &[&str]) - } else { - ("npx", &[] as &[&str]) - }; - - fs::copy(ts, dir.join("host.ts")).unwrap(); - fs::copy("tests/helpers.ts", dir.join("helpers.ts")).unwrap(); - let config = dir.join("tsconfig.json"); - fs::write( - &config, - format!( - r#" - {{ - "files": ["host.ts", "helpers.ts"], - "compilerOptions": {{ - "module": "esnext", - "target": "es2020", - "strict": true, - "strictNullChecks": true, - "baseUrl": {0:?}, - "outDir": {0:?} - }} - }} - "#, - dir, - ), - ) - .unwrap(); - - test_helpers::run_command( - Command::new(cmd) - .args(args) - .arg("tsc") - .arg("--project") - .arg(&config), - ); - - fs::write(dir.join("package.json"), "{\"type\":\"module\"}").unwrap(); - let mut path = Vec::new(); - path.push(env::current_dir().unwrap()); - path.push(dir.clone()); - test_helpers::run_command( - Command::new("node") - .arg("--stack-trace-limit=1000") - .arg(dir.join("host.js")) - .env("NODE_PATH", std::env::join_paths(&path).unwrap()) - .arg(dir), - ); -} - -fn get_first_line_flag_comment(src: &str) -> &str { - src.lines() - .next() - .and_then(|s| s.strip_prefix("// Flags:")) - .unwrap_or("") -} diff --git a/crates/wit-bindgen-demo/Cargo.toml b/crates/wit-bindgen-demo/Cargo.toml deleted file mode 100644 index e6c283520..000000000 --- a/crates/wit-bindgen-demo/Cargo.toml +++ /dev/null @@ -1,25 +0,0 @@ -[package] -name = "wit-bindgen-demo" -authors = ["Alex Crichton "] -version.workspace = true -edition.workspace = true -publish = false - -[lib] -crate-type = ['cdylib'] -test = false -doctest = false - -[dependencies] -anyhow = { workspace = true } -wit-bindgen-core = { workspace = true } -wit-bindgen-gen-guest-rust = { workspace = true } -wit-bindgen-gen-host-js = { workspace = true } -wit-bindgen-gen-guest-c = { workspace = true } -wit-bindgen-gen-guest-teavm-java = { workspace = true } -wit-bindgen-gen-markdown = { workspace = true } -wit-bindgen-guest-rust = { workspace = true, features = ['default'] } -wasmprinter = { workspace = true } -wit-component = { workspace = true, features = ['dummy-module'] } -test-helpers = { path = '../test-helpers', default-features = false } -wasm-encoder = { workspace = true } diff --git a/crates/wit-bindgen-demo/build.sh b/crates/wit-bindgen-demo/build.sh deleted file mode 100755 index 681445323..000000000 --- a/crates/wit-bindgen-demo/build.sh +++ /dev/null @@ -1,29 +0,0 @@ -#!/bin/bash - -set -ex - -rm -rf static -mkdir static - -# Build the core wasm binary that will become a component -cargo build -p wit-bindgen-demo --target wasm32-unknown-unknown --release - -# Translate the core wasm binary to a component -wasm-tools component new \ - target/wasm32-unknown-unknown/release/wit_bindgen_demo.wasm -o target/demo.wasm - -# Generate JS host bindings -cargo run host js target/demo.wasm --map "console=./console.js" --out-dir static - -# Build JS from TypeScript and then copy in the ace editor as well. -cp crates/wit-bindgen-demo/{index.html,main.ts,console.js} static/ -(cd crates/wit-bindgen-demo && npx tsc ../../static/main.ts --target es6) - -if [ ! -d ace ]; then - mkdir ace - cd ace - curl -L https://github.com/ajaxorg/ace-builds/archive/refs/tags/v1.4.12.tar.gz | tar xzf - - cd .. -fi - -cp -r ace/ace-builds-1.4.12/src static/ace diff --git a/crates/wit-bindgen-demo/console.js b/crates/wit-bindgen-demo/console.js deleted file mode 100644 index 1371d9280..000000000 --- a/crates/wit-bindgen-demo/console.js +++ /dev/null @@ -1,7 +0,0 @@ -export function log(msg) { - console.log(msg); -} - -export function error(msg) { - console.error(msg); -} diff --git a/crates/wit-bindgen-demo/index.html b/crates/wit-bindgen-demo/index.html deleted file mode 100644 index 0d12b8154..000000000 --- a/crates/wit-bindgen-demo/index.html +++ /dev/null @@ -1,107 +0,0 @@ - - - - - - - -
-

Input *.wit

-
- -
-
-

Generated bindings

- -
- - - - - · - - - - -
- · - - - - · - - - -
-
-
-
-
-
-
-
- · - - - -
-
- -
-
-
- - - - diff --git a/crates/wit-bindgen-demo/main.ts b/crates/wit-bindgen-demo/main.ts deleted file mode 100644 index 6668d01a0..000000000 --- a/crates/wit-bindgen-demo/main.ts +++ /dev/null @@ -1,165 +0,0 @@ -import { demo } from './demo.js'; -import { Options } from './exports/demo.js'; - -const { render } = demo; - -class Editor { - input: HTMLTextAreaElement; - language: HTMLSelectElement; - files: HTMLSelectElement - rustUnchecked: HTMLInputElement; - jsCompat: HTMLInputElement; - jsInstantiation: HTMLInputElement; - generatedFiles: Record; - options: Options; - rerender: number | null; - inputEditor: AceAjax.Editor; - outputEditor: AceAjax.Editor; - outputHtml: HTMLDivElement; - - constructor() { - this.input = document.getElementById('input-raw') as HTMLTextAreaElement; - this.language = document.getElementById('language-select') as HTMLSelectElement; - this.files = document.getElementById('file-select') as HTMLSelectElement; - this.rustUnchecked = document.getElementById('rust-unchecked') as HTMLInputElement; - this.jsCompat = document.getElementById('js-compat') as HTMLInputElement; - this.jsInstantiation = document.getElementById('js-instantiation') as HTMLInputElement; - this.outputHtml = document.getElementById('html-output') as HTMLDivElement; - - this.inputEditor = ace.edit("input"); - this.outputEditor = ace.edit("output"); - this.inputEditor.setValue(this.input.value); - this.inputEditor.clearSelection(); - this.outputEditor.setReadOnly(true); - this.inputEditor.setOption("useWorker", false); - this.outputEditor.setOption("useWorker", false); - - this.generatedFiles = {}; - this.options = { - rustUnchecked: false, - jsCompat: false, - jsInstantiation: false, - }; - this.rerender = null; - } - - init() { - this.installListeners(); - this.render(); - } - - installListeners() { - this.inputEditor.on('change', () => { - this.input.value = this.inputEditor.getValue(); - if (this.rerender !== null) - clearTimeout(this.rerender); - this.rerender = setTimeout(() => this.render(), 500); - }); - - this.language.addEventListener('change', () => this.render()); - - this.rustUnchecked.addEventListener('change', () => { - this.options.rustUnchecked = this.rustUnchecked.checked; - this.render(); - }); - - this.jsCompat.addEventListener('change', () => { - this.options.jsCompat = this.jsCompat.checked; - this.render(); - }); - - this.jsInstantiation.addEventListener('change', () => { - this.options.jsInstantiation = this.jsInstantiation.checked; - this.render(); - }); - - this.files.addEventListener('change', () => this.updateSelectedFile()); - } - - - render() { - for (let div of document.querySelectorAll('.lang-configure')) { - (div as HTMLDivElement).style.display = 'none'; - } - - const config = document.getElementById(`configure-${this.language.value}`); - config.style.display = 'inline-block'; - - const wit = this.inputEditor.getValue(); - let lang; - switch (this.language.value) { - case "js": - case "rust": - case "java": - case "c": - case "markdown": - lang = this.language.value; - break; - default: return; - } - try { - const results = render(lang, wit, this.options); - this.generatedFiles = {}; - const selectedFile = this.files.value; - this.files.options.length = 0; - for (let i = 0; i < results.length; i++) { - const name = results[i][0]; - const contents = results[i][1]; - this.files.options[i] = new Option(name, name); - this.generatedFiles[name] = contents; - } - if (selectedFile in this.generatedFiles) - this.files.value = selectedFile; - - this.updateSelectedFile(); - } catch (e) { - this.outputEditor.setValue(e.payload); - this.outputEditor.clearSelection(); - this.showOutputEditor(); - } - } - - showOutputEditor() { - this.outputHtml.style.display = 'none'; - document.getElementById('output').style.display = 'block'; - } - - showOutputHtml() { - this.outputHtml.style.display = 'block'; - document.getElementById('output').style.display = 'none'; - } - - updateSelectedFile() { - if (this.files.value.endsWith('.html')) { - const html = this.generatedFiles[this.files.value]; - this.outputHtml.innerHTML = html; - this.showOutputHtml(); - return; - } - - this.showOutputEditor(); - this.outputEditor.setValue(this.generatedFiles[this.files.value]); - this.outputEditor.clearSelection(); - if (this.files.value.endsWith('.d.ts')) - this.outputEditor.session.setMode("ace/mode/typescript"); - else if (this.files.value.endsWith('.js')) - this.outputEditor.session.setMode("ace/mode/javascript"); - else if (this.files.value.endsWith('.rs')) - this.outputEditor.session.setMode("ace/mode/rust"); - else if (this.files.value.endsWith('.c')) - this.outputEditor.session.setMode("ace/mode/c_cpp"); - else if (this.files.value.endsWith('.h')) - this.outputEditor.session.setMode("ace/mode/c_cpp"); - else if (this.files.value.endsWith('.md')) - this.outputEditor.session.setMode("ace/mode/markdown"); - else if (this.files.value.endsWith('.py')) - this.outputEditor.session.setMode("ace/mode/python"); - else if (this.files.value.endsWith('.java')) - this.outputEditor.session.setMode("ace/mode/java"); - else - this.outputEditor.session.setMode(null); - } -} - - -(new Editor()).init() diff --git a/crates/wit-bindgen-demo/package.json b/crates/wit-bindgen-demo/package.json deleted file mode 100644 index 899e2d314..000000000 --- a/crates/wit-bindgen-demo/package.json +++ /dev/null @@ -1,6 +0,0 @@ -{ - "devDependencies": { - "@types/ace": "^0.0.46", - "typescript": "^4.3.4" - } -} diff --git a/crates/wit-bindgen-demo/src/lib.rs b/crates/wit-bindgen-demo/src/lib.rs deleted file mode 100644 index 66274d3ed..000000000 --- a/crates/wit-bindgen-demo/src/lib.rs +++ /dev/null @@ -1,112 +0,0 @@ -use anyhow::{anyhow, Result}; -use std::path::Path; -use std::sync::Once; -use wasm_encoder::{CustomSection, Encode, Section}; -use wit_bindgen_core::component::ComponentGenerator; -use wit_bindgen_core::wit_parser::{Resolve, UnresolvedPackage}; -use wit_bindgen_core::{Files, WorldGenerator}; - -wit_bindgen_guest_rust::generate!("demo"); - -struct Demo; - -export_demo!(Demo); - -impl demo::Demo for Demo { - fn render( - lang: demo::Lang, - wit: String, - options: demo::Options, - ) -> Result, String> { - init(); - - let mut files = Files::default(); - render(lang, &wit, &mut files, &options).map_err(|e| format!("{:?}", e))?; - - Ok(files - .iter() - .map(|(name, contents)| { - let contents = if contents.starts_with(b"\0asm") { - wasmprinter::print_bytes(contents).unwrap() - } else { - String::from_utf8_lossy(&contents).into() - }; - (name.to_string(), contents) - }) - .collect()) - } -} - -fn init() { - static INIT: Once = Once::new(); - INIT.call_once(|| { - console::log("installing panic hook"); - let prev_hook = std::panic::take_hook(); - std::panic::set_hook(Box::new(move |info| { - console::error(&info.to_string()); - prev_hook(info); - })); - }); -} - -fn render(lang: demo::Lang, wit: &str, files: &mut Files, options: &demo::Options) -> Result<()> { - let mut resolve = Resolve::default(); - let pkg = resolve.push( - UnresolvedPackage::parse(Path::new("input"), &wit)?, - &Default::default(), - )?; - let (_, doc) = resolve.packages[pkg].documents.iter().next().unwrap(); - let doc = &resolve.documents[*doc]; - let world = doc - .default_world - .ok_or_else(|| anyhow!("no `default world` specified in document"))?; - - let gen_world = |mut gen: Box, files: &mut Files| { - gen.generate(&resolve, world, files); - }; - - // This generator takes a component as input as opposed to an `Interface`. - // To work with this demo a dummy component is synthesized to generate - // bindings for. The dummy core wasm module is created from the - // `test_helpers` support this workspace already offsets, and then - // `wit-component` is used to synthesize a component from our input - // interface and dummy module. Finally this component is fed into the host - // generator which gives us the files we want. - let gen_component = |mut gen: Box, files: &mut Files| { - let mut dummy = wit_component::dummy_module(&resolve, world); - let metadata = - wit_component::metadata::encode(&resolve, world, wit_component::StringEncoding::UTF8)?; - let section = CustomSection { - name: "component-type", - data: &metadata, - }; - dummy.push(section.id()); - section.encode(&mut dummy); - let wasm = wit_component::ComponentEncoder::default() - .module(&dummy)? - .encode()?; - wit_bindgen_core::component::generate(&mut *gen, "input", &wasm, files) - }; - - match lang { - demo::Lang::Rust => { - let mut opts = wit_bindgen_gen_guest_rust::Opts::default(); - opts.unchecked = options.rust_unchecked; - gen_world(opts.build(), files) - } - demo::Lang::Java => gen_world( - wit_bindgen_gen_guest_teavm_java::Opts::default().build(), - files, - ), - demo::Lang::C => gen_world(wit_bindgen_gen_guest_c::Opts::default().build(), files), - demo::Lang::Markdown => gen_world(wit_bindgen_gen_markdown::Opts::default().build(), files), - demo::Lang::Js => { - let mut opts = wit_bindgen_gen_host_js::Opts::default(); - opts.instantiation = options.js_instantiation; - opts.compat = options.js_compat; - gen_component(opts.build()?, files)? - } - } - - Ok(()) -} diff --git a/crates/wit-bindgen-demo/wit/demo.wit b/crates/wit-bindgen-demo/wit/demo.wit deleted file mode 100644 index 95c8bc1ea..000000000 --- a/crates/wit-bindgen-demo/wit/demo.wit +++ /dev/null @@ -1,26 +0,0 @@ -default world demo { - import console: interface { - log: func(msg: string) - error: func(msg: string) - } - - export demo: interface { - type files = list> - - enum lang { - js, - rust, - java, - c, - markdown, - } - - record options { - rust-unchecked: bool, - js-compat: bool, - js-instantiation: bool, - } - - render: func(lang: lang, wit: string, options: options) -> result - } -} diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 791957e4a..9c7e8ae54 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -1,7 +1,6 @@ use anyhow::{anyhow, bail, Context, Result}; use clap::Parser; use std::path::PathBuf; -use wit_bindgen_core::component::ComponentGenerator; use wit_bindgen_core::{wit_parser, Files, WorldGenerator}; use wit_parser::{Resolve, UnresolvedPackage}; @@ -20,9 +19,6 @@ struct Opt { #[derive(Debug, Parser)] enum Category { - /// Generators for creating hosts that embed WASM modules/components. - #[command(subcommand)] - Host(HostGenerator), /// Generators for writing guest WASM modules/components. #[command(subcommand)] Guest(GuestGenerator), @@ -37,17 +33,6 @@ enum Category { }, } -#[derive(Debug, Parser)] -enum HostGenerator { - /// Generates bindings for JavaScript hosts. - Js { - #[clap(flatten)] - opts: wit_bindgen_gen_host_js::Opts, - #[clap(flatten)] - component: ComponentOpts, - }, -} - #[derive(Debug, Parser)] enum GuestGenerator { /// Generates bindings for Rust guest modules. @@ -121,7 +106,6 @@ impl Opt { | Category::Guest(GuestGenerator::C { common, .. }) | Category::Guest(GuestGenerator::TeavmJava { common, .. }) | Category::Markdown { common, .. } => common, - Category::Host(HostGenerator::Js { component, .. }) => &component.common, } } } @@ -132,9 +116,6 @@ fn main() -> Result<()> { let mut files = Files::default(); match opt.category { - Category::Host(HostGenerator::Js { opts, component }) => { - gen_component(opts.build()?, component, &mut files)?; - } Category::Guest(GuestGenerator::Rust { opts, world, .. }) => { gen_world(opts.build(), world, &mut files)?; } @@ -214,23 +195,3 @@ fn gen_world( generator.generate(&resolve, world, files); Ok(()) } - -fn gen_component( - mut generator: Box, - opts: ComponentOpts, - files: &mut Files, -) -> Result<()> { - let wasm = wat::parse_file(&opts.component)?; - let name = match &opts.name { - Some(name) => name.as_str(), - None => opts - .component - .file_stem() - .and_then(|s| s.to_str()) - .ok_or_else(|| anyhow!("filename not valid utf-8"))?, - }; - - wit_bindgen_core::component::generate(&mut *generator, name, &wasm, files)?; - - Ok(()) -} From 1417cfbb8fdbce774ebbd1daa157571d943e3591 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 10:42:57 -0800 Subject: [PATCH 04/17] Update the CLI with just guest generators No need for a `guest` subcommand any more since all supported generators are just guest generators. --- src/bin/wit-bindgen.rs | 83 +++++++++--------------------------------- 1 file changed, 17 insertions(+), 66 deletions(-) diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 9c7e8ae54..29f86b110 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -15,53 +15,22 @@ fn version() -> &'static str { struct Opt { #[command(subcommand)] category: Category, + #[clap(flatten)] + common: Common, + #[clap(flatten)] + world: WorldOpt, } #[derive(Debug, Parser)] enum Category { - /// Generators for writing guest WASM modules/components. - #[command(subcommand)] - Guest(GuestGenerator), /// This generator outputs a Markdown file describing an interface. - Markdown { - #[clap(flatten)] - opts: wit_bindgen_gen_markdown::Opts, - #[clap(flatten)] - common: Common, - #[clap(flatten)] - world: WorldOpt, - }, -} - -#[derive(Debug, Parser)] -enum GuestGenerator { + Markdown(wit_bindgen_gen_markdown::Opts), /// Generates bindings for Rust guest modules. - Rust { - #[clap(flatten)] - opts: wit_bindgen_gen_guest_rust::Opts, - #[clap(flatten)] - common: Common, - #[clap(flatten)] - world: WorldOpt, - }, + Rust(wit_bindgen_gen_guest_rust::Opts), /// Generates bindings for C/CPP guest modules. - C { - #[clap(flatten)] - opts: wit_bindgen_gen_guest_c::Opts, - #[clap(flatten)] - common: Common, - #[clap(flatten)] - world: WorldOpt, - }, + C(wit_bindgen_gen_guest_c::Opts), /// Generates bindings for TeaVM-based Java guest modules. - TeavmJava { - #[clap(flatten)] - opts: wit_bindgen_gen_guest_teavm_java::Opts, - #[clap(flatten)] - common: Common, - #[clap(flatten)] - world: WorldOpt, - }, + TeavmJava(wit_bindgen_gen_guest_teavm_java::Opts), } #[derive(Debug, Parser)] @@ -99,39 +68,21 @@ struct Common { out_dir: Option, } -impl Opt { - fn common(&self) -> &Common { - match &self.category { - Category::Guest(GuestGenerator::Rust { common, .. }) - | Category::Guest(GuestGenerator::C { common, .. }) - | Category::Guest(GuestGenerator::TeavmJava { common, .. }) - | Category::Markdown { common, .. } => common, - } - } -} - fn main() -> Result<()> { let opt: Opt = Opt::parse(); - let common = opt.common().clone(); let mut files = Files::default(); - match opt.category { - Category::Guest(GuestGenerator::Rust { opts, world, .. }) => { - gen_world(opts.build(), world, &mut files)?; - } - Category::Guest(GuestGenerator::C { opts, world, .. }) => { - gen_world(opts.build(), world, &mut files)?; - } - Category::Guest(GuestGenerator::TeavmJava { opts, world, .. }) => { - gen_world(opts.build(), world, &mut files)?; - } - Category::Markdown { opts, world, .. } => { - gen_world(opts.build(), world, &mut files)?; - } - } + let generator = match opt.category { + Category::Rust(opts) => opts.build(), + Category::C(opts) => opts.build(), + Category::TeavmJava(opts) => opts.build(), + Category::Markdown(opts) => opts.build(), + }; + + gen_world(generator, opt.world, &mut files)?; for (name, contents) in files.iter() { - let dst = match &common.out_dir { + let dst = match &opt.common.out_dir { Some(path) => path.join(name), None => name.into(), }; From ea21f4ccc87063516a56657834b0c91aaedb5be3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 10:44:00 -0800 Subject: [PATCH 05/17] Let tests run for the `wit-bindgen` binary This'll make sure that it builds as part of `cargo test` --- Cargo.toml | 1 - 1 file changed, 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 79b1b8fac..f587f7a76 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -40,7 +40,6 @@ wit-bindgen-rust-macro-shared = { path = 'crates/rust-macro-shared', version = ' [[bin]] name = "wit-bindgen" -test = false [dependencies] anyhow = { workspace = true } From 8814c4a2f7260e94d11ab34d6a9fc8586ac6f243 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:31:28 -0800 Subject: [PATCH 06/17] Add scaffolding to run tests in Wasmtime Tests will unfortunately take longer due to wasmtime build times being somewhat nontrivial and additionally rebuilding the java components on each invocation is costly, but for now this should keep the tests relatively simple and easy to maintain by having all the logic be in one place rather than across multiple crates. --- .github/workflows/main.yml | 7 + Cargo.lock | 1020 ++++++++++++++++- Cargo.toml | 24 +- crates/test-helpers/Cargo.toml | 7 - crates/test-helpers/runtime-macro/Cargo.toml | 26 - crates/test-helpers/runtime-macro/build.rs | 353 ------ crates/test-helpers/runtime-macro/src/lib.rs | 42 - crates/test-rust-wasm/artifacts/Cargo.toml | 5 + crates/test-rust-wasm/artifacts/build.rs | 60 + crates/test-rust-wasm/artifacts/src/lib.rs | 1 + crates/wasi_snapshot_preview1/build.rs | 5 + crates/wasi_snapshot_preview1/src/lib.rs | 11 +- .../wasi_snapshot_preview1/wit/testwasi.wit | 6 + src/bin/wit-bindgen.rs | 8 + tests/runtime/main.rs | 319 ++++++ tests/runtime/numbers.rs | 191 +++ tests/runtime/numbers/host.ts | 87 -- 17 files changed, 1599 insertions(+), 573 deletions(-) delete mode 100644 crates/test-helpers/runtime-macro/Cargo.toml delete mode 100644 crates/test-helpers/runtime-macro/build.rs delete mode 100644 crates/test-helpers/runtime-macro/src/lib.rs create mode 100644 crates/test-rust-wasm/artifacts/Cargo.toml create mode 100644 crates/test-rust-wasm/artifacts/build.rs create mode 100644 crates/test-rust-wasm/artifacts/src/lib.rs create mode 100644 crates/wasi_snapshot_preview1/build.rs create mode 100644 crates/wasi_snapshot_preview1/wit/testwasi.wit create mode 100644 tests/runtime/main.rs create mode 100644 tests/runtime/numbers.rs delete mode 100644 tests/runtime/numbers/host.ts diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 24064168d..f8ce3a685 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -53,6 +53,13 @@ jobs: java-version: '18' distribution: 'adopt' - run: cargo test --workspace + - run: cargo build + - run: cargo build --no-default-features + - run: cargo build --no-default-features --features rust + - run: cargo build --no-default-features --features c + - run: cargo build --no-default-features --features teavm-java + - run: cargo build --no-default-features --features markdown + rustfmt: name: Rustfmt diff --git a/Cargo.lock b/Cargo.lock index f73a37e5d..a8a4afa8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,26 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + [[package]] name = "aho-corasick" version = "0.7.20" @@ -17,18 +37,59 @@ version = "1.0.68" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61" +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + +[[package]] +name = "async-trait" +version = "0.1.63" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eff18d764974428cf3a9328e23fc5c986f5fbed46e6cd4cdf42544df5d297ec1" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "base64" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8" + +[[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 = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "block-buffer" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cce20737498f97b993470a6e536b8523f0af7892a4f928cceb1ac5e52ebe7e" +dependencies = [ + "generic-array", +] + [[package]] name = "bstr" version = "1.1.0" @@ -39,11 +100,26 @@ dependencies = [ "serde", ] +[[package]] +name = "bumpalo" +version = "3.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + [[package]] name = "cc" version = "1.0.78" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d" +dependencies = [ + "jobserver", +] [[package]] name = "cfg-if" @@ -97,6 +173,235 @@ dependencies = [ "quote", ] +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28d997bd5e24a5928dd43e46dc529867e207907fe0b239c3477d924f7f2ca320" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "arrayvec", + "bumpalo", + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "gimli", + "hashbrown", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" + +[[package]] +name = "cranelift-entity" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" + +[[package]] +name = "cranelift-native" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.93.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser", + "wasmtime-types", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset 0.7.1", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crypto-common" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "digest" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8168378f4e5023e7218c89c891c0fd8ecdb5e5e4f18cb78f38cf245dd021e76f" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797" + +[[package]] +name = "encoding_rs" +version = "0.8.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9852635589dc9f9ea1b6fe9f05b50ef208c85c834a562f0c6abb1c475736ec2b" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "env_logger" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85cdab6a89accf66733ad5a1693a4dcced6aeff64602b634530dd73c1f3ee9f0" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + [[package]] name = "errno" version = "0.2.8" @@ -118,6 +423,22 @@ dependencies = [ "libc", ] +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "file-per-thread-logger" +version = "0.1.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "84f2e425d9790201ba4af4630191feac6dcc98765b118d4d18e91d23c2353866" +dependencies = [ + "env_logger", + "log", +] + [[package]] name = "fnv" version = "1.0.7" @@ -133,6 +454,47 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + [[package]] name = "globset" version = "0.4.10" @@ -151,6 +513,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash", +] [[package]] name = "heck" @@ -170,6 +535,12 @@ dependencies = [ "libc", ] +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + [[package]] name = "id-arena" version = "2.2.1" @@ -211,6 +582,7 @@ checksum = "1885e79c1fc4b10f0e172c475f458b7f7b93061064d98c3293e98c5ba0c8b399" dependencies = [ "autocfg", "hashbrown", + "serde", ] [[package]] @@ -235,6 +607,44 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "itertools" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" +dependencies = [ + "either", +] + +[[package]] +name = "ittapi" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e648c437172ce7d3ac35ca11a068755072054826fa455a916b43524fa4a62a7" +dependencies = [ + "anyhow", + "ittapi-sys", + "log", +] + +[[package]] +name = "ittapi-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9b32a4d23f72548178dde54f3c12c6b6a08598e25575c0d0fa5bd861e0dc1a5" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "068b1ee6743e4d11fb9c6a1e6064b3693a1b600e7f5f5988047d98b3dc9fb90b" +dependencies = [ + "libc", +] + [[package]] name = "lazy_static" version = "1.4.0" @@ -268,12 +678,70 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memfd" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b20a59d985586e4a5aef64564ac77299f8586d8be6cf9106a5a40207e8908efb" +dependencies = [ + "rustix", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "memoffset" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" +dependencies = [ + "autocfg", +] + +[[package]] +name = "num_cpus" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" +dependencies = [ + "hermit-abi", + "libc", +] + +[[package]] +name = "object" +version = "0.29.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21158b2c33aa6d4561f1c0a6ea283ca92bc54802a93b263e910746d679a7eb53" +dependencies = [ + "crc32fast", + "hashbrown", + "indexmap", + "memchr", +] + [[package]] name = "once_cell" version = "1.17.0" @@ -281,68 +749,179 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" [[package]] -name = "os_str_bytes" -version = "6.4.1" +name = "os_str_bytes" +version = "6.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" + +[[package]] +name = "paste" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba" + +[[package]] +name = "percent-encoding" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" + +[[package]] +name = "pkg-config" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" + +[[package]] +name = "ppv-lite86" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874" +dependencies = [ + "cc", +] + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] [[package]] -name = "percent-encoding" -version = "2.2.0" +name = "rand_chacha" +version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "rand_core" +version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", + "getrandom", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "rayon" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "either", + "rayon-core", ] [[package]] -name = "proc-macro2" -version = "1.0.50" +name = "rayon-core" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" dependencies = [ - "unicode-ident", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", ] [[package]] -name = "pulldown-cmark" -version = "0.8.0" +name = "redox_syscall" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ "bitflags", - "memchr", - "unicase", ] [[package]] -name = "quote" -version = "1.0.23" +name = "redox_users" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "proc-macro2", + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regalloc2" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300d4fbfb40c1c66a78ba3ddd41c1110247cf52f97b87d0f2fc9209bd49b030c" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", ] [[package]] @@ -363,16 +942,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" [[package]] -name = "runtime-macro" -version = "0.3.0" -dependencies = [ - "heck", - "quote", - "wit-bindgen-core", - "wit-bindgen-gen-guest-c", - "wit-bindgen-gen-guest-teavm-java", - "wit-component", -] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" [[package]] name = "rustix" @@ -397,11 +970,60 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + [[package]] name = "serde" version = "1.0.152" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.152" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "sha2" +version = "0.10.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "slice-group-by" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "strsim" @@ -420,6 +1042,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "target-lexicon" +version = "0.12.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9410d0f6853b1d94f0e519fb95df60f29d2c1eff2d921ffdf01a4c8a3b54f12d" + [[package]] name = "termcolor" version = "1.2.0" @@ -429,12 +1057,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "test-artifacts" +version = "0.1.0" + [[package]] name = "test-helpers" version = "0.3.0" dependencies = [ "codegen-macro", - "runtime-macro", "wasm-encoder", "wat", "wit-bindgen-core", @@ -449,6 +1080,26 @@ dependencies = [ "wit-bindgen-guest-rust", ] +[[package]] +name = "thiserror" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a9cd18aa97d5c45c6603caea1da6628790b37f7a34b6ca89522331c5180fed0" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "thread_local" version = "1.1.4" @@ -473,6 +1124,21 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + +[[package]] +name = "typenum" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba" + [[package]] name = "unicase" version = "2.6.0" @@ -582,6 +1248,238 @@ dependencies = [ "url", ] +[[package]] +name = "wasmprinter" +version = "0.2.49" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27c13dff901f9354fa9a6a877152d9c5642513645985635c9b83bcca99e40ea1" +dependencies = [ + "anyhow", + "wasmparser", +] + +[[package]] +name = "wasmtime" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "anyhow", + "async-trait", + "bincode", + "cfg-if", + "encoding_rs", + "indexmap", + "libc", + "log", + "object", + "once_cell", + "paste", + "psm", + "rayon", + "serde", + "target-lexicon", + "wasmparser", + "wasmtime-cache", + "wasmtime-component-macro", + "wasmtime-component-util", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit", + "wasmtime-runtime", + "wat", + "windows-sys", +] + +[[package]] +name = "wasmtime-asm-macros" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "wasmtime-cache" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "anyhow", + "base64", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix", + "serde", + "sha2", + "toml", + "windows-sys", + "zstd", +] + +[[package]] +name = "wasmtime-component-macro" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "anyhow", + "proc-macro2", + "quote", + "syn", + "wasmtime-component-util", + "wasmtime-wit-bindgen", + "wit-parser", +] + +[[package]] +name = "wasmtime-component-util" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" + +[[package]] +name = "wasmtime-cranelift" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "object", + "target-lexicon", + "thiserror", + "wasmparser", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli", + "indexmap", + "log", + "object", + "serde", + "target-lexicon", + "thiserror", + "wasm-encoder", + "wasmparser", + "wasmprinter", + "wasmtime-component-util", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-fiber" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cc", + "cfg-if", + "rustix", + "wasmtime-asm-macros", + "windows-sys", +] + +[[package]] +name = "wasmtime-jit" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "addr2line", + "anyhow", + "bincode", + "cfg-if", + "cpp_demangle", + "gimli", + "ittapi", + "log", + "object", + "rustc-demangle", + "serde", + "target-lexicon", + "wasmtime-environ", + "wasmtime-jit-debug", + "wasmtime-jit-icache-coherence", + "wasmtime-runtime", + "windows-sys", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "object", + "once_cell", + "rustix", +] + +[[package]] +name = "wasmtime-jit-icache-coherence" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cfg-if", + "libc", + "windows-sys", +] + +[[package]] +name = "wasmtime-runtime" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "anyhow", + "cc", + "cfg-if", + "encoding_rs", + "indexmap", + "libc", + "log", + "mach", + "memfd", + "memoffset 0.6.5", + "paste", + "rand", + "rustix", + "wasmtime-asm-macros", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-debug", + "windows-sys", +] + +[[package]] +name = "wasmtime-types" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser", +] + +[[package]] +name = "wasmtime-wit-bindgen" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "anyhow", + "heck", + "wit-parser", +] + [[package]] name = "wast" version = "52.0.2" @@ -697,6 +1595,9 @@ version = "0.3.0" dependencies = [ "anyhow", "clap", + "heck", + "test-artifacts", + "wasmtime", "wat", "wit-bindgen-core", "wit-bindgen-gen-guest-c", @@ -704,6 +1605,7 @@ dependencies = [ "wit-bindgen-gen-guest-teavm-java", "wit-bindgen-gen-markdown", "wit-component", + "wit-parser", ] [[package]] @@ -822,3 +1724,33 @@ dependencies = [ "unicode-xid", "url", ] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.5+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edc50ffce891ad571e9f9afe5039c4837bede781ac4bb13052ed7ae695518596" +dependencies = [ + "cc", + "libc", + "pkg-config", +] diff --git a/Cargo.toml b/Cargo.toml index f587f7a76..a318d7c13 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -45,9 +45,25 @@ name = "wit-bindgen" anyhow = { workspace = true } clap = { workspace = true } wit-bindgen-core = { workspace = true } -wit-bindgen-gen-guest-rust = { workspace = true, features = ['clap'] } -wit-bindgen-gen-guest-c = { workspace = true, features = ['clap'] } -wit-bindgen-gen-markdown = { workspace = true, features = ['clap'] } -wit-bindgen-gen-guest-teavm-java = { workspace = true, features = ['clap'] } +wit-bindgen-gen-guest-rust = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-gen-guest-c = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-gen-markdown = { workspace = true, features = ['clap'], optional = true } +wit-bindgen-gen-guest-teavm-java = { workspace = true, features = ['clap'], optional = true } wat = { workspace = true } wit-component = { workspace = true } + +[features] +default = ['c', 'rust', 'markdown', 'teavm-java'] +c = ['dep:wit-bindgen-gen-guest-c'] +rust = ['dep:wit-bindgen-gen-guest-rust'] +markdown = ['dep:wit-bindgen-gen-markdown'] +teavm-java = ['dep:wit-bindgen-gen-guest-teavm-java'] + +[dev-dependencies] +heck = { workspace = true } +wasmtime = { version = "6.0.0", features = ['component-model'] } +test-artifacts = { path = 'crates/test-rust-wasm/artifacts' } +wit-parser = { workspace = true } + +[patch.crates-io] +wasmtime = { git = 'https://github.com/bytecodealliance/wasmtime' } diff --git a/crates/test-helpers/Cargo.toml b/crates/test-helpers/Cargo.toml index d53e47418..531e4f6d4 100644 --- a/crates/test-helpers/Cargo.toml +++ b/crates/test-helpers/Cargo.toml @@ -9,16 +9,9 @@ doctest = false test = false [dependencies] -runtime-macro = { path = 'runtime-macro', optional = true } codegen-macro = { path = 'codegen-macro' } wit-bindgen-core = { workspace = true } wit-parser = { workspace = true } wit-component = { workspace = true, features = ['dummy-module'] } wat = { workspace = true } wasm-encoder = { workspace = true } - -[features] -default = ['guest-rust', 'guest-c', 'guest-teavm-java'] -guest-rust = ['runtime-macro?/guest-rust'] -guest-c = ['runtime-macro?/guest-c'] -guest-teavm-java = ['runtime-macro?/guest-teavm-java'] diff --git a/crates/test-helpers/runtime-macro/Cargo.toml b/crates/test-helpers/runtime-macro/Cargo.toml deleted file mode 100644 index 8037f39e0..000000000 --- a/crates/test-helpers/runtime-macro/Cargo.toml +++ /dev/null @@ -1,26 +0,0 @@ -[package] -name = "runtime-macro" -authors = ["Alex Crichton "] -version.workspace = true -edition.workspace = true -publish = false - -[lib] -proc-macro = true -doctest = false -test = false - -[dependencies] -quote = "1.0.9" - -[build-dependencies] -heck = { workspace = true } -wit-bindgen-gen-guest-c = { workspace = true, optional = true } -wit-bindgen-gen-guest-teavm-java = { workspace = true, optional = true } -wit-bindgen-core = { workspace = true } -wit-component = { workspace = true } - -[features] -guest-rust = [] -guest-c = ['dep:wit-bindgen-gen-guest-c'] -guest-teavm-java = ['dep:wit-bindgen-gen-guest-teavm-java'] diff --git a/crates/test-helpers/runtime-macro/build.rs b/crates/test-helpers/runtime-macro/build.rs deleted file mode 100644 index 230d9667b..000000000 --- a/crates/test-helpers/runtime-macro/build.rs +++ /dev/null @@ -1,353 +0,0 @@ -use std::fs; -use std::path::{Path, PathBuf}; -use std::process::Command; -use wit_bindgen_core::wit_parser::{Resolve, WorldId}; -use wit_component::ComponentEncoder; - -#[cfg(feature = "guest-c")] -fn guest_c( - wasms: &mut Vec<(String, String, String, String)>, - out_dir: &PathBuf, - wasi_adapter: &[u8], - utf_16: bool, -) { - let utf16_suffix = if utf_16 { "_utf16" } else { "" }; - for test_dir in fs::read_dir("../../../tests/runtime").unwrap() { - let test_dir = test_dir.unwrap().path(); - let c_impl = test_dir.join(format!("wasm{}.c", utf16_suffix)); - if !c_impl.exists() { - continue; - } - println!("cargo:rerun-if-changed={}", c_impl.display()); - let (resolve, world) = read_world(&test_dir); - let snake = resolve.worlds[world].name.replace("-", "_"); - let mut files = Default::default(); - let mut opts = wit_bindgen_gen_guest_c::Opts::default(); - if utf_16 { - opts.string_encoding = wit_component::StringEncoding::UTF16; - } - opts.build().generate(&resolve, world, &mut files); - - let out_dir = out_dir.join(format!( - "c{}-{}", - utf16_suffix, - test_dir.file_name().unwrap().to_str().unwrap() - )); - drop(fs::remove_dir_all(&out_dir)); - fs::create_dir(&out_dir).unwrap(); - for (file, contents) in files.iter() { - let dst = out_dir.join(file); - fs::write(dst, contents).unwrap(); - } - - let path = - PathBuf::from(std::env::var_os("WASI_SDK_PATH").expect( - "point the `WASI_SDK_PATH` environment variable to the path of your wasi-sdk", - )); - let mut cmd = Command::new(path.join("bin/clang")); - let out_wasm = out_dir.join(format!("c{}.wasm", utf16_suffix)); - cmd.arg("--sysroot").arg(path.join("share/wasi-sysroot")); - cmd.arg(c_impl) - .arg(out_dir.join(format!("{snake}.c"))) - .arg(out_dir.join(format!("{snake}_component_type.o"))) - .arg("-I") - .arg(&out_dir) - .arg("-Wall") - .arg("-Wextra") - .arg("-Werror") - .arg("-Wno-unused-parameter") - .arg("-mexec-model=reactor") - .arg("-g") - .arg("-o") - .arg(&out_wasm); - println!("{:?}", cmd); - let output = match cmd.output() { - Ok(output) => output, - Err(e) => panic!("failed to spawn compiler: {}", e), - }; - - if !output.status.success() { - println!("status: {}", output.status); - println!("stdout: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stderr)); - panic!("failed to compile"); - } - - // Translate the canonical ABI module into a component. - let module = fs::read(&out_wasm).expect("failed to read wasm file"); - let component = ComponentEncoder::default() - .module(module.as_slice()) - .expect("pull custom sections from module") - .validate(true) - .adapter("wasi_snapshot_preview1", &wasi_adapter) - .expect("adapter failed to get loaded") - .encode() - .expect(&format!( - "module {:?} can be translated to a component", - out_wasm - )); - let component_path = out_dir.join(format!("c{}.component.wasm", utf16_suffix)); - fs::write(&component_path, component).expect("write component to disk"); - - wasms.push(( - format!("c{}", utf16_suffix), - resolve.worlds[world].name.to_string(), - out_wasm.to_str().unwrap().to_string(), - component_path.to_str().unwrap().to_string(), - )); - } -} - -fn main() { - let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); - - let mut wasms: Vec<(String, String, String, String)> = Vec::new(); - - // Build the `wasi_snapshot_preview1.wasm` adapter which is used to convert - // core wasm modules below into components via `wit-component`. - let mut cmd = Command::new("cargo"); - cmd.arg("build") - .arg("--release") - .current_dir("../../wasi_snapshot_preview1") - .arg("--target=wasm32-unknown-unknown") - .env("CARGO_TARGET_DIR", &out_dir) - .env( - "RUSTFLAGS", - "-Clink-args=--import-memory -Clink-args=-zstack-size=0", - ) - .env_remove("CARGO_ENCODED_RUSTFLAGS"); - let status = cmd.status().unwrap(); - assert!(status.success()); - println!("cargo:rerun-if-changed=../../wasi_snapshot_preview1"); - let wasi_adapter = out_dir.join("wasm32-unknown-unknown/release/wasi_snapshot_preview1.wasm"); - println!("wasi adapter: {:?}", &wasi_adapter); - let wasi_adapter = std::fs::read(&wasi_adapter).unwrap(); - - if cfg!(feature = "guest-rust") { - let mut cmd = Command::new("cargo"); - cmd.arg("build") - .current_dir("../../test-rust-wasm") - .arg("--target=wasm32-wasi") - .env("CARGO_TARGET_DIR", &out_dir) - .env("CARGO_PROFILE_DEV_DEBUG", "1") - .env("RUSTFLAGS", "-Clink-args=--export-table") - .env_remove("CARGO_ENCODED_RUSTFLAGS"); - let status = cmd.status().unwrap(); - assert!(status.success()); - for file in out_dir.join("wasm32-wasi/debug").read_dir().unwrap() { - let file = file.unwrap().path(); - if file.extension().and_then(|s| s.to_str()) != Some("wasm") { - continue; - } - let stem = file.file_stem().unwrap().to_str().unwrap().to_string(); - - // Translate the canonical ABI module into a component. - let module = fs::read(&file).expect("failed to read wasm file"); - let component = ComponentEncoder::default() - .module(module.as_slice()) - .expect("pull custom sections from module") - .validate(true) - .adapter("wasi_snapshot_preview1", &wasi_adapter) - .expect("adapter failed to get loaded") - .encode() - .expect(&format!( - "module {:?} can be translated to a component", - file - )); - let component_path = out_dir.join(format!("{}.component.wasm", stem)); - fs::write(&component_path, component).expect("write component to disk"); - - wasms.push(( - "rust".into(), - stem, - file.to_str().unwrap().to_string(), - component_path.to_str().unwrap().to_string(), - )); - - let dep_file = file.with_extension("d"); - let deps = fs::read_to_string(&dep_file).expect("failed to read dep file"); - for dep in deps - .splitn(2, ":") - .skip(1) - .next() - .unwrap() - .split_whitespace() - { - println!("cargo:rerun-if-changed={}", dep); - } - } - println!("cargo:rerun-if-changed=../../test-rust-wasm/Cargo.toml"); - } - - #[cfg(feature = "guest-c")] - { - guest_c(&mut wasms, &out_dir, &wasi_adapter, false); - guest_c(&mut wasms, &out_dir, &wasi_adapter, true); - } - - #[cfg(feature = "guest-teavm-java")] - { - use heck::*; - - for test_dir in fs::read_dir("../../../tests/runtime").unwrap() { - let test_dir = test_dir.unwrap().path(); - let java_impls = fs::read_dir(&test_dir) - .unwrap() - .filter_map(|entry| { - let path = entry.unwrap().path(); - if let Some("java") = path.extension().map(|ext| ext.to_str().unwrap()) { - Some(path) - } else { - None - } - }) - .collect::>(); - if java_impls.is_empty() { - continue; - } - for java_impl in &java_impls { - println!("cargo:rerun-if-changed={}", java_impl.display()); - } - - let (resolve, world) = read_world(&test_dir); - let world_name = &resolve.worlds[world].name; - let out_dir = out_dir.join(format!("java-{}", world_name)); - drop(fs::remove_dir_all(&out_dir)); - let java_dir = out_dir.join("src/main/java"); - let mut files = Default::default(); - - wit_bindgen_gen_guest_teavm_java::Opts::default() - .build() - .generate(&resolve, world, &mut files); - - let package_dir = java_dir.join(&format!("wit_{}", world_name)); - fs::create_dir_all(&package_dir).unwrap(); - for (file, contents) in files.iter() { - let dst = package_dir.join(file); - fs::write(dst, contents).unwrap(); - } - - let snake = world_name.to_snake_case(); - let upper = world_name.to_upper_camel_case(); - for java_impl in &java_impls { - fs::copy( - &java_impl, - java_dir - .join(&format!("wit_{snake}")) - .join(java_impl.file_name().unwrap()), - ) - .unwrap(); - } - fs::write( - out_dir.join("pom.xml"), - pom_xml(&[ - &format!("wit_{snake}.{upper}"), - &format!("wit_{snake}.{upper}World"), - &format!("wit_{snake}.Imports"), - &format!("wit_{snake}.Exports"), - ]), - ) - .unwrap(); - fs::write( - java_dir.join("Main.java"), - include_bytes!("../../gen-guest-teavm-java/tests/Main.java"), - ) - .unwrap(); - - let mut cmd = mvn(); - cmd.arg("prepare-package").current_dir(&out_dir); - - println!("{cmd:?}"); - let output = match cmd.output() { - Ok(output) => output, - Err(e) => panic!("failed to run Maven: {}", e), - }; - - if !output.status.success() { - println!("status: {}", output.status); - println!("stdout: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stdout)); - println!("stderr: ------------------------------------------"); - println!("{}", String::from_utf8_lossy(&output.stderr)); - panic!("failed to build"); - } - - let out_wasm = out_dir.join("target/generated/wasm/teavm-wasm/classes.wasm"); - - // Translate the canonical ABI module into a component. - let module = fs::read(&out_wasm).expect("failed to read wasm file"); - let component = ComponentEncoder::default() - .module(module.as_slice()) - .expect("pull custom sections from module") - .validate(true) - .adapter("wasi_snapshot_preview1", &wasi_adapter) - .expect("adapter failed to get loaded") - .encode() - .expect(&format!( - "module {out_wasm:?} can be translated to a component", - )); - let component_path = - out_dir.join("target/generated/wasm/teavm-wasm/classes.component.wasm"); - fs::write(&component_path, component).expect("write component to disk"); - - wasms.push(( - "java".into(), - test_dir.file_stem().unwrap().to_str().unwrap().to_string(), - out_wasm.to_str().unwrap().to_string(), - component_path.to_str().unwrap().to_string(), - )); - } - } - - let src = format!("const WASMS: &[(&str, &str, &str, &str)] = &{:?};", wasms); - std::fs::write(out_dir.join("wasms.rs"), src).unwrap(); -} - -fn read_world(dir: &Path) -> (Resolve, WorldId) { - let mut resolve = Resolve::new(); - let (pkg, files) = resolve.push_dir(dir).unwrap(); - for file in files { - println!("cargo:rerun-if-changed={}", file.display()); - } - let world = resolve.packages[pkg] - .documents - .iter() - .filter_map(|(_, doc)| resolve.documents[*doc].default_world) - .next() - .expect("no default world found"); - (resolve, world) -} - -#[cfg(feature = "guest-teavm-java")] -fn mvn() -> Command { - if cfg!(windows) { - let mut cmd = Command::new("cmd"); - cmd.args(&["/c", "mvn"]); - cmd - } else { - Command::new("mvn") - } -} - -#[cfg(feature = "guest-teavm-java")] -fn pom_xml(classes_to_preserve: &[&str]) -> Vec { - let xml = include_str!("../../gen-guest-teavm-java/tests/pom.xml"); - let position = xml.find("").unwrap(); - let (before, after) = xml.split_at(position); - let classes_to_preserve = classes_to_preserve - .iter() - .map(|&class| format!("{class}")) - .collect::>() - .join("\n"); - - format!( - "{before} - - {classes_to_preserve} - - {after}" - ) - .into_bytes() -} diff --git a/crates/test-helpers/runtime-macro/src/lib.rs b/crates/test-helpers/runtime-macro/src/lib.rs deleted file mode 100644 index b9bfe75f9..000000000 --- a/crates/test-helpers/runtime-macro/src/lib.rs +++ /dev/null @@ -1,42 +0,0 @@ -use proc_macro::TokenStream; -use std::env; - -include!(concat!(env!("OUT_DIR"), "/wasms.rs")); - -/// Invoked as `runtime_component_tests!("js")` to run a top-level `execute` -/// function with all host tests that use the "js" extension. -#[proc_macro] -pub fn runtime_component_tests(input: TokenStream) -> TokenStream { - let host_extension = input.to_string(); - let host_extension = host_extension.trim_matches('"'); - let host_file = format!("host.{}", host_extension); - let mut tests = Vec::new(); - let cwd = std::env::current_dir().unwrap(); - for entry in std::fs::read_dir(cwd.join("tests/runtime")).unwrap() { - let entry = entry.unwrap().path(); - if !entry.join(&host_file).exists() { - continue; - } - let name_str = entry.file_name().unwrap().to_str().unwrap(); - for (lang, name, _wasm, component) in WASMS { - if *name != name_str { - continue; - } - let name = quote::format_ident!("{}_{}", name_str, lang); - let host_file = entry.join(&host_file).to_str().unwrap().to_string(); - tests.push(quote::quote! { - #[test] - fn #name() { - crate::execute( - #name_str, - #lang, - #component.as_ref(), - #host_file.as_ref(), - ) - } - }); - } - } - - (quote::quote!(#(#tests)*)).into() -} diff --git a/crates/test-rust-wasm/artifacts/Cargo.toml b/crates/test-rust-wasm/artifacts/Cargo.toml new file mode 100644 index 000000000..5de206c03 --- /dev/null +++ b/crates/test-rust-wasm/artifacts/Cargo.toml @@ -0,0 +1,5 @@ +[package] +name = "test-artifacts" +version = "0.1.0" +edition.workspace = true +publish = false diff --git a/crates/test-rust-wasm/artifacts/build.rs b/crates/test-rust-wasm/artifacts/build.rs new file mode 100644 index 000000000..fa44c9dc5 --- /dev/null +++ b/crates/test-rust-wasm/artifacts/build.rs @@ -0,0 +1,60 @@ +use std::fs; +use std::path::PathBuf; +use std::process::Command; + +fn main() { + std::env::remove_var("CARGO_ENCODED_RUSTFLAGS"); + + let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + + let mut cmd = Command::new("cargo"); + cmd.arg("build") + .arg("--release") + .current_dir("../../wasi_snapshot_preview1") + .arg("--target=wasm32-unknown-unknown") + .env("CARGO_TARGET_DIR", &out_dir); + let status = cmd.status().unwrap(); + assert!(status.success()); + println!("cargo:rerun-if-changed=../../wasi_snapshot_preview1"); + let wasi_adapter = out_dir.join("wasm32-unknown-unknown/release/wasi_snapshot_preview1.wasm"); + + let mut cmd = Command::new("cargo"); + cmd.arg("build") + .current_dir("../../test-rust-wasm") + .arg("--target=wasm32-wasi") + .env("CARGO_TARGET_DIR", &out_dir) + .env("CARGO_PROFILE_DEV_DEBUG", "1"); + let status = cmd.status().unwrap(); + assert!(status.success()); + + let mut wasms = Vec::new(); + for file in out_dir.join("wasm32-wasi/debug").read_dir().unwrap() { + let file = file.unwrap().path(); + if file.extension().and_then(|s| s.to_str()) != Some("wasm") { + continue; + } + + let dep_file = file.with_extension("d"); + let deps = fs::read_to_string(&dep_file).expect("failed to read dep file"); + for dep in deps + .splitn(2, ":") + .skip(1) + .next() + .unwrap() + .split_whitespace() + { + println!("cargo:rerun-if-changed={}", dep); + } + + wasms.push(file); + } + println!("cargo:rerun-if-changed=../../test-rust-wasm/Cargo.toml"); + + let src = format!( + " + pub const ADAPTER: &str = {wasi_adapter:?}; + pub const WASMS: &[&str] = &{wasms:?}; + ", + ); + std::fs::write(out_dir.join("wasms.rs"), src).unwrap(); +} diff --git a/crates/test-rust-wasm/artifacts/src/lib.rs b/crates/test-rust-wasm/artifacts/src/lib.rs new file mode 100644 index 000000000..0cda4ae6f --- /dev/null +++ b/crates/test-rust-wasm/artifacts/src/lib.rs @@ -0,0 +1 @@ +include!(concat!(env!("OUT_DIR"), "/wasms.rs")); diff --git a/crates/wasi_snapshot_preview1/build.rs b/crates/wasi_snapshot_preview1/build.rs new file mode 100644 index 000000000..4f4edeca1 --- /dev/null +++ b/crates/wasi_snapshot_preview1/build.rs @@ -0,0 +1,5 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + println!("cargo:rustc-link-arg=--import-memory"); + println!("cargo:rustc-link-arg=-zstack-size=0"); +} diff --git a/crates/wasi_snapshot_preview1/src/lib.rs b/crates/wasi_snapshot_preview1/src/lib.rs index fdc8d8db5..33f76b04c 100644 --- a/crates/wasi_snapshot_preview1/src/lib.rs +++ b/crates/wasi_snapshot_preview1/src/lib.rs @@ -18,16 +18,7 @@ use std::arch::wasm32::unreachable; use wasi::*; -wit_bindgen_guest_rust::generate!({ - inline: " - default world testwasi { - import testwasi: interface { - log: func(bytes: list) - log-err: func(bytes: list) - } - } - ", -}); +wit_bindgen_guest_rust::generate!("testwasi"); #[no_mangle] pub extern "C" fn environ_get(environ: *mut *mut u8, environ_buf: *mut u8) -> Errno { diff --git a/crates/wasi_snapshot_preview1/wit/testwasi.wit b/crates/wasi_snapshot_preview1/wit/testwasi.wit new file mode 100644 index 000000000..f9377f801 --- /dev/null +++ b/crates/wasi_snapshot_preview1/wit/testwasi.wit @@ -0,0 +1,6 @@ +default world testwasi { + import testwasi: interface { + log: func(bytes: list) + log-err: func(bytes: list) + } +} diff --git a/src/bin/wit-bindgen.rs b/src/bin/wit-bindgen.rs index 29f86b110..231129db6 100644 --- a/src/bin/wit-bindgen.rs +++ b/src/bin/wit-bindgen.rs @@ -24,12 +24,16 @@ struct Opt { #[derive(Debug, Parser)] enum Category { /// This generator outputs a Markdown file describing an interface. + #[cfg(feature = "markdown")] Markdown(wit_bindgen_gen_markdown::Opts), /// Generates bindings for Rust guest modules. + #[cfg(feature = "rust")] Rust(wit_bindgen_gen_guest_rust::Opts), /// Generates bindings for C/CPP guest modules. + #[cfg(feature = "c")] C(wit_bindgen_gen_guest_c::Opts), /// Generates bindings for TeaVM-based Java guest modules. + #[cfg(feature = "teavm-java")] TeavmJava(wit_bindgen_gen_guest_teavm_java::Opts), } @@ -73,9 +77,13 @@ fn main() -> Result<()> { let mut files = Files::default(); let generator = match opt.category { + #[cfg(feature = "rust")] Category::Rust(opts) => opts.build(), + #[cfg(feature = "c")] Category::C(opts) => opts.build(), + #[cfg(feature = "teavm-java")] Category::TeavmJava(opts) => opts.build(), + #[cfg(feature = "markdown")] Category::Markdown(opts) => opts.build(), }; diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs new file mode 100644 index 000000000..636c868ad --- /dev/null +++ b/tests/runtime/main.rs @@ -0,0 +1,319 @@ +use anyhow::Result; +use std::fs; +use std::io::Write; +use std::path::PathBuf; +use std::process::Command; +use wasmtime::component::{Component, Instance, Linker}; +use wasmtime::{Config, Engine, Store}; +use wit_component::ComponentEncoder; +use wit_parser::Resolve; + +mod numbers; + +wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit"); + +#[derive(Default)] +struct Wasi(T); + +impl testwasi::Testwasi for Wasi { + fn log(&mut self, bytes: Vec) -> Result<()> { + std::io::stdout().write_all(&bytes)?; + Ok(()) + } + + fn log_err(&mut self, bytes: Vec) -> Result<()> { + std::io::stderr().write_all(&bytes)?; + Ok(()) + } +} + +fn run_test( + name: &str, + add_to_linker: fn(&mut Linker>) -> Result<()>, + instantiate: fn(&mut Store>, &Component, &Linker>) -> Result<(U, Instance)>, + test: fn(U, &mut Store>) -> Result<()>, +) -> Result<()> +where + T: Default, +{ + // Create an engine with caching enabled to assist with iteration in this + // project. + let mut config = Config::new(); + config.cache_config_load_default()?; + config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); + config.wasm_component_model(true); + let engine = Engine::new(&config)?; + + for wasm in tests(name)? { + let component = Component::from_file(&engine, &wasm)?; + let mut linker = Linker::new(&engine); + + add_to_linker(&mut linker)?; + crate::testwasi::add_to_linker(&mut linker, |x| x)?; + let mut store = Store::new(&engine, Wasi::default()); + let (exports, _) = instantiate(&mut store, &component, &linker)?; + + println!("testing {wasm:?}"); + test(exports, &mut store)?; + } + + Ok(()) +} + +fn tests(name: &str) -> Result> { + let mut result = Vec::new(); + + let mut dir = PathBuf::from("./tests/runtime"); + dir.push(name); + + let mut resolve = Resolve::new(); + let (pkg, _files) = resolve.push_dir(&dir).unwrap(); + let world = resolve.packages[pkg] + .documents + .iter() + .filter_map(|(_, doc)| resolve.documents[*doc].default_world) + .next() + .expect("no default world found"); + + let mut rust = Vec::new(); + let mut c = Vec::new(); + let mut java = Vec::new(); + for file in dir.read_dir()? { + let path = file?.path(); + match path.extension().and_then(|s| s.to_str()) { + Some("c") => c.push(path), + Some("java") => java.push(path), + Some("rs") => rust.push(path), + _ => {} + } + } + + let mut out_dir = std::env::current_exe()?; + out_dir.pop(); + out_dir.pop(); + out_dir.pop(); + out_dir.push("runtime-tests"); + out_dir.push(name); + + println!("wasi adapter = {:?}", test_artifacts::ADAPTER); + let wasi_adapter = std::fs::read(&test_artifacts::ADAPTER)?; + + drop(std::fs::remove_dir_all(&out_dir)); + std::fs::create_dir_all(&out_dir)?; + + if cfg!(feature = "rust") && !rust.is_empty() { + let core = test_artifacts::WASMS + .iter() + .map(PathBuf::from) + .find(|p| match p.file_stem().and_then(|s| s.to_str()) { + Some(n) => n == name, + None => false, + }) + .unwrap(); + println!("rust core module = {core:?}"); + let module = std::fs::read(&core)?; + let wasm = ComponentEncoder::default() + .module(&module)? + .validate(true) + .adapter("wasi_snapshot_preview1", &wasi_adapter)? + .encode()?; + + let dst = out_dir.join("rust.wasm"); + println!("rust component {dst:?}"); + std::fs::write(&dst, &wasm)?; + result.push(dst); + } + + #[cfg(feature = "c")] + for path in c.iter() { + let snake = resolve.worlds[world].name.replace("-", "_"); + let mut files = Default::default(); + let mut opts = wit_bindgen_gen_guest_c::Opts::default(); + if let Some(path) = path.file_name().and_then(|s| s.to_str()) { + if path.contains("utf16") { + opts.string_encoding = wit_component::StringEncoding::UTF16; + } + } + opts.build().generate(&resolve, world, &mut files); + + for (file, contents) in files.iter() { + let dst = out_dir.join(file); + fs::write(dst, contents).unwrap(); + } + + let sdk = + PathBuf::from(std::env::var_os("WASI_SDK_PATH").expect( + "point the `WASI_SDK_PATH` environment variable to the path of your wasi-sdk", + )); + let mut cmd = Command::new(sdk.join("bin/clang")); + let out_wasm = out_dir.join(format!( + "c-{}.wasm", + path.file_stem().and_then(|s| s.to_str()).unwrap() + )); + cmd.arg("--sysroot").arg(sdk.join("share/wasi-sysroot")); + cmd.arg(path) + .arg(out_dir.join(format!("{snake}.c"))) + .arg(out_dir.join(format!("{snake}_component_type.o"))) + .arg("-I") + .arg(&out_dir) + .arg("-Wall") + .arg("-Wextra") + .arg("-Werror") + .arg("-Wno-unused-parameter") + .arg("-mexec-model=reactor") + .arg("-g") + .arg("-o") + .arg(&out_wasm); + println!("{:?}", cmd); + let output = match cmd.output() { + Ok(output) => output, + Err(e) => panic!("failed to spawn compiler: {}", e), + }; + + if !output.status.success() { + println!("status: {}", output.status); + println!("stdout: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stderr)); + panic!("failed to compile"); + } + + // Translate the canonical ABI module into a component. + let module = fs::read(&out_wasm).expect("failed to read wasm file"); + let component = ComponentEncoder::default() + .module(module.as_slice()) + .expect("pull custom sections from module") + .validate(true) + .adapter("wasi_snapshot_preview1", &wasi_adapter) + .expect("adapter failed to get loaded") + .encode() + .expect(&format!( + "module {:?} can be translated to a component", + out_wasm + )); + let component_path = out_wasm.with_extension("component.wasm"); + fs::write(&component_path, component).expect("write component to disk"); + + result.push(component_path); + } + + #[cfg(feature = "teavm-java")] + if !java.is_empty() { + use heck::*; + + let world_name = &resolve.worlds[world].name; + let out_dir = out_dir.join(format!("java-{}", world_name)); + drop(fs::remove_dir_all(&out_dir)); + let java_dir = out_dir.join("src/main/java"); + let mut files = Default::default(); + + wit_bindgen_gen_guest_teavm_java::Opts::default() + .build() + .generate(&resolve, world, &mut files); + + let package_dir = java_dir.join(&format!("wit_{}", world_name)); + fs::create_dir_all(&package_dir).unwrap(); + for (file, contents) in files.iter() { + let dst = package_dir.join(file); + fs::write(dst, contents).unwrap(); + } + + let snake = world_name.to_snake_case(); + let upper = world_name.to_upper_camel_case(); + for java_impl in &java { + fs::copy( + &java_impl, + java_dir + .join(&format!("wit_{snake}")) + .join(java_impl.file_name().unwrap()), + ) + .unwrap(); + } + fs::write( + out_dir.join("pom.xml"), + pom_xml(&[ + &format!("wit_{snake}.{upper}"), + &format!("wit_{snake}.{upper}World"), + &format!("wit_{snake}.Imports"), + &format!("wit_{snake}.Exports"), + ]), + ) + .unwrap(); + fs::write( + java_dir.join("Main.java"), + include_bytes!("../../crates/gen-guest-teavm-java/tests/Main.java"), + ) + .unwrap(); + + let mut cmd = mvn(); + cmd.arg("prepare-package").current_dir(&out_dir); + + println!("{cmd:?}"); + let output = match cmd.output() { + Ok(output) => output, + Err(e) => panic!("failed to run Maven: {}", e), + }; + + if !output.status.success() { + println!("status: {}", output.status); + println!("stdout: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stderr)); + panic!("failed to build"); + } + + let out_wasm = out_dir.join("target/generated/wasm/teavm-wasm/classes.wasm"); + + // Translate the canonical ABI module into a component. + let module = fs::read(&out_wasm).expect("failed to read wasm file"); + let component = ComponentEncoder::default() + .module(module.as_slice()) + .expect("pull custom sections from module") + .validate(true) + .adapter("wasi_snapshot_preview1", &wasi_adapter) + .expect("adapter failed to get loaded") + .encode() + .expect(&format!( + "module {out_wasm:?} can be translated to a component", + )); + let component_path = + out_dir.join("target/generated/wasm/teavm-wasm/classes.component.wasm"); + fs::write(&component_path, component).expect("write component to disk"); + + result.push(component_path); + + fn mvn() -> Command { + if cfg!(windows) { + let mut cmd = Command::new("cmd"); + cmd.args(&["/c", "mvn"]); + cmd + } else { + Command::new("mvn") + } + } + + fn pom_xml(classes_to_preserve: &[&str]) -> Vec { + let xml = include_str!("../../crates/gen-guest-teavm-java/tests/pom.xml"); + let position = xml.find("").unwrap(); + let (before, after) = xml.split_at(position); + let classes_to_preserve = classes_to_preserve + .iter() + .map(|&class| format!("{class}")) + .collect::>() + .join("\n"); + + format!( + "{before} + + {classes_to_preserve} + + {after}" + ) + .into_bytes() + } + } + + Ok(result) +} diff --git a/tests/runtime/numbers.rs b/tests/runtime/numbers.rs new file mode 100644 index 000000000..e45fec646 --- /dev/null +++ b/tests/runtime/numbers.rs @@ -0,0 +1,191 @@ +use anyhow::Result; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/numbers"); + +#[derive(Default)] +pub struct MyImports { + scalar: u32, +} + +impl imports::Imports for MyImports { + fn roundtrip_u8(&mut self, val: u8) -> Result { + Ok(val) + } + + fn roundtrip_s8(&mut self, val: i8) -> Result { + Ok(val) + } + + fn roundtrip_u16(&mut self, val: u16) -> Result { + Ok(val) + } + + fn roundtrip_s16(&mut self, val: i16) -> Result { + Ok(val) + } + + fn roundtrip_u32(&mut self, val: u32) -> Result { + Ok(val) + } + + fn roundtrip_s32(&mut self, val: i32) -> Result { + Ok(val) + } + + fn roundtrip_u64(&mut self, val: u64) -> Result { + Ok(val) + } + + fn roundtrip_s64(&mut self, val: i64) -> Result { + Ok(val) + } + + fn roundtrip_float32(&mut self, val: f32) -> Result { + Ok(val) + } + + fn roundtrip_float64(&mut self, val: f64) -> Result { + Ok(val) + } + + fn roundtrip_char(&mut self, val: char) -> Result { + Ok(val) + } + + fn set_scalar(&mut self, val: u32) -> Result<()> { + self.scalar = val; + Ok(()) + } + + fn get_scalar(&mut self) -> Result { + Ok(self.scalar) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "numbers", + |linker| Numbers::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| Numbers::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(exports: Numbers, store: &mut Store>) -> Result<()> { + exports.test_imports(&mut *store)?; + let exports = exports.exports(); + assert_eq!(exports.roundtrip_u8(&mut *store, 1)?, 1); + assert_eq!( + exports.roundtrip_u8(&mut *store, u8::min_value())?, + u8::min_value() + ); + assert_eq!( + exports.roundtrip_u8(&mut *store, u8::max_value())?, + u8::max_value() + ); + + assert_eq!(exports.roundtrip_s8(&mut *store, 1)?, 1); + assert_eq!( + exports.roundtrip_s8(&mut *store, i8::min_value())?, + i8::min_value() + ); + assert_eq!( + exports.roundtrip_s8(&mut *store, i8::max_value())?, + i8::max_value() + ); + + assert_eq!(exports.roundtrip_u16(&mut *store, 1)?, 1); + assert_eq!( + exports.roundtrip_u16(&mut *store, u16::min_value())?, + u16::min_value() + ); + assert_eq!( + exports.roundtrip_u16(&mut *store, u16::max_value())?, + u16::max_value() + ); + + assert_eq!(exports.roundtrip_s16(&mut *store, 1)?, 1); + assert_eq!( + exports.roundtrip_s16(&mut *store, i16::min_value())?, + i16::min_value() + ); + assert_eq!( + exports.roundtrip_s16(&mut *store, i16::max_value())?, + i16::max_value() + ); + + assert_eq!(exports.roundtrip_u32(&mut *store, 1)?, 1); + assert_eq!( + exports.roundtrip_u32(&mut *store, u32::min_value())?, + u32::min_value() + ); + assert_eq!( + exports.roundtrip_u32(&mut *store, u32::max_value())?, + u32::max_value() + ); + + assert_eq!(exports.roundtrip_s32(&mut *store, 1)?, 1); + assert_eq!( + exports.roundtrip_s32(&mut *store, i32::min_value())?, + i32::min_value() + ); + assert_eq!( + exports.roundtrip_s32(&mut *store, i32::max_value())?, + i32::max_value() + ); + + assert_eq!(exports.roundtrip_u64(&mut *store, 1)?, 1); + assert_eq!( + exports.roundtrip_u64(&mut *store, u64::min_value())?, + u64::min_value() + ); + assert_eq!( + exports.roundtrip_u64(&mut *store, u64::max_value())?, + u64::max_value() + ); + + assert_eq!(exports.roundtrip_s64(&mut *store, 1)?, 1); + assert_eq!( + exports.roundtrip_s64(&mut *store, i64::min_value())?, + i64::min_value() + ); + assert_eq!( + exports.roundtrip_s64(&mut *store, i64::max_value())?, + i64::max_value() + ); + + assert_eq!(exports.roundtrip_float32(&mut *store, 1.0)?, 1.0); + assert_eq!( + exports.roundtrip_float32(&mut *store, f32::INFINITY)?, + f32::INFINITY + ); + assert_eq!( + exports.roundtrip_float32(&mut *store, f32::NEG_INFINITY)?, + f32::NEG_INFINITY + ); + assert!(exports.roundtrip_float32(&mut *store, f32::NAN)?.is_nan()); + + assert_eq!(exports.roundtrip_float64(&mut *store, 1.0)?, 1.0); + assert_eq!( + exports.roundtrip_float64(&mut *store, f64::INFINITY)?, + f64::INFINITY + ); + assert_eq!( + exports.roundtrip_float64(&mut *store, f64::NEG_INFINITY)?, + f64::NEG_INFINITY + ); + assert!(exports.roundtrip_float64(&mut *store, f64::NAN)?.is_nan()); + + assert_eq!(exports.roundtrip_char(&mut *store, 'a')?, 'a'); + assert_eq!(exports.roundtrip_char(&mut *store, ' ')?, ' '); + assert_eq!(exports.roundtrip_char(&mut *store, '🚩')?, '🚩'); + + exports.set_scalar(&mut *store, 2)?; + assert_eq!(exports.get_scalar(&mut *store)?, 2); + exports.set_scalar(&mut *store, 4)?; + assert_eq!(exports.get_scalar(&mut *store)?, 4); + + Ok(()) +} diff --git a/tests/runtime/numbers/host.ts b/tests/runtime/numbers/host.ts deleted file mode 100644 index cdf69f3a6..000000000 --- a/tests/runtime/numbers/host.ts +++ /dev/null @@ -1,87 +0,0 @@ -// Flags: --instantiation - -import * as helpers from "./helpers.js"; -import { instantiate } from "./numbers.js"; - -function assertEq(x: any, y: any) { - if (x !== y) - throw new Error(`${x} != ${y}`); -} - -function assert(x: boolean) { - if (!x) - throw new Error("assert failed"); -} - -async function run() { - let scalar = 0; - const wasm = await instantiate(helpers.loadWasm, { - testwasi: helpers, - imports: { - roundtripU8(x) { return x; }, - roundtripS8(x) { return x; }, - roundtripU16(x) { return x; }, - roundtripS16(x) { return x; }, - roundtripU32(x) { return x; }, - roundtripS32(x) { return x; }, - roundtripU64(x) { return x; }, - roundtripS64(x) { return x; }, - roundtripFloat32(x) { return x; }, - roundtripFloat64(x) { return x; }, - roundtripChar(x) { return x; }, - setScalar(x) { scalar = x; }, - getScalar() { return scalar; }, - }, - }); - - wasm.testImports(); - - assertEq(wasm.exports.roundtripU8(1), 1); - assertEq(wasm.exports.roundtripU8((1 << 8) - 1), (1 << 8) - 1); - - assertEq(wasm.exports.roundtripS8(1), 1); - assertEq(wasm.exports.roundtripS8((1 << 7) - 1), (1 << 7) - 1); - assertEq(wasm.exports.roundtripS8(-(1 << 7)), -(1 << 7)); - - assertEq(wasm.exports.roundtripU16(1), 1); - assertEq(wasm.exports.roundtripU16((1 << 16) - 1), (1 << 16) - 1); - - assertEq(wasm.exports.roundtripS16(1), 1); - assertEq(wasm.exports.roundtripS16((1 << 15) - 1), (1 << 15) - 1); - assertEq(wasm.exports.roundtripS16(-(1 << 15)), -(1 << 15)); - - assertEq(wasm.exports.roundtripU32(1), 1); - assertEq(wasm.exports.roundtripU32(~0 >>> 0), ~0 >>> 0); - - assertEq(wasm.exports.roundtripS32(1), 1); - assertEq(wasm.exports.roundtripS32(((1 << 31) - 1) >>> 0), ((1 << 31) - 1) >>> 0); - assertEq(wasm.exports.roundtripS32(1 << 31), 1 << 31); - - assertEq(wasm.exports.roundtripU64(1n), 1n); - assertEq(wasm.exports.roundtripU64((1n << 64n) - 1n), (1n << 64n) - 1n); - - assertEq(wasm.exports.roundtripS64(1n), 1n); - assertEq(wasm.exports.roundtripS64((1n << 63n) - 1n), (1n << 63n) - 1n); - assertEq(wasm.exports.roundtripS64(-(1n << 63n)), -(1n << 63n)); - - assertEq(wasm.exports.roundtripFloat32(1), 1); - assertEq(wasm.exports.roundtripFloat32(Infinity), Infinity); - assertEq(wasm.exports.roundtripFloat32(-Infinity), -Infinity); - assert(Number.isNaN(wasm.exports.roundtripFloat32(NaN))); - - assertEq(wasm.exports.roundtripFloat64(1), 1); - assertEq(wasm.exports.roundtripFloat64(Infinity), Infinity); - assertEq(wasm.exports.roundtripFloat64(-Infinity), -Infinity); - assert(Number.isNaN(wasm.exports.roundtripFloat64(NaN))); - - assertEq(wasm.exports.roundtripChar('a'), 'a'); - assertEq(wasm.exports.roundtripChar(' '), ' '); - assertEq(wasm.exports.roundtripChar('🚩'), '🚩'); - - wasm.exports.setScalar(2); - assertEq(wasm.exports.getScalar(), 2); - wasm.exports.setScalar(4); - assertEq(wasm.exports.getScalar(), 4); -} - -await run() From 657b9b6d1cc00fa5aaeb3f6f3097328c264f709d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:38:34 -0800 Subject: [PATCH 07/17] Migrate flavorful test --- tests/runtime/flavorful.rs | 169 ++++++++++++++++++++++++++++++++ tests/runtime/flavorful/host.ts | 88 ----------------- tests/runtime/main.rs | 1 + 3 files changed, 170 insertions(+), 88 deletions(-) create mode 100644 tests/runtime/flavorful.rs delete mode 100644 tests/runtime/flavorful/host.ts diff --git a/tests/runtime/flavorful.rs b/tests/runtime/flavorful.rs new file mode 100644 index 000000000..4553ae3ce --- /dev/null +++ b/tests/runtime/flavorful.rs @@ -0,0 +1,169 @@ +use anyhow::Result; +use exports::*; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/flavorful"); + +#[derive(Default)] +pub struct MyImports { + errored: bool, +} + +impl imports::Imports for MyImports { + fn f_list_in_record1(&mut self, ty: imports::ListInRecord1) -> Result<()> { + assert_eq!(ty.a, "list_in_record1"); + Ok(()) + } + + fn f_list_in_record2(&mut self) -> Result { + Ok(imports::ListInRecord2 { + a: "list_in_record2".to_string(), + }) + } + + fn f_list_in_record3(&mut self, a: imports::ListInRecord3) -> Result { + assert_eq!(a.a, "list_in_record3 input"); + Ok(imports::ListInRecord3 { + a: "list_in_record3 output".to_string(), + }) + } + + fn f_list_in_record4(&mut self, a: imports::ListInAlias) -> Result { + assert_eq!(a.a, "input4"); + Ok(imports::ListInRecord4 { + a: "result4".to_string(), + }) + } + + fn f_list_in_variant1( + &mut self, + a: imports::ListInVariant1V1, + b: imports::ListInVariant1V2, + c: imports::ListInVariant1V3, + ) -> Result<()> { + assert_eq!(a.unwrap(), "foo"); + assert_eq!(b.unwrap_err(), "bar"); + match c { + imports::ListInVariant1V3::String(s) => assert_eq!(s, "baz"), + imports::ListInVariant1V3::F32(_) => panic!(), + } + Ok(()) + } + + fn f_list_in_variant2(&mut self) -> Result> { + Ok(Some("list_in_variant2".to_string())) + } + + fn f_list_in_variant3(&mut self, a: imports::ListInVariant3) -> Result> { + assert_eq!(a.unwrap(), "input3"); + Ok(Some("output3".to_string())) + } + + fn errno_result(&mut self) -> Result> { + if self.errored { + return Ok(Ok(())); + } + imports::MyErrno::A.to_string(); + format!("{:?}", imports::MyErrno::A); + fn assert_error() {} + assert_error::(); + self.errored = true; + Ok(Err(imports::MyErrno::B)) + } + + fn list_typedefs( + &mut self, + a: imports::ListTypedef, + b: imports::ListTypedef3, + ) -> Result<(imports::ListTypedef2, imports::ListTypedef3)> { + assert_eq!(a, "typedef1"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef2"); + Ok((b"typedef3".to_vec(), vec!["typedef4".to_string()])) + } + + fn list_of_variants( + &mut self, + bools: Vec, + results: Vec>, + enums: Vec, + ) -> Result<(Vec, Vec>, Vec)> { + assert_eq!(bools, [true, false]); + assert_eq!(results, [Ok(()), Err(())]); + assert_eq!(enums, [imports::MyErrno::Success, imports::MyErrno::A]); + Ok(( + vec![false, true], + vec![Err(()), Ok(())], + vec![imports::MyErrno::A, imports::MyErrno::B], + )) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "flavorful", + |linker| Flavorful::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| Flavorful::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(exports: Flavorful, store: &mut Store>) -> Result<()> { + exports.test_imports(&mut *store)?; + let exports = exports.exports(); + + exports.f_list_in_record1( + &mut *store, + ListInRecord1 { + a: "list_in_record1", + }, + )?; + assert_eq!(exports.f_list_in_record2(&mut *store)?.a, "list_in_record2"); + + assert_eq!( + exports + .f_list_in_record3( + &mut *store, + ListInRecord3Param { + a: "list_in_record3 input" + } + )? + .a, + "list_in_record3 output" + ); + + assert_eq!( + exports + .f_list_in_record4(&mut *store, ListInAliasParam { a: "input4" })? + .a, + "result4" + ); + + exports.f_list_in_variant1( + &mut *store, + Some("foo"), + Err("bar"), + ListInVariant1V3::String("baz"), + )?; + assert_eq!( + exports.f_list_in_variant2(&mut *store)?, + Some("list_in_variant2".to_string()) + ); + assert_eq!( + exports.f_list_in_variant3(&mut *store, Some("input3"))?, + Some("output3".to_string()) + ); + + assert!(exports.errno_result(&mut *store)?.is_err()); + MyErrno::A.to_string(); + format!("{:?}", MyErrno::A); + fn assert_error() {} + assert_error::(); + + let (a, b) = exports.list_typedefs(&mut *store, "typedef1", &["typedef2"])?; + assert_eq!(a, b"typedef3"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef4"); + Ok(()) +} diff --git a/tests/runtime/flavorful/host.ts b/tests/runtime/flavorful/host.ts deleted file mode 100644 index e3fc65317..000000000 --- a/tests/runtime/flavorful/host.ts +++ /dev/null @@ -1,88 +0,0 @@ -// Flags: --map testwasi=./helpers.js,imports=./host.js - -// @ts-ignore -import * as assert from 'assert'; - -// Imports -export function fListInRecord1(x: any) {} -export function fListInRecord2() { return { a: 'list_in_record2' }; } -export function fListInRecord3(x: any) { - assert.strictEqual(x.a, 'list_in_record3 input'); - return { a: 'list_in_record3 output' }; -} -export function fListInRecord4(x: any) { - assert.strictEqual(x.a, 'input4'); - return { a: 'result4' }; -} -export function fListInVariant1(a: any, b: any, c: any) { - assert.strictEqual(a, 'foo'); - assert.deepStrictEqual(b, { tag: 'err', val: 'bar' }); - assert.deepStrictEqual(c, { tag: 0, val: 'baz' }); -} -export function fListInVariant2() { return 'list_in_variant2'; } -export function fListInVariant3(x: any) { - assert.strictEqual(x, 'input3'); - return 'output3'; -} -let firstErr = true; -export function errnoResult() { - if (firstErr) { - firstErr = false; - throw new Error('b'); - } -} -export function listTypedefs(x: any, y: any) { - assert.strictEqual(x, 'typedef1'); - assert.deepStrictEqual(y, ['typedef2']); - return [(new TextEncoder).encode('typedef3'), ['typedef4']]; -} -export function listOfVariants(bools: any, results: any, enums: any) { - assert.deepStrictEqual(bools, [true, false]); - assert.deepStrictEqual(results, [{ tag: 'ok', val: undefined }, { tag: 'err', val: undefined }]); - assert.deepStrictEqual(enums, ["success", "a"]); - return [ - [false, true], - [{ tag: 'err', val: undefined }, { tag: 'ok', val: undefined }], - ["a", "b"], - ]; -} - -export async function run () { - const wasm = await import('./flavorful.js'); - - wasm.testImports(); - wasm.exports.fListInRecord1({ a: "list_in_record1" }); - assert.deepStrictEqual(wasm.exports.fListInRecord2(), { a: "list_in_record2" }); - - assert.deepStrictEqual( - wasm.exports.fListInRecord3({ a: "list_in_record3 input" }), - { a: "list_in_record3 output" }, - ); - - assert.deepStrictEqual( - wasm.exports.fListInRecord4({ a: "input4" }), - { a: "result4" }, - ); - - wasm.exports.fListInVariant1("foo", { tag: 'err', val: 'bar' }, { tag: 0, val: 'baz' }); - - assert.deepStrictEqual(wasm.exports.fListInVariant2(), "list_in_variant2"); - assert.deepStrictEqual(wasm.exports.fListInVariant3("input3"), "output3"); - - try { - wasm.exports.errnoResult(); - assert.ok(false); - } - catch (e: any) { - assert.strictEqual(e.constructor.name, 'ComponentError'); - assert.ok(e.toString().includes('Error: b')); - assert.strictEqual(e.payload, 'b'); - } - - const [r1, r2] = wasm.exports.listTypedefs("typedef1", ["typedef2"]); - assert.deepStrictEqual(r1, (new TextEncoder()).encode('typedef3')); - assert.deepStrictEqual(r2, ['typedef4']); -} - -// TLA cycle avoidance -setTimeout(run); diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 636c868ad..d03b2b061 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -8,6 +8,7 @@ use wasmtime::{Config, Engine, Store}; use wit_component::ComponentEncoder; use wit_parser::Resolve; +mod flavorful; mod numbers; wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit"); From 90d4de0ad7c075cf4dcb623add11c39951f9e694 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:41:29 -0800 Subject: [PATCH 08/17] Migrate the lists test --- tests/runtime/lists.rs | 142 ++++++++++++++++++++++++++++++++++++ tests/runtime/lists/host.ts | 134 ---------------------------------- tests/runtime/main.rs | 1 + 3 files changed, 143 insertions(+), 134 deletions(-) create mode 100644 tests/runtime/lists.rs delete mode 100644 tests/runtime/lists/host.ts diff --git a/tests/runtime/lists.rs b/tests/runtime/lists.rs new file mode 100644 index 000000000..0d0df7997 --- /dev/null +++ b/tests/runtime/lists.rs @@ -0,0 +1,142 @@ +use anyhow::Result; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/lists"); + +use imports::*; + +#[derive(Default)] +pub struct MyImports; + +impl Imports for MyImports { + fn empty_list_param(&mut self, a: Vec) -> Result<()> { + assert_eq!(a, []); + Ok(()) + } + + fn empty_string_param(&mut self, a: String) -> Result<()> { + assert_eq!(a, ""); + Ok(()) + } + + fn empty_list_result(&mut self) -> Result> { + Ok(Vec::new()) + } + + fn empty_string_result(&mut self) -> Result { + Ok(String::new()) + } + + fn list_param(&mut self, list: Vec) -> Result<()> { + assert_eq!(list, [1, 2, 3, 4]); + Ok(()) + } + + fn list_param2(&mut self, ptr: String) -> Result<()> { + assert_eq!(ptr, "foo"); + Ok(()) + } + + fn list_param3(&mut self, ptr: Vec) -> Result<()> { + assert_eq!(ptr.len(), 3); + assert_eq!(ptr[0], "foo"); + assert_eq!(ptr[1], "bar"); + assert_eq!(ptr[2], "baz"); + Ok(()) + } + + fn list_param4(&mut self, ptr: Vec>) -> Result<()> { + assert_eq!(ptr.len(), 2); + assert_eq!(ptr[0][0], "foo"); + assert_eq!(ptr[0][1], "bar"); + assert_eq!(ptr[1][0], "baz"); + Ok(()) + } + + fn list_result(&mut self) -> Result> { + Ok(vec![1, 2, 3, 4, 5]) + } + + fn list_result2(&mut self) -> Result { + Ok("hello!".to_string()) + } + + fn list_result3(&mut self) -> Result> { + Ok(vec!["hello,".to_string(), "world!".to_string()]) + } + + fn list_roundtrip(&mut self, list: Vec) -> Result> { + Ok(list.to_vec()) + } + + fn string_roundtrip(&mut self, s: String) -> Result { + Ok(s.to_string()) + } + + fn list_minmax8(&mut self, u: Vec, s: Vec) -> Result<(Vec, Vec)> { + assert_eq!(u, [u8::MIN, u8::MAX]); + assert_eq!(s, [i8::MIN, i8::MAX]); + Ok((u, s)) + } + + fn list_minmax16(&mut self, u: Vec, s: Vec) -> Result<(Vec, Vec)> { + assert_eq!(u, [u16::MIN, u16::MAX]); + assert_eq!(s, [i16::MIN, i16::MAX]); + Ok((u, s)) + } + + fn list_minmax32(&mut self, u: Vec, s: Vec) -> Result<(Vec, Vec)> { + assert_eq!(u, [u32::MIN, u32::MAX]); + assert_eq!(s, [i32::MIN, i32::MAX]); + Ok((u, s)) + } + + fn list_minmax64(&mut self, u: Vec, s: Vec) -> Result<(Vec, Vec)> { + assert_eq!(u, [u64::MIN, u64::MAX]); + assert_eq!(s, [i64::MIN, i64::MAX]); + Ok((u, s)) + } + + fn list_minmax_float(&mut self, u: Vec, s: Vec) -> Result<(Vec, Vec)> { + assert_eq!(u, [f32::MIN, f32::MAX, f32::NEG_INFINITY, f32::INFINITY]); + assert_eq!(s, [f64::MIN, f64::MAX, f64::NEG_INFINITY, f64::INFINITY]); + Ok((u, s)) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "lists", + |linker| Lists::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| Lists::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(lists: Lists, store: &mut Store>) -> Result<()> { + let bytes = lists.allocated_bytes(&mut *store)?; + lists.test_imports(&mut *store)?; + let exports = lists.exports(); + exports.empty_list_param(&mut *store, &[])?; + exports.empty_string_param(&mut *store, "")?; + assert_eq!(exports.empty_list_result(&mut *store)?, []); + assert_eq!(exports.empty_string_result(&mut *store)?, ""); + exports.list_param(&mut *store, &[1, 2, 3, 4])?; + exports.list_param2(&mut *store, "foo")?; + exports.list_param3(&mut *store, &["foo", "bar", "baz"])?; + exports.list_param4(&mut *store, &[&["foo", "bar"], &["baz"]])?; + assert_eq!(exports.list_result(&mut *store)?, [1, 2, 3, 4, 5]); + assert_eq!(exports.list_result2(&mut *store)?, "hello!"); + assert_eq!(exports.list_result3(&mut *store)?, ["hello,", "world!"]); + assert_eq!(exports.string_roundtrip(&mut *store, "x")?, "x"); + assert_eq!(exports.string_roundtrip(&mut *store, "")?, ""); + assert_eq!( + exports.string_roundtrip(&mut *store, "hello ⚑ world")?, + "hello ⚑ world" + ); + // Ensure that we properly called `free` everywhere in all the glue that we + // needed to. + assert_eq!(bytes, lists.allocated_bytes(&mut *store)?); + Ok(()) +} diff --git a/tests/runtime/lists/host.ts b/tests/runtime/lists/host.ts deleted file mode 100644 index c1f72b0be..000000000 --- a/tests/runtime/lists/host.ts +++ /dev/null @@ -1,134 +0,0 @@ -// Flags: --instantiation - -import * as helpers from "./helpers.js"; -import { instantiate } from "./lists.js"; - -// @ts-ignore -import * as assert from 'assert'; - -async function run() { - const wasm = await instantiate(helpers.loadWasm, { - testwasi: helpers, - imports: { - emptyListParam(a) { - assert.deepStrictEqual(Array.from(a), []); - }, - emptyStringParam(a) { - assert.strictEqual(a, ''); - }, - emptyListResult() { - return new Uint8Array([]); - }, - emptyStringResult() { return ''; }, - listParam(a) { - assert.deepStrictEqual(Array.from(a), [1, 2, 3, 4]); - }, - listParam2(a) { - assert.strictEqual(a, 'foo'); - }, - listParam3(a) { - assert.deepStrictEqual(a, ['foo', 'bar', 'baz']); - }, - listParam4(a) { - assert.deepStrictEqual(a, [['foo', 'bar'], ['baz']]); - }, - listResult() { - return new Uint8Array([1, 2, 3, 4, 5]); - }, - listResult2() { return 'hello!'; }, - listResult3() { return ['hello,', 'world!']; }, - listRoundtrip(x) { return x; }, - stringRoundtrip(x) { return x; }, - - listMinmax8(u, s) { - assert.deepEqual(u.length, 2); - assert.deepEqual(u[0], 0); - assert.deepEqual(u[1], (1 << 8) - 1); - assert.deepEqual(s.length, 2); - assert.deepEqual(s[0], -(1 << 7)); - assert.deepEqual(s[1], (1 << 7) - 1); - - return [u, s]; - }, - - listMinmax16(u, s) { - assert.deepEqual(u.length, 2); - assert.deepEqual(u[0], 0); - assert.deepEqual(u[1], (1 << 16) - 1); - assert.deepEqual(s.length, 2); - assert.deepEqual(s[0], -(1 << 15)); - assert.deepEqual(s[1], (1 << 15) - 1); - - return [u, s]; - }, - - listMinmax32(u, s) { - assert.deepEqual(u.length, 2); - assert.deepEqual(u[0], 0); - assert.deepEqual(u[1], ~0 >>> 0); - assert.deepEqual(s.length, 2); - assert.deepEqual(s[0], 1 << 31); - assert.deepEqual(s[1], ((1 << 31) - 1) >>> 0); - - return [u, s]; - }, - - listMinmax64(u, s) { - assert.deepEqual(u.length, 2); - assert.deepEqual(u[0], 0n); - assert.deepEqual(u[1], (2n ** 64n) - 1n); - assert.deepEqual(s.length, 2); - assert.deepEqual(s[0], -(2n ** 63n)); - assert.deepEqual(s[1], (2n ** 63n) - 1n); - - return [u, s]; - }, - - listMinmaxFloat(f, d) { - assert.deepEqual(f.length, 4); - assert.deepEqual(f[0], -3.4028234663852886e+38); - assert.deepEqual(f[1], 3.4028234663852886e+38); - assert.deepEqual(f[2], Number.NEGATIVE_INFINITY); - assert.deepEqual(f[3], Number.POSITIVE_INFINITY); - - assert.deepEqual(d.length, 4); - assert.deepEqual(d[0], -Number.MAX_VALUE); - assert.deepEqual(d[1], Number.MAX_VALUE); - assert.deepEqual(d[2], Number.NEGATIVE_INFINITY); - assert.deepEqual(d[3], Number.POSITIVE_INFINITY); - - return [f, d]; - }, - }, - }); - - const bytes = wasm.allocatedBytes(); - wasm.testImports(); - wasm.exports.emptyListParam(new Uint8Array([])); - wasm.exports.emptyStringParam(''); - wasm.exports.listParam(new Uint8Array([1, 2, 3, 4]).buffer); - wasm.exports.listParam2("foo"); - wasm.exports.listParam3(["foo", "bar", "baz"]); - wasm.exports.listParam4([["foo", "bar"], ["baz"]]); - assert.deepStrictEqual(Array.from(wasm.exports.emptyListResult()), []); - assert.deepStrictEqual(wasm.exports.emptyStringResult(), ""); - assert.deepStrictEqual(Array.from(wasm.exports.listResult()), [1, 2, 3, 4, 5]); - assert.deepStrictEqual(wasm.exports.listResult2(), "hello!"); - assert.deepStrictEqual(wasm.exports.listResult3(), ["hello,", "world!"]); - - const buffer = new ArrayBuffer(8); - (new Uint8Array(buffer)).set(new Uint8Array([1, 2, 3, 4]), 2); - // Create a view of the four bytes in the middle of the buffer - const view = new Uint8Array(buffer, 2, 4); - assert.deepStrictEqual(Array.from(wasm.exports.listRoundtrip(view)), [1, 2, 3, 4]); - - assert.deepStrictEqual(wasm.exports.stringRoundtrip("x"), "x"); - assert.deepStrictEqual(wasm.exports.stringRoundtrip(""), ""); - assert.deepStrictEqual(wasm.exports.stringRoundtrip("hello ⚑ world"), "hello ⚑ world"); - - // Ensure that we properly called `free` everywhere in all the glue that we - // needed to. - assert.strictEqual(bytes, wasm.allocatedBytes()); -} - -await run() diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index d03b2b061..1305f5ab8 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -9,6 +9,7 @@ use wit_component::ComponentEncoder; use wit_parser::Resolve; mod flavorful; +mod lists; mod numbers; wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit"); From d4342390f973fec91e50ab6b413e89bdb71de0fc Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:42:51 -0800 Subject: [PATCH 09/17] Migrate the many arguments test --- tests/runtime/main.rs | 1 + tests/runtime/many_arguments.rs | 81 ++++++++++++++++++++++++++++ tests/runtime/many_arguments/host.ts | 78 --------------------------- 3 files changed, 82 insertions(+), 78 deletions(-) create mode 100644 tests/runtime/many_arguments.rs delete mode 100644 tests/runtime/many_arguments/host.ts diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 1305f5ab8..2a7ba1932 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -10,6 +10,7 @@ use wit_parser::Resolve; mod flavorful; mod lists; +mod many_arguments; mod numbers; wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit"); diff --git a/tests/runtime/many_arguments.rs b/tests/runtime/many_arguments.rs new file mode 100644 index 000000000..e6c84cfd2 --- /dev/null +++ b/tests/runtime/many_arguments.rs @@ -0,0 +1,81 @@ +use anyhow::Result; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/many_arguments"); + +#[derive(Default)] +pub struct MyImports {} + +impl imports::Imports for MyImports { + fn many_arguments( + &mut self, + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + a8: u64, + a9: u64, + a10: u64, + a11: u64, + a12: u64, + a13: u64, + a14: u64, + a15: u64, + a16: u64, + ) -> Result<()> { + assert_eq!(a1, 1); + assert_eq!(a2, 2); + assert_eq!(a3, 3); + assert_eq!(a4, 4); + assert_eq!(a5, 5); + assert_eq!(a6, 6); + assert_eq!(a7, 7); + assert_eq!(a8, 8); + assert_eq!(a9, 9); + assert_eq!(a10, 10); + assert_eq!(a11, 11); + assert_eq!(a12, 12); + assert_eq!(a13, 13); + assert_eq!(a14, 14); + assert_eq!(a15, 15); + assert_eq!(a16, 16); + Ok(()) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "many_arguments", + |linker| ManyArguments::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| ManyArguments::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(exports: ManyArguments, store: &mut Store>) -> Result<()> { + exports.many_arguments( + &mut *store, + 1, + 2, + 3, + 4, + 5, + 6, + 7, + 8, + 9, + 10, + 11, + 12, + 13, + 14, + 15, + 16, + )?; + + Ok(()) +} diff --git a/tests/runtime/many_arguments/host.ts b/tests/runtime/many_arguments/host.ts deleted file mode 100644 index cebb0bed1..000000000 --- a/tests/runtime/many_arguments/host.ts +++ /dev/null @@ -1,78 +0,0 @@ -// Flags: --instantiation - -import { instantiate } from "./many_arguments.js"; -import * as helpers from "./helpers.js"; - -function assertEq(x: any, y: any) { - if (x !== y) - throw new Error(`${x} != ${y}`); -} - -function assert(x: boolean) { - if (!x) - throw new Error("assert failed"); -} - -async function run() { - const wasm = await instantiate(helpers.loadWasm, { - testwasi: helpers, - imports: { - manyArguments( - a1, - a2, - a3, - a4, - a5, - a6, - a7, - a8, - a9, - a10, - a11, - a12, - a13, - a14, - a15, - a16, - ) { - assertEq(a1, 1n); - assertEq(a2, 2n); - assertEq(a3, 3n); - assertEq(a4, 4n); - assertEq(a5, 5n); - assertEq(a6, 6n); - assertEq(a7, 7n); - assertEq(a8, 8n); - assertEq(a9, 9n); - assertEq(a10, 10n); - assertEq(a11, 11n); - assertEq(a12, 12n); - assertEq(a13, 13n); - assertEq(a14, 14n); - assertEq(a15, 15n); - assertEq(a16, 16n); - }, - }, - }); - - wasm.manyArguments( - 1n, - 2n, - 3n, - 4n, - 5n, - 6n, - 7n, - 8n, - 9n, - 10n, - 11n, - 12n, - 13n, - 14n, - 15n, - 16n, - ); -} - -await run() From f9f21b5d02d5a83af99e1f4bd5aa3e7c05ab1d26 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:44:30 -0800 Subject: [PATCH 10/17] Migrate the records test --- tests/runtime/main.rs | 1 + tests/runtime/records.rs | 119 ++++++++++++++++++++++++++++++++++ tests/runtime/records/host.ts | 51 --------------- 3 files changed, 120 insertions(+), 51 deletions(-) create mode 100644 tests/runtime/records.rs delete mode 100644 tests/runtime/records/host.ts diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 2a7ba1932..977fda2c8 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -12,6 +12,7 @@ mod flavorful; mod lists; mod many_arguments; mod numbers; +mod records; wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit"); diff --git a/tests/runtime/records.rs b/tests/runtime/records.rs new file mode 100644 index 000000000..ed045e153 --- /dev/null +++ b/tests/runtime/records.rs @@ -0,0 +1,119 @@ +use anyhow::Result; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/records"); + +#[derive(Default)] +pub struct MyImports; + +impl imports::Imports for MyImports { + fn multiple_results(&mut self) -> Result<(u8, u16)> { + Ok((4, 5)) + } + + fn swap_tuple(&mut self, a: (u8, u32)) -> Result<(u32, u8)> { + Ok((a.1, a.0)) + } + + fn roundtrip_flags1(&mut self, a: imports::F1) -> Result { + drop(format!("{:?}", a)); + drop(a & imports::F1::all()); + Ok(a) + } + + fn roundtrip_flags2(&mut self, a: imports::F2) -> Result { + Ok(a) + } + + fn roundtrip_flags3( + &mut self, + a: imports::Flag8, + b: imports::Flag16, + c: imports::Flag32, + d: imports::Flag64, + ) -> Result<( + imports::Flag8, + imports::Flag16, + imports::Flag32, + imports::Flag64, + )> { + Ok((a, b, c, d)) + } + + fn roundtrip_record1(&mut self, a: imports::R1) -> Result { + drop(format!("{:?}", a)); + Ok(a) + } + + fn tuple0(&mut self, _: ()) -> Result<()> { + Ok(()) + } + + fn tuple1(&mut self, a: (u8,)) -> Result<(u8,)> { + Ok((a.0,)) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "records", + |linker| Records::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| Records::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(exports: Records, store: &mut Store>) -> Result<()> { + use exports::*; + + exports.test_imports(&mut *store)?; + let exports = exports.exports(); + assert_eq!(exports.multiple_results(&mut *store,)?, (100, 200)); + assert_eq!(exports.swap_tuple(&mut *store, (1u8, 2u32))?, (2u32, 1u8)); + assert_eq!(exports.roundtrip_flags1(&mut *store, F1::A)?, F1::A); + assert_eq!( + exports.roundtrip_flags1(&mut *store, F1::empty())?, + F1::empty() + ); + assert_eq!(exports.roundtrip_flags1(&mut *store, F1::B)?, F1::B); + assert_eq!( + exports.roundtrip_flags1(&mut *store, F1::A | F1::B)?, + F1::A | F1::B + ); + + assert_eq!(exports.roundtrip_flags2(&mut *store, F2::C)?, F2::C); + assert_eq!( + exports.roundtrip_flags2(&mut *store, F2::empty())?, + F2::empty() + ); + assert_eq!(exports.roundtrip_flags2(&mut *store, F2::D)?, F2::D); + assert_eq!( + exports.roundtrip_flags2(&mut *store, F2::C | F2::E)?, + F2::C | F2::E + ); + + let r = exports.roundtrip_record1( + &mut *store, + R1 { + a: 8, + b: F1::empty(), + }, + )?; + assert_eq!(r.a, 8); + assert_eq!(r.b, F1::empty()); + + let r = exports.roundtrip_record1( + &mut *store, + R1 { + a: 0, + b: F1::A | F1::B, + }, + )?; + assert_eq!(r.a, 0); + assert_eq!(r.b, F1::A | F1::B); + + assert_eq!(exports.tuple0(&mut *store, ())?, ()); + assert_eq!(exports.tuple1(&mut *store, (1,))?, (1,)); + Ok(()) +} diff --git a/tests/runtime/records/host.ts b/tests/runtime/records/host.ts deleted file mode 100644 index e302254e0..000000000 --- a/tests/runtime/records/host.ts +++ /dev/null @@ -1,51 +0,0 @@ -// Flags: --instantiation - -import * as helpers from "./helpers.js"; -import { instantiate, ImportObject } from "./records.js"; -// @ts-ignore -import * as assert from 'node:assert'; - -async function run() { - const wasm = await instantiate(helpers.loadWasm, { - testwasi: helpers, - imports: { - multipleResults() { return [4, 5]; }, - swapTuple([a, b]) { return [b, a]; }, - roundtripFlags1(x) { return x; }, - roundtripFlags2(x) { return x; }, - roundtripFlags3(r0, r1, r2, r3) { return [r0, r1, r2, r3]; }, - roundtripRecord1(x) { return x; }, - tuple0([]) { return []; }, - tuple1([x]) { return [x]; }, - }, - }); - - wasm.testImports(); - assert.deepEqual(wasm.exports.multipleResults(), [100, 200]); - assert.deepStrictEqual(wasm.exports.swapTuple([1, 2]), [2, 1]); - assert.deepEqual(wasm.exports.roundtripFlags1({ a: true }), { a: true, b: false }); - assert.deepEqual(wasm.exports.roundtripFlags1({}), { a: false, b: false }); - assert.deepEqual(wasm.exports.roundtripFlags1({ a: true, b: true }), { a: true, b: true }); - - assert.deepEqual(wasm.exports.roundtripFlags2({ c: true }), { c: true, d: false, e: false }); - assert.deepEqual(wasm.exports.roundtripFlags2({}), { c: false, d: false, e: false }); - assert.deepEqual(wasm.exports.roundtripFlags2({ d: true }), { c: false, d: true, e: false }); - assert.deepEqual(wasm.exports.roundtripFlags2({ c: true, e: true }), { c: true, d: false, e: true }); - - { - const { a, b } = wasm.exports.roundtripRecord1({ a: 8, b: {} }); - assert.deepEqual(a, 8); - assert.deepEqual(b, { a: false, b: false }); - } - - { - const { a, b } = wasm.exports.roundtripRecord1({ a: 0, b: { a: true, b: true } }); - assert.deepEqual(a, 0); - assert.deepEqual(b, { a: true, b: true }); - } - - assert.deepStrictEqual(wasm.exports.tuple0([]), []); - assert.deepStrictEqual(wasm.exports.tuple1([1]), [1]); -} - -await run() From d12e3ea0f285997fafa665802929dd791196c17d Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:45:56 -0800 Subject: [PATCH 11/17] Migrate the smoke test --- tests/runtime/main.rs | 1 + tests/runtime/smoke.rs | 35 +++++++++++++++++++++++++++++++++++ tests/runtime/smoke/host.ts | 23 ----------------------- 3 files changed, 36 insertions(+), 23 deletions(-) create mode 100644 tests/runtime/smoke.rs delete mode 100644 tests/runtime/smoke/host.ts diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 977fda2c8..54284beee 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -13,6 +13,7 @@ mod lists; mod many_arguments; mod numbers; mod records; +mod smoke; wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit"); diff --git a/tests/runtime/smoke.rs b/tests/runtime/smoke.rs new file mode 100644 index 000000000..66c220efd --- /dev/null +++ b/tests/runtime/smoke.rs @@ -0,0 +1,35 @@ +use anyhow::Result; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/smoke"); + +#[derive(Default)] +pub struct MyImports { + hit: bool, +} + +impl imports::Imports for MyImports { + fn thunk(&mut self) -> Result<()> { + self.hit = true; + println!("in the host"); + Ok(()) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "smoke", + |linker| Smoke::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| Smoke::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(exports: Smoke, store: &mut Store>) -> Result<()> { + exports.thunk(&mut *store)?; + + assert!(store.data().0.hit); + + Ok(()) +} diff --git a/tests/runtime/smoke/host.ts b/tests/runtime/smoke/host.ts deleted file mode 100644 index 762f40bd2..000000000 --- a/tests/runtime/smoke/host.ts +++ /dev/null @@ -1,23 +0,0 @@ -// Flags: --compat --map testwasi=./helpers.js,imports=./host.js --base64-cutoff=2500 -function assert(x: boolean, msg: string) { - if (!x) - throw new Error(msg); -} - -let hit = false; - -export function thunk () { - hit = true; -} - -async function run() { - const wasm = await import('./smoke.js'); - - await wasm.$init; - - wasm.thunk(); - assert(hit, "import not called"); -} - -// Async cycle handling -setTimeout(run); From 6dcd1cadc91b50c14d33e230b69bf05cbfa25ce3 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:47:30 -0800 Subject: [PATCH 12/17] Bring back the unions host test --- tests/runtime/main.rs | 1 + tests/runtime/unions.rs | 323 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 324 insertions(+) create mode 100644 tests/runtime/unions.rs diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 54284beee..d8956b63b 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -14,6 +14,7 @@ mod many_arguments; mod numbers; mod records; mod smoke; +mod unions; wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit"); diff --git a/tests/runtime/unions.rs b/tests/runtime/unions.rs new file mode 100644 index 000000000..b8f7d2e80 --- /dev/null +++ b/tests/runtime/unions.rs @@ -0,0 +1,323 @@ +use anyhow::Result; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/unions"); + +#[derive(Default)] +pub struct MyImports; + +impl imports::Imports for MyImports { + fn add_one_integer(&mut self, num: imports::AllIntegers) -> Result { + use imports::AllIntegers; + Ok(match num { + AllIntegers::Bool(false) => AllIntegers::Bool(true), + AllIntegers::Bool(true) => AllIntegers::Bool(false), + AllIntegers::U8(n) => AllIntegers::U8(n.wrapping_add(1)), + AllIntegers::U16(n) => AllIntegers::U16(n.wrapping_add(1)), + AllIntegers::U32(n) => AllIntegers::U32(n.wrapping_add(1)), + AllIntegers::U64(n) => AllIntegers::U64(n.wrapping_add(1)), + AllIntegers::I8(n) => AllIntegers::I8(n.wrapping_add(1)), + AllIntegers::I16(n) => AllIntegers::I16(n.wrapping_add(1)), + AllIntegers::I32(n) => AllIntegers::I32(n.wrapping_add(1)), + AllIntegers::I64(n) => AllIntegers::I64(n.wrapping_add(1)), + }) + } + fn add_one_float(&mut self, num: imports::AllFloats) -> Result { + use imports::AllFloats; + Ok(match num { + AllFloats::F32(n) => AllFloats::F32(n + 1.0), + AllFloats::F64(n) => AllFloats::F64(n + 1.0), + }) + } + fn replace_first_char(&mut self, text: imports::AllText, c: char) -> Result { + use imports::AllText; + Ok(match text { + AllText::Char(_) => AllText::Char(c), + AllText::String(t) => AllText::String(format!("{}{}", c, &t[1..])), + }) + } + fn identify_integer(&mut self, num: imports::AllIntegers) -> Result { + use imports::AllIntegers; + Ok(match num { + AllIntegers::Bool { .. } => 0, + AllIntegers::U8 { .. } => 1, + AllIntegers::U16 { .. } => 2, + AllIntegers::U32 { .. } => 3, + AllIntegers::U64 { .. } => 4, + AllIntegers::I8 { .. } => 5, + AllIntegers::I16 { .. } => 6, + AllIntegers::I32 { .. } => 7, + AllIntegers::I64 { .. } => 8, + }) + } + fn identify_float(&mut self, num: imports::AllFloats) -> Result { + use imports::AllFloats; + Ok(match num { + AllFloats::F32 { .. } => 0, + AllFloats::F64 { .. } => 1, + }) + } + fn identify_text(&mut self, text: imports::AllText) -> Result { + use imports::AllText; + Ok(match text { + AllText::Char { .. } => 0, + AllText::String { .. } => 1, + }) + } + fn identify_duplicated(&mut self, dup: imports::DuplicatedS32) -> Result { + use imports::DuplicatedS32; + Ok(match dup { + DuplicatedS32::I320 { .. } => 0, + DuplicatedS32::I321 { .. } => 1, + DuplicatedS32::I322 { .. } => 2, + }) + } + fn add_one_duplicated( + &mut self, + dup: imports::DuplicatedS32, + ) -> Result { + use imports::DuplicatedS32; + Ok(match dup { + DuplicatedS32::I320(n) => DuplicatedS32::I320(n.wrapping_add(1)), + DuplicatedS32::I321(n) => DuplicatedS32::I321(n.wrapping_add(1)), + DuplicatedS32::I322(n) => DuplicatedS32::I322(n.wrapping_add(1)), + }) + } + fn identify_distinguishable_num(&mut self, num: imports::DistinguishableNum) -> Result { + use imports::DistinguishableNum; + Ok(match num { + DistinguishableNum::F64 { .. } => 0, + DistinguishableNum::I64 { .. } => 1, + }) + } + fn add_one_distinguishable_num( + &mut self, + num: imports::DistinguishableNum, + ) -> Result { + use imports::DistinguishableNum; + Ok(match num { + DistinguishableNum::F64(n) => DistinguishableNum::F64(n + 1.0), + DistinguishableNum::I64(n) => DistinguishableNum::I64(n.wrapping_add(1)), + }) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "unions", + |linker| Unions::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| Unions::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(exports: Unions, store: &mut Store>) -> Result<()> { + use exports::*; + + exports.test_imports(&mut *store)?; + let exports = exports.exports(); + + // Booleans + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::Bool(false))?, + AllIntegers::Bool(true) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::Bool(true))?, + AllIntegers::Bool(false) + )); + // Unsigned integers + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::U8(0))?, + AllIntegers::U8(1) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::U8(u8::MAX))?, + AllIntegers::U8(0) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::U16(0))?, + AllIntegers::U16(1) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::U16(u16::MAX))?, + AllIntegers::U16(0) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::U32(0))?, + AllIntegers::U32(1) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::U32(u32::MAX))?, + AllIntegers::U32(0) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::U64(0))?, + AllIntegers::U64(1) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::U64(u64::MAX))?, + AllIntegers::U64(0) + )); + // Signed integers + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::I8(0))?, + AllIntegers::I8(1) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::I8(i8::MAX))?, + AllIntegers::I8(i8::MIN) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::I16(0))?, + AllIntegers::I16(1) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::I16(i16::MAX))?, + AllIntegers::I16(i16::MIN) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::I32(0))?, + AllIntegers::I32(1) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::I32(i32::MAX))?, + AllIntegers::I32(i32::MIN) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::I64(0))?, + AllIntegers::I64(1) + )); + assert!(matches!( + exports.add_one_integer(&mut *store, AllIntegers::I64(i64::MAX))?, + AllIntegers::I64(i64::MIN) + )); + + // Floats + match exports.add_one_float(&mut *store, AllFloats::F32(0.0))? { + AllFloats::F32(r) => assert_eq!(r, 1.0), + _ => panic!(), + } + match exports.add_one_float(&mut *store, AllFloats::F32(420.0))? { + AllFloats::F32(r) => assert_eq!(r, 421.0), + _ => panic!(), + } + match exports.add_one_float(&mut *store, AllFloats::F64(0.0))? { + AllFloats::F64(r) => assert_eq!(r, 1.0), + _ => panic!(), + } + match exports.add_one_float(&mut *store, AllFloats::F64(420.0))? { + AllFloats::F64(r) => assert_eq!(r, 421.0), + _ => panic!(), + } + + // Text + assert!(matches!( + exports.replace_first_char(&mut *store, AllTextParam::Char('a'), 'z')?, + AllTextResult::Char('z') + )); + match exports.replace_first_char(&mut *store, AllTextParam::String("abc"), 'z')? { + AllTextResult::String(s) => assert_eq!(s, "zbc"), + _ => panic!(), + } + + // Identify Integers + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::Bool(false))?, + 0 + ); + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::U8(0))?, + 1 + ); + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::U16(0))?, + 2 + ); + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::U32(0))?, + 3 + ); + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::U64(0))?, + 4 + ); + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::I8(0))?, + 5 + ); + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::I16(0))?, + 6 + ); + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::I32(0))?, + 7 + ); + assert_eq!( + exports.identify_integer(&mut *store, AllIntegers::I64(0))?, + 8 + ); + + // Identify floats + assert_eq!(exports.identify_float(&mut *store, AllFloats::F32(0.0))?, 0); + assert_eq!(exports.identify_float(&mut *store, AllFloats::F64(0.0))?, 1); + + // Identify text + assert_eq!( + exports.identify_text(&mut *store, AllTextParam::Char('\0'))?, + 0 + ); + assert_eq!( + exports.identify_text(&mut *store, AllTextParam::String(""))?, + 1 + ); + + // Identify Duplicated + assert_eq!( + exports.identify_duplicated(&mut *store, DuplicatedS32::I320(0))?, + 0 + ); + assert_eq!( + exports.identify_duplicated(&mut *store, DuplicatedS32::I321(0))?, + 1 + ); + assert_eq!( + exports.identify_duplicated(&mut *store, DuplicatedS32::I322(0))?, + 2 + ); + + assert!(matches!( + exports.add_one_duplicated(&mut *store, DuplicatedS32::I320(0))?, + DuplicatedS32::I320(1) + )); + assert!(matches!( + exports.add_one_duplicated(&mut *store, DuplicatedS32::I321(0))?, + DuplicatedS32::I321(1) + )); + assert!(matches!( + exports.add_one_duplicated(&mut *store, DuplicatedS32::I322(0))?, + DuplicatedS32::I322(1) + )); + + // Identify Distinguishable Num + assert_eq!( + exports.identify_distinguishable_num(&mut *store, DistinguishableNum::F64(0.0))?, + 0 + ); + assert_eq!( + exports.identify_distinguishable_num(&mut *store, DistinguishableNum::I64(0))?, + 1 + ); + + match exports.add_one_distinguishable_num(&mut *store, DistinguishableNum::F64(0.0))? { + DistinguishableNum::F64(f) => assert_eq!(f, 1.0), + _ => panic!(), + }; + assert!(matches!( + exports.add_one_distinguishable_num(&mut *store, DistinguishableNum::I64(0))?, + DistinguishableNum::I64(1), + )); + Ok(()) +} From ce6e537b76b477dd7363dee6a703ded1099cabdf Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:49:17 -0800 Subject: [PATCH 13/17] Migrate the variants test --- tests/runtime/main.rs | 1 + tests/runtime/variants.rs | 128 +++++++++++++++++++++++++++++++++ tests/runtime/variants/host.ts | 114 ----------------------------- 3 files changed, 129 insertions(+), 114 deletions(-) create mode 100644 tests/runtime/variants.rs delete mode 100644 tests/runtime/variants/host.ts diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index d8956b63b..4918db96e 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -15,6 +15,7 @@ mod numbers; mod records; mod smoke; mod unions; +mod variants; wasmtime::component::bindgen!("testwasi" in "crates/wasi_snapshot_preview1/wit"); diff --git a/tests/runtime/variants.rs b/tests/runtime/variants.rs new file mode 100644 index 000000000..66ca61fe6 --- /dev/null +++ b/tests/runtime/variants.rs @@ -0,0 +1,128 @@ +use anyhow::Result; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/variants"); + +#[derive(Default)] +pub struct MyImports; + +impl imports::Imports for MyImports { + fn roundtrip_option(&mut self, a: Option) -> anyhow::Result> { + Ok(a.map(|x| x as u8)) + } + + fn roundtrip_result(&mut self, a: Result) -> anyhow::Result> { + Ok(match a { + Ok(a) => Ok(a.into()), + Err(b) => Err(b as u8), + }) + } + + fn roundtrip_enum(&mut self, a: imports::E1) -> anyhow::Result { + assert_eq!(a, a); + Ok(a) + } + + fn invert_bool(&mut self, a: bool) -> anyhow::Result { + Ok(!a) + } + + fn variant_casts(&mut self, a: imports::Casts) -> anyhow::Result { + Ok(a) + } + + fn variant_zeros(&mut self, a: imports::Zeros) -> anyhow::Result { + Ok(a) + } + + fn variant_typedefs( + &mut self, + _: Option, + _: bool, + _: Result, + ) -> anyhow::Result<()> { + Ok(()) + } + + fn variant_enums( + &mut self, + a: bool, + b: Result<(), ()>, + c: imports::MyErrno, + ) -> anyhow::Result<(bool, Result<(), ()>, imports::MyErrno)> { + assert_eq!(a, true); + assert_eq!(b, Ok(())); + assert_eq!(c, imports::MyErrno::Success); + Ok((false, Err(()), imports::MyErrno::A)) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "variants", + |linker| Variants::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| Variants::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(exports: Variants, store: &mut Store>) -> Result<()> { + use exports::*; + + exports.test_imports(&mut *store)?; + let exports = exports.exports(); + + assert_eq!(exports.roundtrip_option(&mut *store, Some(1.0))?, Some(1)); + assert_eq!(exports.roundtrip_option(&mut *store, None)?, None); + assert_eq!(exports.roundtrip_option(&mut *store, Some(2.0))?, Some(2)); + assert_eq!(exports.roundtrip_result(&mut *store, Ok(2))?, Ok(2.0)); + assert_eq!(exports.roundtrip_result(&mut *store, Ok(4))?, Ok(4.0)); + assert_eq!(exports.roundtrip_result(&mut *store, Err(5.3))?, Err(5)); + + assert_eq!(exports.roundtrip_enum(&mut *store, E1::A)?, E1::A); + assert_eq!(exports.roundtrip_enum(&mut *store, E1::B)?, E1::B); + + assert_eq!(exports.invert_bool(&mut *store, true)?, false); + assert_eq!(exports.invert_bool(&mut *store, false)?, true); + + let (a1, a2, a3, a4, a5, a6) = exports.variant_casts( + &mut *store, + (C1::A(1), C2::A(2), C3::A(3), C4::A(4), C5::A(5), C6::A(6.0)), + )?; + assert!(matches!(a1, C1::A(1))); + assert!(matches!(a2, C2::A(2))); + assert!(matches!(a3, C3::A(3))); + assert!(matches!(a4, C4::A(4))); + assert!(matches!(a5, C5::A(5))); + assert!(matches!(a6, C6::A(b) if b == 6.0)); + + let (a1, a2, a3, a4, a5, a6) = exports.variant_casts( + &mut *store, + ( + C1::B(1), + C2::B(2.0), + C3::B(3.0), + C4::B(4.0), + C5::B(5.0), + C6::B(6.0), + ), + )?; + assert!(matches!(a1, C1::B(1))); + assert!(matches!(a2, C2::B(b) if b == 2.0)); + assert!(matches!(a3, C3::B(b) if b == 3.0)); + assert!(matches!(a4, C4::B(b) if b == 4.0)); + assert!(matches!(a5, C5::B(b) if b == 5.0)); + assert!(matches!(a6, C6::B(b) if b == 6.0)); + + let (a1, a2, a3, a4) = + exports.variant_zeros(&mut *store, (Z1::A(1), Z2::A(2), Z3::A(3.0), Z4::A(4.0)))?; + assert!(matches!(a1, Z1::A(1))); + assert!(matches!(a2, Z2::A(2))); + assert!(matches!(a3, Z3::A(b) if b == 3.0)); + assert!(matches!(a4, Z4::A(b) if b == 4.0)); + + exports.variant_typedefs(&mut *store, None, false, Err(()))?; + + Ok(()) +} diff --git a/tests/runtime/variants/host.ts b/tests/runtime/variants/host.ts deleted file mode 100644 index 2f3f09210..000000000 --- a/tests/runtime/variants/host.ts +++ /dev/null @@ -1,114 +0,0 @@ -// Flags: --instantiation - -import * as helpers from "./helpers.js"; -import { instantiate } from "./variants.js"; -// @ts-ignore -import * as assert from 'assert'; - -async function run() { - const wasm = await instantiate(helpers.loadWasm, { - testwasi: helpers, - imports: { - roundtripOption(x) { return x; }, - roundtripResult(x) { - if (x.tag == 'ok') { - return x.val; - } else { - throw Object.assign(new Error(''), { payload: Math.round(x.val) }); - } - }, - roundtripEnum(x) { return x; }, - invertBool(x) { return !x; }, - variantCasts(x) { return x; }, - variantZeros(x) { return x; }, - variantTypedefs(x, y, z) {}, - variantEnums(a, b, c) { - assert.deepStrictEqual(a, true); - assert.deepStrictEqual(b, { tag: 'ok', val: undefined }); - assert.deepStrictEqual(c, "success"); - return [ - false, - { tag: 'err', val: undefined }, - "a", - ]; - }, - }, - }); - - wasm.testImports(); - assert.deepStrictEqual(wasm.exports.roundtripOption(1), 1); - assert.deepStrictEqual(wasm.exports.roundtripOption(null), null); - // @ts-ignore - assert.deepStrictEqual(wasm.exports.roundtripOption(undefined), null); - // @ts-ignore - assert.deepStrictEqual(wasm.exports.roundtripOption(), null); - assert.deepStrictEqual(wasm.exports.roundtripOption(2), 2); - assert.deepStrictEqual(wasm.exports.roundtripResult({ tag: 'ok', val: 2 }), 2); - assert.deepStrictEqual(wasm.exports.roundtripResult({ tag: 'ok', val: 4 }), 4); - const f = Math.fround(5.2); - - try { - wasm.exports.roundtripResult({ tag: 'err', val: f }); - assert.fail('Expected an error'); - } catch (e: any) { - assert.strictEqual(e.constructor.name, 'ComponentError'); - assert.ok(e.message.includes('5')); - assert.strictEqual(e.payload, 5); - } - - assert.deepStrictEqual(wasm.exports.roundtripEnum("a"), "a"); - assert.deepStrictEqual(wasm.exports.roundtripEnum("b"), "b"); - - assert.deepStrictEqual(wasm.exports.invertBool(true), false); - assert.deepStrictEqual(wasm.exports.invertBool(false), true); - - { - const [a1, a2, a3, a4, a5, a6] = wasm.exports.variantCasts([ - { tag: 'a', val: 1 }, - { tag: 'a', val: 2 }, - { tag: 'a', val: 3 }, - { tag: 'a', val: 4n }, - { tag: 'a', val: 5n }, - { tag: 'a', val: 6 }, - ]); - assert.deepStrictEqual(a1, { tag: 'a', val: 1 }); - assert.deepStrictEqual(a2, { tag: 'a', val: 2 }); - assert.deepStrictEqual(a3, { tag: 'a', val: 3 }); - assert.deepStrictEqual(a4, { tag: 'a', val: 4n }); - assert.deepStrictEqual(a5, { tag: 'a', val: 5n }); - assert.deepStrictEqual(a6, { tag: 'a', val: 6 }); - } - { - const [b1, b2, b3, b4, b5, b6] = wasm.exports.variantCasts([ - { tag: 'b', val: 1n }, - { tag: 'b', val: 2 }, - { tag: 'b', val: 3 }, - { tag: 'b', val: 4 }, - { tag: 'b', val: 5 }, - { tag: 'b', val: 6 }, - ]); - assert.deepStrictEqual(b1, { tag: 'b', val: 1n }); - assert.deepStrictEqual(b2, { tag: 'b', val: 2 }); - assert.deepStrictEqual(b3, { tag: 'b', val: 3 }); - assert.deepStrictEqual(b4, { tag: 'b', val: 4 }); - assert.deepStrictEqual(b5, { tag: 'b', val: 5 }); - assert.deepStrictEqual(b6, { tag: 'b', val: 6 }); - } - - { - const [a1, a2, a3, a4] = wasm.exports.variantZeros([ - { tag: 'a', val: 1 }, - { tag: 'a', val: 2n }, - { tag: 'a', val: 3 }, - { tag: 'a', val: 4 }, - ]); - assert.deepStrictEqual(a1, { tag: 'a', val: 1 }); - assert.deepStrictEqual(a2, { tag: 'a', val: 2n }); - assert.deepStrictEqual(a3, { tag: 'a', val: 3 }); - assert.deepStrictEqual(a4, { tag: 'a', val: 4 }); - } - - wasm.exports.variantTypedefs(null, false, { tag: 'err', val: undefined }); -} - -await run() From 1f6d38cb838a9673bf5f442d9132e2213f4faed0 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:52:18 -0800 Subject: [PATCH 14/17] Migrate strings test to Wasmtime --- tests/runtime/main.rs | 1 + tests/runtime/strings.rs | 35 +++++++++++++++++++++++++++++++++++ tests/runtime/strings/host.ts | 27 --------------------------- 3 files changed, 36 insertions(+), 27 deletions(-) create mode 100644 tests/runtime/strings.rs delete mode 100644 tests/runtime/strings/host.ts diff --git a/tests/runtime/main.rs b/tests/runtime/main.rs index 4918db96e..2a5c2d3b2 100644 --- a/tests/runtime/main.rs +++ b/tests/runtime/main.rs @@ -14,6 +14,7 @@ mod many_arguments; mod numbers; mod records; mod smoke; +mod strings; mod unions; mod variants; diff --git a/tests/runtime/strings.rs b/tests/runtime/strings.rs new file mode 100644 index 000000000..7a99ad89c --- /dev/null +++ b/tests/runtime/strings.rs @@ -0,0 +1,35 @@ +use anyhow::Result; +use wasmtime::Store; + +wasmtime::component::bindgen!("world" in "tests/runtime/strings"); + +#[derive(Default)] +pub struct MyImports; + +impl imports::Imports for MyImports { + fn take_basic(&mut self, s: String) -> Result<()> { + assert_eq!(s, "latin utf16"); + Ok(()) + } + + fn return_unicode(&mut self) -> Result { + Ok("🚀🚀🚀 𠈄𓀀".to_string()) + } +} + +#[test] +fn run() -> Result<()> { + crate::run_test( + "strings", + |linker| Strings::add_to_linker(linker, |x| &mut x.0), + |store, component, linker| Strings::instantiate(store, component, linker), + run_test, + ) +} + +fn run_test(exports: Strings, store: &mut Store>) -> Result<()> { + exports.test_imports(&mut *store)?; + assert_eq!(exports.roundtrip(&mut *store, "str")?, "str"); + assert_eq!(exports.roundtrip(&mut *store, "🚀🚀🚀 𠈄𓀀")?, "🚀🚀🚀 𠈄𓀀"); + Ok(()) +} diff --git a/tests/runtime/strings/host.ts b/tests/runtime/strings/host.ts deleted file mode 100644 index 47c29cd83..000000000 --- a/tests/runtime/strings/host.ts +++ /dev/null @@ -1,27 +0,0 @@ -// Flags: --instantiation - -import * as helpers from "./helpers.js"; -import { instantiate } from "./strings.js"; - -// @ts-ignore -import * as assert from 'assert'; - -async function run() { - const wasm = await instantiate(helpers.loadWasm, { - testwasi: helpers, - imports: { - takeBasic(s: string) { - assert.strictEqual(s, 'latin utf16'); - }, - returnUnicode() { - return '🚀🚀🚀 𠈄𓀀'; - } - } - }); - - wasm.testImports(); - assert.strictEqual(wasm.roundtrip('str'), 'str'); - assert.strictEqual(wasm.roundtrip('🚀🚀🚀 𠈄𓀀'), '🚀🚀🚀 𠈄𓀀'); -} - -await run() From ba0238a272bcd561682100f204a6a332617ef4cb Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:53:08 -0800 Subject: [PATCH 15/17] Remove invalid runtime test This is a test for host generators which is no longer being tested in this repository. --- crates/test-rust-wasm/Cargo.toml | 4 - crates/test-rust-wasm/src/bin/invalid.rs | 3 - tests/runtime/invalid/host.ts | 48 -------- tests/runtime/invalid/wasm.rs | 143 ----------------------- tests/runtime/invalid/world.wit | 66 ----------- 5 files changed, 264 deletions(-) delete mode 100644 crates/test-rust-wasm/src/bin/invalid.rs delete mode 100644 tests/runtime/invalid/host.ts delete mode 100644 tests/runtime/invalid/wasm.rs delete mode 100644 tests/runtime/invalid/world.wit diff --git a/crates/test-rust-wasm/Cargo.toml b/crates/test-rust-wasm/Cargo.toml index eda5649f7..d2cf90b14 100644 --- a/crates/test-rust-wasm/Cargo.toml +++ b/crates/test-rust-wasm/Cargo.toml @@ -43,10 +43,6 @@ test = false name = "flavorful" test = false -[[bin]] -name = "invalid" -test = false - [[bin]] name = "many_arguments" test = false diff --git a/crates/test-rust-wasm/src/bin/invalid.rs b/crates/test-rust-wasm/src/bin/invalid.rs deleted file mode 100644 index bcbe1a5c7..000000000 --- a/crates/test-rust-wasm/src/bin/invalid.rs +++ /dev/null @@ -1,3 +0,0 @@ -include!("../../../../tests/runtime/invalid/wasm.rs"); - -fn main() {} diff --git a/tests/runtime/invalid/host.ts b/tests/runtime/invalid/host.ts deleted file mode 100644 index 18f943672..000000000 --- a/tests/runtime/invalid/host.ts +++ /dev/null @@ -1,48 +0,0 @@ -// Flags: --instantiation - -import { instantiate } from "./invalid.js"; -import * as helpers from "./helpers.js"; -// @ts-ignore -import * as assert from 'assert'; - -async function run() { - const wasm = await instantiate(helpers.loadWasm, { - testwasi: helpers, - imports: { - roundtripU8(x) { throw new Error('unreachable'); }, - roundtripS8(x) { throw new Error('unreachable'); }, - roundtripU16(x) { throw new Error('unreachable'); }, - roundtripS16(x) { throw new Error('unreachable'); }, - roundtripBool(x) { throw new Error('unreachable'); }, - roundtripChar(x) { throw new Error('unreachable'); }, - roundtripEnum(x) { throw new Error('unreachable'); }, - unaligned1(x) { throw new Error('unreachable'); }, - unaligned2(x) { throw new Error('unreachable'); }, - unaligned3(x) { throw new Error('unreachable'); }, - unaligned4(x) { throw new Error('unreachable'); }, - unaligned5(x) { throw new Error('unreachable'); }, - unaligned6(x) { throw new Error('unreachable'); }, - unaligned7(x) { throw new Error('unreachable'); }, - unaligned8(x) { throw new Error('unreachable'); }, - unaligned9(x) { throw new Error('unreachable'); }, - unaligned10(x) { throw new Error('unreachable'); }, - }, - }); - - // FIXME(#376) these should succeed - assert.throws(() => wasm.invalidBool(), /invalid variant discriminant for bool/); - assert.throws(() => wasm.invalidU8(), /must be between/); - assert.throws(() => wasm.invalidS8(), /must be between/); - assert.throws(() => wasm.invalidU16(), /must be between/); - assert.throws(() => wasm.invalidS16(), /must be between/); - - // FIXME(#375) these should require a new instantiation - assert.throws(() => wasm.invalidChar(), /not a valid char/); - assert.throws(() => wasm.invalidEnum(), /invalid discriminant specified for E/); - - /* - assert.throws(() => wasm.testUnaligned(), /is not aligned/); - */ -} - -await run() diff --git a/tests/runtime/invalid/wasm.rs b/tests/runtime/invalid/wasm.rs deleted file mode 100644 index d919da908..000000000 --- a/tests/runtime/invalid/wasm.rs +++ /dev/null @@ -1,143 +0,0 @@ -wit_bindgen_guest_rust::generate!("world" in "../../tests/runtime/invalid"); - -#[link(wasm_import_module = "imports")] -extern "C" { - #[link_name = "roundtrip-bool"] - fn roundtrip_bool(a: i32) -> i32; - #[link_name = "roundtrip-u16"] - fn roundtrip_u16(a: i32) -> i32; - #[link_name = "roundtrip-u8"] - fn roundtrip_u8(a: i32) -> i32; - #[link_name = "roundtrip-s16"] - fn roundtrip_s16(a: i32) -> i32; - #[link_name = "roundtrip-s8"] - fn roundtrip_s8(a: i32) -> i32; - #[link_name = "roundtrip-char"] - fn roundtrip_char(a: i32) -> i32; - #[link_name = "roundtrip-enum"] - fn roundtrip_enum(a: i32) -> i32; - #[link_name = "unaligned1"] - fn unaligned1(ptr: i32, len: i32); - #[link_name = "unaligned2"] - fn unaligned2(ptr: i32, len: i32); - #[link_name = "unaligned3"] - fn unaligned3(ptr: i32, len: i32); - #[link_name = "unaligned4"] - fn unaligned4(ptr: i32, len: i32); - #[link_name = "unaligned5"] - fn unaligned5(ptr: i32, len: i32); - #[link_name = "unaligned6"] - fn unaligned6(ptr: i32, len: i32); - #[link_name = "unaligned7"] - fn unaligned7(ptr: i32, len: i32); - #[link_name = "unaligned8"] - fn unaligned8(ptr: i32, len: i32); - #[link_name = "unaligned9"] - fn unaligned9(ptr: i32, len: i32); - #[link_name = "unaligned10"] - fn unaligned10(ptr: i32, len: i32); -} - -struct Exports; - -export_invalid!(Exports); - -impl Invalid for Exports { - fn invalid_bool() { - unsafe { - let b = roundtrip_bool(2); - assert_eq!(b, 1); - } - } - fn invalid_u8() { - unsafe { - let u = roundtrip_u8(i32::MAX); - assert!(u <= (u8::MAX as i32)); - assert!(u >= (u8::MIN as i32)); - } - } - fn invalid_s8() { - unsafe { - let s = roundtrip_s8(i32::MAX); - assert!(s <= (i8::MAX as i32)); - assert!(s >= (i8::MIN as i32)); - } - } - fn invalid_u16() { - unsafe { - let u = roundtrip_u16(i32::MAX); - assert!(u <= (u16::MAX as i32)); - assert!(u >= (u16::MIN as i32)); - } - } - fn invalid_s16() { - unsafe { - let s = roundtrip_s16(i32::MAX); - assert!(s <= (i16::MAX as i32)); - assert!(s >= (i16::MIN as i32)); - } - } - fn invalid_char() { - unsafe { - roundtrip_char(0xd800); - } - unreachable!(); - } - fn invalid_enum() { - unsafe { - roundtrip_enum(400); - } - unreachable!(); - } - - fn unaligned1() { - unsafe { - unaligned1(1, 1); - } - } - fn unaligned2() { - unsafe { - unaligned2(1, 1); - } - } - fn unaligned3() { - unsafe { - unaligned3(1, 1); - } - } - fn unaligned4() { - unsafe { - unaligned4(1, 1); - } - } - fn unaligned5() { - unsafe { - unaligned5(1, 1); - } - } - fn unaligned6() { - unsafe { - unaligned6(1, 1); - } - } - fn unaligned7() { - unsafe { - unaligned7(1, 1); - } - } - fn unaligned8() { - unsafe { - unaligned8(1, 1); - } - } - fn unaligned9() { - unsafe { - unaligned9(1, 1); - } - } - fn unaligned10() { - unsafe { - unaligned10(1, 1); - } - } -} diff --git a/tests/runtime/invalid/world.wit b/tests/runtime/invalid/world.wit deleted file mode 100644 index 58d583b9d..000000000 --- a/tests/runtime/invalid/world.wit +++ /dev/null @@ -1,66 +0,0 @@ -interface imports { - roundtrip-u8: func(a: u8) -> u8 - roundtrip-s8: func(a: s8) -> s8 - roundtrip-u16: func(a: u16) -> u16 - roundtrip-s16: func(a: s16) -> s16 - roundtrip-char: func(a: char) -> char - - enum e { a, b, c } - roundtrip-enum: func(a: e) -> e - - roundtrip-bool: func(a: bool) -> bool - - - flags flag32 { - 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, - } - - 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, - } - - record unaligned-record { a: u32, b: u64 } - - unaligned1: func(a: list) - unaligned2: func(a: list) - unaligned3: func(a: list) - unaligned4: func(a: list) - unaligned5: func(a: list) - unaligned6: func(a: list) - unaligned7: func(a: list) - unaligned8: func(a: list) - unaligned9: func(a: list) - unaligned10: func(a: list>) -} - -default world invalid { - import imports: self.imports - - export invalid-u8: func() - export invalid-s8: func() - export invalid-u16: func() - export invalid-s16: func() - export invalid-char: func() - export invalid-bool: func() - export invalid-enum: func() - export unaligned1: func() - export unaligned2: func() - export unaligned3: func() - export unaligned4: func() - export unaligned5: func() - export unaligned6: func() - export unaligned7: func() - export unaligned8: func() - export unaligned9: func() - export unaligned10: func() -} From 9624b8ebd9b4ac5b786f8109bc6b918a18ff535b Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:53:49 -0800 Subject: [PATCH 16/17] Remove exports_only test This is a JS host test primarily --- tests/runtime/exports_only/exports_only.c | 56 ------------------ tests/runtime/exports_only/exports_only.h | 29 --------- .../exports_only_component_type.o | Bin 146 -> 0 bytes tests/runtime/exports_only/host.ts | 18 ------ tests/runtime/exports_only/wasm.c | 21 ------- tests/runtime/exports_only/world.wit | 3 - 6 files changed, 127 deletions(-) delete mode 100644 tests/runtime/exports_only/exports_only.c delete mode 100644 tests/runtime/exports_only/exports_only.h delete mode 100644 tests/runtime/exports_only/exports_only_component_type.o delete mode 100644 tests/runtime/exports_only/host.ts delete mode 100644 tests/runtime/exports_only/wasm.c delete mode 100644 tests/runtime/exports_only/world.wit diff --git a/tests/runtime/exports_only/exports_only.c b/tests/runtime/exports_only/exports_only.c deleted file mode 100644 index da69facb9..000000000 --- a/tests/runtime/exports_only/exports_only.c +++ /dev/null @@ -1,56 +0,0 @@ -#include "exports_only.h" - -__attribute__((weak, export_name("cabi_post_thunk"))) -void __wasm_export_exports_only_thunk_post_return(int32_t arg0) { - if ((*((int32_t*) (arg0 + 4))) > 0) { - free((void*) (*((int32_t*) (arg0 + 0)))); - } -} - -__attribute__((weak, export_name("cabi_realloc"))) -void *cabi_realloc(void *ptr, size_t orig_size, size_t org_align, size_t new_size) { - void *ret = realloc(ptr, new_size); - if (!ret) abort(); - return ret; -} - -// Helper Functions - -void exports_only_string_set(exports_only_string_t *ret, const char*s) { - ret->ptr = (char*) s; - ret->len = strlen(s); -} - -void exports_only_string_dup(exports_only_string_t *ret, const char*s) { - ret->len = strlen(s); - ret->ptr = cabi_realloc(NULL, 0, 1, ret->len * 1); - memcpy(ret->ptr, s, ret->len * 1); -} - -void exports_only_string_free(exports_only_string_t *ret) { - if (ret->len > 0) { - free(ret->ptr); - } - ret->ptr = NULL; - ret->len = 0; -} - -// Component Adapters - -__attribute__((aligned(4))) -static uint8_t RET_AREA[8]; - -__attribute__((export_name("thunk"))) -int32_t __wasm_export_exports_only_thunk(void) { - exports_only_string_t ret; - exports_only_thunk(&ret); - int32_t ptr = (int32_t) &RET_AREA; - *((int32_t*)(ptr + 4)) = (int32_t) (ret).len; - *((int32_t*)(ptr + 0)) = (int32_t) (ret).ptr; - return ptr; -} - -extern void __component_type_object_force_link_exports_only(void); -void __component_type_object_force_link_exports_only_public_use_in_this_compilation_unit(void) { - __component_type_object_force_link_exports_only(); -} diff --git a/tests/runtime/exports_only/exports_only.h b/tests/runtime/exports_only/exports_only.h deleted file mode 100644 index 51f162c14..000000000 --- a/tests/runtime/exports_only/exports_only.h +++ /dev/null @@ -1,29 +0,0 @@ -#ifndef __BINDINGS_EXPORTS_ONLY_H -#define __BINDINGS_EXPORTS_ONLY_H -#ifdef __cplusplus -extern "C" { -#endif - -#include -#include -#include -#include - -typedef struct { - char*ptr; - size_t len; -} exports_only_string_t; - -// Exported Functions from `exports-only` -void exports_only_thunk(exports_only_string_t *ret); - -// Helper Functions - -void exports_only_string_set(exports_only_string_t *ret, const char*s); -void exports_only_string_dup(exports_only_string_t *ret, const char*s); -void exports_only_string_free(exports_only_string_t *ret); - -#ifdef __cplusplus -} -#endif -#endif diff --git a/tests/runtime/exports_only/exports_only_component_type.o b/tests/runtime/exports_only/exports_only_component_type.o deleted file mode 100644 index 301ed7782c29cef2d33debd04bf79ca26cd86379..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 146 zcmX}kO%8%E5Cz~jRD$9WJOgp#g1CX3jM`L1V5Xrpk=xrQ#_jw0a1Ap7pbcFBQxIxH zDQFK?XRqQUHgWat>^@$X7#fvnO?|r6AXFvwK-ll7jL)}>2I#73ka3Vlu{~w^$o!`` UQ>?d_vytz*SjTzHKb$lE1*>r=od5s; diff --git a/tests/runtime/exports_only/host.ts b/tests/runtime/exports_only/host.ts deleted file mode 100644 index 73584eb4f..000000000 --- a/tests/runtime/exports_only/host.ts +++ /dev/null @@ -1,18 +0,0 @@ -// Flags: --valid-lifting-optimization --base64-cutoff=0 -// @ts-ignore -import { ok, strictEqual } from 'assert'; -// @ts-ignore -import { readFile } from 'fs/promises'; -// @ts-ignore -import { fileURLToPath } from 'url'; -import { thunk } from './exports_only.js'; - -const result = thunk(); -strictEqual(result, 'test'); - -// Verify the inlined file size does not regress -const url = new URL('./exports_only.js', import.meta.url); -const jsSource = await readFile(url); -const max_size = 1200; -ok(jsSource.byteLength <= max_size, `JS inlined bytelength ${jsSource.byteLength} is greater than ${max_size} bytes, at ${fileURLToPath(url)}`); - diff --git a/tests/runtime/exports_only/wasm.c b/tests/runtime/exports_only/wasm.c deleted file mode 100644 index b560d64da..000000000 --- a/tests/runtime/exports_only/wasm.c +++ /dev/null @@ -1,21 +0,0 @@ -#include -#include - -__attribute__((weak, export_name("cabi_realloc"))) -void *cabi_realloc(void *ptr, size_t orig_size, size_t org_align, size_t new_size) { - return ptr; -} - -__attribute__((weak, export_name("cabi_post_thunk"))) -void __wasm_export_exports_only_thunk_post_return(int32_t arg0) { -} - -static char msg[] = "test"; - -void exports_only_thunk(exports_only_string_t* ret) { - exports_only_string_t result = { - .ptr = &msg[0], - .len = sizeof(msg) - 1, - }; - *ret = result; -} diff --git a/tests/runtime/exports_only/world.wit b/tests/runtime/exports_only/world.wit deleted file mode 100644 index f290a1d70..000000000 --- a/tests/runtime/exports_only/world.wit +++ /dev/null @@ -1,3 +0,0 @@ -default world exports-only { - export thunk: func() -> string -} From 3b6ebd87a3592a2aa05430dca0765bf1db1118a7 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 25 Jan 2023 13:58:49 -0800 Subject: [PATCH 17/17] Fix a rustfmt error --- crates/bindgen-core/src/lib.rs | 3 --- 1 file changed, 3 deletions(-) diff --git a/crates/bindgen-core/src/lib.rs b/crates/bindgen-core/src/lib.rs index e65ba418b..0e75c7818 100644 --- a/crates/bindgen-core/src/lib.rs +++ b/crates/bindgen-core/src/lib.rs @@ -8,9 +8,6 @@ mod ns; pub use ns::Ns; -#[cfg(feature = "component-generator")] -pub mod component; - #[derive(Default)] pub struct Types { type_info: HashMap,