diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 9573b8c63..f8ce3a685 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -48,15 +48,18 @@ 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 + - 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 @@ -67,36 +70,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 17414241a..a8a4afa8a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # 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" @@ -28,6 +37,23 @@ 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" @@ -40,12 +66,30 @@ 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" @@ -56,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" @@ -114,14 +173,114 @@ 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#b58a197d33f044193c3d608010f5e6ec394ac07e" +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" @@ -131,6 +290,118 @@ 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" @@ -158,6 +429,16 @@ 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" @@ -174,40 +455,22 @@ dependencies = [ ] [[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" +name = "fxhash" +version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bdfb8ce053d86b91919aad980c220b1fb8401a9394410e1c289ed7e66b61835d" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" dependencies = [ - "proc-macro2", - "quote", - "syn", + "byteorder", ] [[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" +name = "generic-array" +version = "0.14.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +checksum = "bff49e947297f3312447abdca79f45f4738097cc82b06e72054d2223f601f1b9" dependencies = [ - "futures-core", - "futures-macro", - "futures-task", - "pin-project-lite", - "pin-utils", - "slab", + "typenum", + "version_check", ] [[package]] @@ -228,6 +491,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22030e2c5a68ec659fde1e949a745124b48e6fa8b045b7ed5bd1fe4ccc5c4e5d" dependencies = [ "fallible-iterator", + "indexmap", "stable_deref_trait", ] @@ -271,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" @@ -337,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" @@ -370,12 +678,58 @@ 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" @@ -400,6 +754,12 @@ 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" @@ -407,68 +767,161 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" [[package]] -name = "pin-project-lite" -version = "0.2.9" +name = "pkg-config" +version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" +checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" [[package]] -name = "pin-utils" -version = "0.1.0" +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 = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" +checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] -name = "proc-macro-error" -version = "1.0.4" +name = "rayon" +version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7" dependencies = [ - "proc-macro-error-attr", - "proc-macro2", - "quote", - "syn", - "version_check", + "either", + "rayon-core", ] [[package]] -name = "proc-macro-error-attr" -version = "1.0.4" +name = "rayon-core" +version = "1.10.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b" dependencies = [ - "proc-macro2", - "quote", - "version_check", + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", ] [[package]] -name = "proc-macro2" -version = "1.0.50" +name = "redox_syscall" +version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" dependencies = [ - "unicode-ident", + "bitflags", ] [[package]] -name = "pulldown-cmark" -version = "0.8.0" +name = "redox_users" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "bitflags", - "memchr", - "unicase", + "getrandom", + "redox_syscall", + "thiserror", ] [[package]] -name = "quote" -version = "1.0.23" +name = "regalloc2" +version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b" +checksum = "300d4fbfb40c1c66a78ba3ddd41c1110247cf52f97b87d0f2fc9209bd49b030c" dependencies = [ - "proc-macro2", + "fxhash", + "log", + "slice-group-by", + "smallvec", ] [[package]] @@ -489,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" @@ -523,6 +970,12 @@ 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" @@ -544,14 +997,28 @@ dependencies = [ ] [[package]] -name = "slab" -version = "0.4.7" +name = "sha2" +version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4614a76b2a8be0058caa9dbbaf66d988527d86d003c11a94fbd335d7661edcef" +checksum = "82e6b795fe2e3b1e845bafcb27aa35405c4d47cdfc92af5fc8d3002f76cebdc0" dependencies = [ - "autocfg", + "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" @@ -590,13 +1057,16 @@ 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 0.21.0", + "wasm-encoder", "wat", "wit-bindgen-core", "wit-component", @@ -607,7 +1077,6 @@ dependencies = [ name = "test-rust-wasm" version = "0.3.0" dependencies = [ - "futures-util", "wit-bindgen-guest-rust", ] @@ -655,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" @@ -745,15 +1229,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 +1238,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 +1255,112 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "27c13dff901f9354fa9a6a877152d9c5642513645985635c9b83bcca99e40ea1" dependencies = [ "anyhow", - "wasmparser 0.99.0", + "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#b58a197d33f044193c3d608010f5e6ec394ac07e" +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#b58a197d33f044193c3d608010f5e6ec394ac07e" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" dependencies = [ "anyhow", "cranelift-entity", @@ -812,22 +1371,113 @@ dependencies = [ "serde", "target-lexicon", "thiserror", - "wasm-encoder 0.21.0", - "wasmparser 0.97.0", + "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#b58a197d33f044193c3d608010f5e6ec394ac07e" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" dependencies = [ "cranelift-entity", "serde", "thiserror", - "wasmparser 0.97.0", + "wasmparser", +] + +[[package]] +name = "wasmtime-wit-bindgen" +version = "6.0.0" +source = "git+https://github.com/bytecodealliance/wasmtime#4ad86752de2c5353183364593d6c37ca43ec2a9d" +dependencies = [ + "anyhow", + "heck", + "wit-parser", ] [[package]] @@ -839,7 +1489,7 @@ dependencies = [ "leb128", "memchr", "unicode-width", - "wasm-encoder 0.22.0", + "wasm-encoder", ] [[package]] @@ -945,14 +1595,17 @@ version = "0.3.0" dependencies = [ "anyhow", "clap", + "heck", + "test-artifacts", + "wasmtime", "wat", "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-component", + "wit-parser", ] [[package]] @@ -960,29 +1613,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 0.21.0", - "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" @@ -991,7 +1625,7 @@ dependencies = [ "clap", "heck", "test-helpers", - "wasm-encoder 0.21.0", + "wasm-encoder", "wit-bindgen-core", "wit-component", ] @@ -1020,21 +1654,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" @@ -1085,8 +1704,8 @@ dependencies = [ "indexmap", "log", "url", - "wasm-encoder 0.22.0", - "wasmparser 0.99.0", + "wasm-encoder", + "wasmparser", "wat", "wit-parser", ] @@ -1105,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 f400c7012..a318d7c13 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,19 +24,15 @@ clap = { version = "4.0.9", features = ["derive"] } env_logger = "0.9.1" indexmap = "1.9.1" -wasmtime-environ = { git = 'https://github.com/bytecodealliance/wasmtime' } -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' } 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 } @@ -45,16 +40,30 @@ wit-bindgen-rust-macro-shared = { path = 'crates/rust-macro-shared', version = ' [[bin]] name = "wit-bindgen" -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'], 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/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/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, 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/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/Cargo.toml b/crates/test-rust-wasm/Cargo.toml index b09bbda45..d2cf90b14 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] @@ -44,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/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/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/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/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..231129db6 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}; @@ -16,67 +15,26 @@ 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 creating hosts that embed WASM modules/components. - #[command(subcommand)] - Host(HostGenerator), - /// 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 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 { + #[cfg(feature = "markdown")] + 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, - }, + #[cfg(feature = "rust")] + 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, - }, + #[cfg(feature = "c")] + 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, - }, + #[cfg(feature = "teavm-java")] + TeavmJava(wit_bindgen_gen_guest_teavm_java::Opts), } #[derive(Debug, Parser)] @@ -114,43 +72,25 @@ 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, - Category::Host(HostGenerator::Js { component, .. }) => &component.common, - } - } -} - fn main() -> Result<()> { let opt: Opt = Opt::parse(); - let common = opt.common().clone(); 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)?; - } - 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 { + #[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(), + }; + + 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(), }; @@ -214,23 +154,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(()) -} 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 301ed7782..000000000 Binary files a/tests/runtime/exports_only/exports_only_component_type.o and /dev/null differ 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 -} 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/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() -} 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 new file mode 100644 index 000000000..2a5c2d3b2 --- /dev/null +++ b/tests/runtime/main.rs @@ -0,0 +1,327 @@ +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 flavorful; +mod lists; +mod many_arguments; +mod numbers; +mod records; +mod smoke; +mod strings; +mod unions; +mod variants; + +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/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() 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() 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() 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); 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() 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(()) +} 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()