From 93261d0d882a971bcb17aaab6472e936d3ef0c05 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Nov 2025 13:22:18 -0800 Subject: [PATCH 1/3] Delete `wasmtime.loader` and `wasmtime.bindgen` This commit deletes two nice-to-have but difficult-to-maintain modules in this repository. Specifically: * The `wasmtime.loader` module is deleted as it was a proof-of-concept but doesn't map well to Python and Wasmtime. For example #302 shows how destruction of Python modules isn't handled correctly. In general it's a "cute" implementation but isn't too too useful and doesn't map well to how Wasm is embedded in many contexts. I'm deleting this to reduce maintenance burden on this repository. It would be ok to reimplement in the future but it would likely require someone with more Python knowledge than I. * The `wasmtime.bindgen` module is deleted in favor of component support in #308. This module still makes sense to have but it would need a ground-up rewrite to work with component types rather than component definitions. This is deferred to future work in #309 and in the meantime it's removed to avoid any confusion about what should be used and what shouldn't. Closes #105 Closes #106 Closes #107 Closes #108 Closes #109 Closes #119 Closes #143 Closes #178 Closes #181 Closes #197 Closes #202 Closes #218 Closes #245 Closes #282 Closes #302 --- .github/actions/setup/action.yml | 32 - ci/build-rust.py | 45 - examples/loader.py | 29 - examples/loader_add.wat | 6 - examples/loader_component.py | 14 - examples/loader_component_add.wat | 12 - examples/loader_load_python.wat | 5 - examples/loader_load_wasi.wat | 13 - examples/loader_load_wasm.wat | 5 - examples/loader_load_wasm_target.wat | 4 - examples/loader_python_target.py | 5 - rust/.gitignore | 1 - rust/Cargo.lock | 694 ---- rust/Cargo.toml | 28 - rust/bindgen.wit | 5 - rust/python.wit | 8 - rust/src/bin/bootstrap.rs | 19 - rust/src/bindgen.rs | 3048 ----------------- rust/src/files.rs | 23 - rust/src/imports.rs | 121 - rust/src/lib.rs | 59 - rust/src/ns.rs | 27 - rust/src/source.rs | 175 - tests/bindgen/__init__.py | 9 - tests/bindgen/bare_funcs/app.py | 3 - tests/bindgen/bare_funcs/component.wit | 5 - tests/bindgen/bare_funcs/test_bare_funcs.py | 9 - tests/bindgen/conftest.py | 146 - tests/bindgen/export_resources/app.py | 30 - tests/bindgen/export_resources/component.wit | 13 - .../export_resources/test_export_resources.py | 12 - tests/bindgen/list_types/app.py | 15 - tests/bindgen/list_types/component.wit | 8 - tests/bindgen/list_types/test_lists.py | 26 - tests/codegen/__init__.py | 137 - tests/codegen/foo.wat | 17 - tests/codegen/test_bare_funcs.py | 42 - tests/codegen/test_empty.py | 13 - tests/codegen/test_empty_import.py | 16 - tests/codegen/test_export_resources.py | 88 - tests/codegen/test_external_types.py | 84 - tests/codegen/test_keywords.py | 87 - tests/codegen/test_lists.py | 112 - tests/codegen/test_many_arguments.py | 124 - tests/codegen/test_records.py | 207 -- tests/codegen/test_scalars.py | 221 -- tests/codegen/test_simple_export.py | 25 - tests/codegen/test_simple_import.py | 37 - tests/codegen/test_two_exports.py | 42 - tests/codegen/test_variants.py | 555 --- wasmtime/bindgen/__init__.py | 160 - wasmtime/bindgen/__main__.py | 40 - wasmtime/loader.py | 174 - 53 files changed, 6835 deletions(-) delete mode 100644 ci/build-rust.py delete mode 100644 examples/loader.py delete mode 100644 examples/loader_add.wat delete mode 100644 examples/loader_component.py delete mode 100644 examples/loader_component_add.wat delete mode 100644 examples/loader_load_python.wat delete mode 100644 examples/loader_load_wasi.wat delete mode 100644 examples/loader_load_wasm.wat delete mode 100644 examples/loader_load_wasm_target.wat delete mode 100644 examples/loader_python_target.py delete mode 100644 rust/.gitignore delete mode 100644 rust/Cargo.lock delete mode 100644 rust/Cargo.toml delete mode 100644 rust/bindgen.wit delete mode 100644 rust/python.wit delete mode 100644 rust/src/bin/bootstrap.rs delete mode 100644 rust/src/bindgen.rs delete mode 100644 rust/src/files.rs delete mode 100644 rust/src/imports.rs delete mode 100644 rust/src/lib.rs delete mode 100644 rust/src/ns.rs delete mode 100644 rust/src/source.rs delete mode 100644 tests/bindgen/__init__.py delete mode 100644 tests/bindgen/bare_funcs/app.py delete mode 100644 tests/bindgen/bare_funcs/component.wit delete mode 100644 tests/bindgen/bare_funcs/test_bare_funcs.py delete mode 100644 tests/bindgen/conftest.py delete mode 100644 tests/bindgen/export_resources/app.py delete mode 100644 tests/bindgen/export_resources/component.wit delete mode 100644 tests/bindgen/export_resources/test_export_resources.py delete mode 100644 tests/bindgen/list_types/app.py delete mode 100644 tests/bindgen/list_types/component.wit delete mode 100644 tests/bindgen/list_types/test_lists.py delete mode 100644 tests/codegen/__init__.py delete mode 100644 tests/codegen/foo.wat delete mode 100644 tests/codegen/test_bare_funcs.py delete mode 100644 tests/codegen/test_empty.py delete mode 100644 tests/codegen/test_empty_import.py delete mode 100644 tests/codegen/test_export_resources.py delete mode 100644 tests/codegen/test_external_types.py delete mode 100644 tests/codegen/test_keywords.py delete mode 100644 tests/codegen/test_lists.py delete mode 100644 tests/codegen/test_many_arguments.py delete mode 100644 tests/codegen/test_records.py delete mode 100644 tests/codegen/test_scalars.py delete mode 100644 tests/codegen/test_simple_export.py delete mode 100644 tests/codegen/test_simple_import.py delete mode 100644 tests/codegen/test_two_exports.py delete mode 100644 tests/codegen/test_variants.py delete mode 100644 wasmtime/bindgen/__init__.py delete mode 100644 wasmtime/bindgen/__main__.py delete mode 100644 wasmtime/loader.py diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 9627b723..86e7be83 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -17,35 +17,3 @@ runs: # Download precompiled wasmtime binaries for running the project - run: python ci/download-wasmtime.py shell: bash - - # https://github.com/actions/cache/blob/main/workarounds.md#improving-cache-restore-performance-on-windows-using-cross-os-caching - - if: ${{ runner.os == 'Windows' }} - name: Use GNU tar - shell: cmd - run: | - echo "Adding GNU tar to PATH" - echo C:\Program Files\Git\usr\bin>>"%GITHUB_PATH%" - - # Ensure the Rust lockfile is up-to-date - - run: cargo fetch --manifest-path rust/Cargo.toml --locked - shell: bash - - # Install the `wasm-tools` binary with the `component` subcommand that is all - # that's needed here. - - uses: bytecodealliance/actions/wasm-tools/setup@v1 - with: - version: "1.208.1" - - # Build the bindgen wasm blob with some extra Rust targets. - - run: | - rustup target add wasm32-unknown-unknown wasm32-wasip1 - echo CARGO_INCREMENTAL=0 >> $GITHUB_ENV - echo CARGO_PROFILE_DEV_DEBUG=0 >> $GITHUB_ENV - echo RUSTC_VERSION=`rustc --version` >> $GITHUB_ENV - shell: bash - - uses: actions/cache@v4 - with: - path: rust/target - key: rust-target-${{ env.RUSTC_VERSION }}-${{ runner.os }}-${{ hashFiles('rust/Cargo.lock') }} - - run: python ci/build-rust.py - shell: bash diff --git a/ci/build-rust.py b/ci/build-rust.py deleted file mode 100644 index 3313d750..00000000 --- a/ci/build-rust.py +++ /dev/null @@ -1,45 +0,0 @@ -# This is a script to generate the `wasmtime/bindgen/generated` directory. That -# directory itself exposes the Python-based functionality for generating -# bindings itself, so a bit of a bootstrapping process happens here. -# -# Bindings generation itself is written in Rust since that's where all of the -# `*.wit` tooling is located. That's compiled to a `bindgen.wasm` file and then -# assembled into a `component.wasm` using the `bindgen.wit` world. -# -# From this compiled component we sort of need to run it on itself. To avoid -# that odd bootstrapping problem we work around that by running the bindgen -# on the native platform, on the component, to generate bindings. That -# is then loaded here and re-executed, through wasm, to ensure that everything -# remains the same. - -import subprocess - - -def main(): - print('======================= Building bindgen.wasm =====================') - - subprocess.run( - ['cargo', 'build', '--release', '--target=wasm32-wasip1', '-p=bindgen'], - cwd='rust' - ).check_returncode() - - core = 'rust/target/wasm32-wasip1/release/bindgen.wasm' - wasi = 'ci/wasi_snapshot_preview1.reactor.wasm' - component = 'rust/target/component.wasm' - - print('======================= Building component.wasm ===================') - - subprocess.run( - ['wasm-tools', 'component', 'new', core, '--adapt', f'wasi_snapshot_preview1={wasi}', '-o', component], - ).check_returncode() - - print('======================= Bootstrapping with native platform ========') - - subprocess.run( - ['cargo', 'run', '-p=bindgen', '--features=cli', 'target/component.wasm', '../wasmtime/bindgen/generated'], - cwd='rust' - ).check_returncode() - - -if __name__ == '__main__': - main() diff --git a/examples/loader.py b/examples/loader.py deleted file mode 100644 index 504a09a7..00000000 --- a/examples/loader.py +++ /dev/null @@ -1,29 +0,0 @@ -# This example shows how you can use the `wasmtime.loader` module to load wasm -# modules as if they were native Python modules - -import wasmtime.loader # noqa: F401 - -def run(): - import loader_load_python # type: ignore - import loader_load_wasm # type: ignore - import loader_load_wasi # type: ignore # noqa: F401 - - # This imports our `loader_add.wat` file next to this module - import loader_add # type: ignore - assert(loader_add.add(1, 2) == 3) - - # This imports our `loader_load_wasm.wat`, which in turn imports - # `loader_load_wasm_target.wat`, which gives us the answer of 4 - assert(loader_load_wasm.call_dependency() == 4) - - # This imports our `loader_load_python.wat` file which then imports its own - # python file. - assert(loader_load_python.call_python() == 42) - - # This imports our `loader_load_wasi.wat`, which in turn imports - # the random_get functionality from the wasi runtime environment - random_value = loader_load_wasi.wasi_random() - assert(random_value >= 0 and random_value < 256) - -if __name__ == '__main__': - run() diff --git a/examples/loader_add.wat b/examples/loader_add.wat deleted file mode 100644 index cc85eb39..00000000 --- a/examples/loader_add.wat +++ /dev/null @@ -1,6 +0,0 @@ -(module - (func (export "add") (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.add) -) diff --git a/examples/loader_component.py b/examples/loader_component.py deleted file mode 100644 index b8406101..00000000 --- a/examples/loader_component.py +++ /dev/null @@ -1,14 +0,0 @@ -# This example shows how you can use the `wasmtime.loader` module to load wasm -# components without having to generate bindings manually - -import wasmtime, wasmtime.loader - -def run(): - import loader_component_add # type: ignore - - store = wasmtime.Store() - component = loader_component_add.Root(store) - assert component.add(store, 1, 2) == 3 - -if __name__ == '__main__': - run() diff --git a/examples/loader_component_add.wat b/examples/loader_component_add.wat deleted file mode 100644 index fcf8f699..00000000 --- a/examples/loader_component_add.wat +++ /dev/null @@ -1,12 +0,0 @@ -(component - (core module $C - (func (export "add") (param i32 i32) (result i32) - local.get 0 - local.get 1 - i32.add) - ) - (core instance $c (instantiate $C)) - (core func $add (alias core export $c "add")) - (func (export "add") (param "x" s32) (param "y" s32) (result s32) - (canon lift (core func $add))) -) diff --git a/examples/loader_load_python.wat b/examples/loader_load_python.wat deleted file mode 100644 index 74424a19..00000000 --- a/examples/loader_load_python.wat +++ /dev/null @@ -1,5 +0,0 @@ -(module - (import "loader_python_target" "answer" (func $python (result i32))) - (func (export "call_python") (result i32) - call $python) -) diff --git a/examples/loader_load_wasi.wat b/examples/loader_load_wasi.wat deleted file mode 100644 index 08db7d27..00000000 --- a/examples/loader_load_wasi.wat +++ /dev/null @@ -1,13 +0,0 @@ -(module - (import "wasi_snapshot_preview1" "random_get" (func $random_get (param i32 i32) (result i32))) - (memory 1) - (export "memory" (memory 0)) - (func $wasi_random (export "wasi_random") - (result i32) - (call $random_get - (i32.const 0) ;; buffer start position - (i32.const 1)) ;; buffer length 1 bytes - ;; this bounds our random between 0-255 - drop ;; random_get returns an error code - (i32.const 0) - i32.load)) diff --git a/examples/loader_load_wasm.wat b/examples/loader_load_wasm.wat deleted file mode 100644 index 9efde2ec..00000000 --- a/examples/loader_load_wasm.wat +++ /dev/null @@ -1,5 +0,0 @@ -(module - (import "loader_load_wasm_target" "dependency" (func (result i32))) - (func (export "call_dependency") (result i32) - call 0) -) diff --git a/examples/loader_load_wasm_target.wat b/examples/loader_load_wasm_target.wat deleted file mode 100644 index 6fd879b4..00000000 --- a/examples/loader_load_wasm_target.wat +++ /dev/null @@ -1,4 +0,0 @@ -(module - (func (export "dependency") (result i32) - i32.const 4) -) diff --git a/examples/loader_python_target.py b/examples/loader_python_target.py deleted file mode 100644 index 24299c93..00000000 --- a/examples/loader_python_target.py +++ /dev/null @@ -1,5 +0,0 @@ -# This is part of the `loader.py` example - - -def answer(): - return 42 diff --git a/rust/.gitignore b/rust/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/rust/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/rust/Cargo.lock b/rust/Cargo.lock deleted file mode 100644 index a2f92def..00000000 --- a/rust/Cargo.lock +++ /dev/null @@ -1,694 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -version = 4 - -[[package]] -name = "anyhow" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775" - -[[package]] -name = "autocfg" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" - -[[package]] -name = "bindgen" -version = "0.0.0" -dependencies = [ - "anyhow", - "heck 0.4.1", - "indexmap", - "wasmtime-environ", - "wit-bindgen", - "wit-bindgen-core", - "wit-component", - "wit-parser", -] - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cobs" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "67ba02a97a2bd10f4b59b25c7973101c79642302776489e030cd13cdab09ed15" - -[[package]] -name = "cranelift-bitset" -version = "0.125.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0e60319a8242c8d1c7b5a2444d140c416f903f75e0d84da3256fceb822bab85" -dependencies = [ - "serde", - "serde_derive", -] - -[[package]] -name = "cranelift-entity" -version = "0.125.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15564c6f0c72750ca4374f40b044857cbc8087571e46d4c7ccdbdcc29b1dec8b" -dependencies = [ - "cranelift-bitset", - "serde", - "serde_derive", -] - -[[package]] -name = "crc32fast" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "embedded-io" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef1a6892d9eef45c8fa6b9e0086428a2cca8491aca8f787c534a3d6d0bcb3ced" - -[[package]] -name = "embedded-io" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "foldhash" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f81ec6369c545a7d40e4589b5597581fa1c441fe1cce96dd1de43159910a36a2" - -[[package]] -name = "futures" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "65bc07b1a8bc7c85c5f2e110c476c7389b4554ba72af57d8445ea63a576b0876" -dependencies = [ - "futures-channel", - "futures-core", - "futures-executor", - "futures-io", - "futures-sink", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-channel" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10" -dependencies = [ - "futures-core", - "futures-sink", -] - -[[package]] -name = "futures-core" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" - -[[package]] -name = "futures-executor" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f" -dependencies = [ - "futures-core", - "futures-task", - "futures-util", -] - -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - -[[package]] -name = "futures-macro" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "futures-sink" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e575fab7d1e0dcb8d0c7bcf9a63ee213816ab51902e6d244a95819acacf1d4f7" - -[[package]] -name = "futures-task" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f90f7dce0722e95104fcb095585910c0977252f286e354b5e3bd38902cd99988" - -[[package]] -name = "futures-util" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" -dependencies = [ - "futures-channel", - "futures-core", - "futures-io", - "futures-macro", - "futures-sink", - "futures-task", - "memchr", - "pin-project-lite", - "pin-utils", - "slab", -] - -[[package]] -name = "gimli" -version = "0.32.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "93563d740bc9ef04104f9ed6f86f1e3275c2cdafb95664e26584b9ca807a8ffe" -dependencies = [ - "indexmap", -] - -[[package]] -name = "hashbrown" -version = "0.15.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289" -dependencies = [ - "foldhash", - "serde", -] - -[[package]] -name = "heck" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" -dependencies = [ - "unicode-segmentation", -] - -[[package]] -name = "heck" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" - -[[package]] -name = "id-arena" -version = "2.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" - -[[package]] -name = "indexmap" -version = "2.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9c992b02b5b4c94ea26e32fe5bccb7aa7d9f390ab5c1221ff895bc7ea8b652" -dependencies = [ - "equivalent", - "hashbrown", - "serde", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "leb128fmt" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09edd9e8b54e49e587e4f6295a7d29c3ea94d469cb40ab8ca70b288248a81db2" - -[[package]] -name = "log" -version = "0.4.27" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "object" -version = "0.37.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ff76201f031d8863c38aa7f905eca4f53abbfa15f609db4277d44cd8938f33fe" -dependencies = [ - "crc32fast", - "hashbrown", - "indexmap", - "memchr", -] - -[[package]] -name = "once_cell" -version = "1.20.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" - -[[package]] -name = "pin-project-lite" -version = "0.2.16" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b3cff922bd51709b605d9ead9aa71031d81447142d828eb4a6eba76fe619f9b" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "postcard" -version = "1.0.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f7f0a8d620d71c457dd1d47df76bb18960378da56af4527aaa10f515eee732e" -dependencies = [ - "cobs", - "embedded-io 0.4.0", - "embedded-io 0.6.1", - "serde", -] - -[[package]] -name = "prettyplease" -version = "0.2.25" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64d1ec885c64d0457d564db4ec299b2dae3f9c02808b8ad9c3a089c591b18033" -dependencies = [ - "proc-macro2", - "syn", -] - -[[package]] -name = "proc-macro2" -version = "1.0.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "60946a68e5f9d28b0dc1c21bb8a97ee7d018a8b322fa57838ba31cc878e22d99" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "semver" -version = "1.0.23" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "61697e0a1c7e512e84a621326239844a24d8207b4669b41bc18b32ea5cbf988b" -dependencies = [ - "serde", -] - -[[package]] -name = "serde" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - -[[package]] -name = "serde_json" -version = "1.0.132" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d726bfaff4b320266d395898905d0eba0345aae23b54aee3a737e260fd46db03" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "slab" -version = "0.4.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" -dependencies = [ - "autocfg", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" -dependencies = [ - "serde", -] - -[[package]] -name = "syn" -version = "2.0.96" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d5d0adab1ae378d7f53bdebc67a39f1f151407ef230f0ce2883572f5d8985c80" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "target-lexicon" -version = "0.13.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc12939a1c9b9d391e0b7135f72fd30508b73450753e28341fed159317582a77" - -[[package]] -name = "termcolor" -version = "1.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" -dependencies = [ - "winapi-util", -] - -[[package]] -name = "unicode-ident" -version = "1.0.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" - -[[package]] -name = "unicode-segmentation" -version = "1.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493" - -[[package]] -name = "unicode-xid" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ebc1c04c71510c7f702b52b7c350734c9ff1295c464a03335b00bb84fc54f853" - -[[package]] -name = "wasm-encoder" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5be00faa2b4950c76fe618c409d2c3ea5a3c9422013e079482d78544bb2d184c" -dependencies = [ - "leb128fmt", - "wasmparser", -] - -[[package]] -name = "wasm-metadata" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "20b3ec880a9ac69ccd92fbdbcf46ee833071cf09f82bb005b2327c7ae6025ae2" -dependencies = [ - "anyhow", - "indexmap", - "wasm-encoder", - "wasmparser", -] - -[[package]] -name = "wasmparser" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c9d90bb93e764f6beabf1d02028c70a2156a6583e63ac4218dd07ef733368b0" -dependencies = [ - "bitflags", - "hashbrown", - "indexmap", - "semver", - "serde", -] - -[[package]] -name = "wasmprinter" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b3981f3d51f39f24f5fc90f93049a90f08dbbca8deba602cd46bb8ca67a94718" -dependencies = [ - "anyhow", - "termcolor", - "wasmparser", -] - -[[package]] -name = "wasmtime-environ" -version = "38.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "633f753e4acbec1c0bfc28c266c5dd9e50e0212cafbb6d5a24cbb61d4d41d7ee" -dependencies = [ - "anyhow", - "cranelift-bitset", - "cranelift-entity", - "gimli", - "indexmap", - "log", - "object", - "postcard", - "semver", - "serde", - "serde_derive", - "smallvec", - "target-lexicon", - "wasm-encoder", - "wasmparser", - "wasmprinter", - "wasmtime-internal-component-util", -] - -[[package]] -name = "wasmtime-internal-component-util" -version = "38.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c257e4bb3e13d6838430e8ca2745a234ceeb57275ea5bfc3fc7c200ca6a77441" - -[[package]] -name = "winapi-util" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" -dependencies = [ - "windows-sys", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm", - "windows_aarch64_msvc", - "windows_i686_gnu", - "windows_i686_gnullvm", - "windows_i686_msvc", - "windows_x86_64_gnu", - "windows_x86_64_gnullvm", - "windows_x86_64_msvc", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "wit-bindgen" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f17a85883d4e6d00e8a97c586de764dabcc06133f7f1d55dce5cdc070ad7fe59" -dependencies = [ - "bitflags", - "futures", - "once_cell", - "wit-bindgen-rust-macro", -] - -[[package]] -name = "wit-bindgen-core" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabd629f94da277abc739c71353397046401518efb2c707669f805205f0b9890" -dependencies = [ - "anyhow", - "heck 0.5.0", - "wit-parser", -] - -[[package]] -name = "wit-bindgen-rust" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a4232e841089fa5f3c4fc732a92e1c74e1a3958db3b12f1de5934da2027f1f4" -dependencies = [ - "anyhow", - "heck 0.5.0", - "indexmap", - "prettyplease", - "syn", - "wasm-metadata", - "wit-bindgen-core", - "wit-component", -] - -[[package]] -name = "wit-bindgen-rust-macro" -version = "0.46.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e0d4698c2913d8d9c2b220d116409c3f51a7aa8d7765151b886918367179ee9" -dependencies = [ - "anyhow", - "prettyplease", - "proc-macro2", - "quote", - "syn", - "wit-bindgen-core", - "wit-bindgen-rust", -] - -[[package]] -name = "wit-component" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "88a866b19dba2c94d706ec58c92a4c62ab63e482b4c935d2a085ac94caecb136" -dependencies = [ - "anyhow", - "bitflags", - "indexmap", - "log", - "serde", - "serde_derive", - "serde_json", - "wasm-encoder", - "wasm-metadata", - "wasmparser", - "wit-parser", -] - -[[package]] -name = "wit-parser" -version = "0.239.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "55c92c939d667b7bf0c6bf2d1f67196529758f99a2a45a3355cc56964fd5315d" -dependencies = [ - "anyhow", - "id-arena", - "indexmap", - "log", - "semver", - "serde", - "serde_derive", - "serde_json", - "unicode-xid", - "wasmparser", -] diff --git a/rust/Cargo.toml b/rust/Cargo.toml deleted file mode 100644 index 4c98261c..00000000 --- a/rust/Cargo.toml +++ /dev/null @@ -1,28 +0,0 @@ -[package] -name = "bindgen" -publish = false -edition = "2021" - -[lib] -crate-type = ['cdylib', 'rlib'] - -[dependencies] -anyhow = "1.0" -heck = { version = "0.4", features = ["unicode"] } -wit-parser = "0.239.0" -wit-component = "0.239.0" -indexmap = "2.0" -wasmtime-environ = { version = "38.0.0", features = ['component-model', 'compile'] } -wit-bindgen = "0.46.0" -wit-bindgen-core = "0.46.0" - - -[features] -cli = [] - -[[bin]] -name = "bootstrap" -required-features = ["cli"] - -[profile.release] -strip = 'debuginfo' diff --git a/rust/bindgen.wit b/rust/bindgen.wit deleted file mode 100644 index 51d364f7..00000000 --- a/rust/bindgen.wit +++ /dev/null @@ -1,5 +0,0 @@ -package wasmtime:python; - -world bindgen { - export generate: func(name: string, wit: list) -> result>>, string>; -} diff --git a/rust/python.wit b/rust/python.wit deleted file mode 100644 index 143076f1..00000000 --- a/rust/python.wit +++ /dev/null @@ -1,8 +0,0 @@ -package wasi:snapshot-preview1 - -world python { - import python: interface { - print: func(slice: list) - eprint: func(slice: list) - } -} diff --git a/rust/src/bin/bootstrap.rs b/rust/src/bin/bootstrap.rs deleted file mode 100644 index 024ac9ef..00000000 --- a/rust/src/bin/bootstrap.rs +++ /dev/null @@ -1,19 +0,0 @@ -use std::path::PathBuf; - -fn main() { - let mut args = std::env::args().skip(1); - let component = args.next().unwrap(); - let out_dir = PathBuf::from(args.next().unwrap()); - - let mut gen = bindgen::WasmtimePy::default(); - let mut files = Default::default(); - let component = std::fs::read(&component).unwrap(); - gen.generate("bindgen", &component, &mut files).unwrap(); - - for (name, bytes) in files.iter() { - let path = out_dir.join(name); - let parent = path.parent().unwrap(); - std::fs::create_dir_all(parent).unwrap(); - std::fs::write(&path, bytes).unwrap(); - } -} diff --git a/rust/src/bindgen.rs b/rust/src/bindgen.rs deleted file mode 100644 index b163112a..00000000 --- a/rust/src/bindgen.rs +++ /dev/null @@ -1,3048 +0,0 @@ -//! Code generator for the `wasmtime` PyPI package. -//! -//! This crate will generate bindings for a single component, like JS, for -//! Python source code. Component-model types are translated to Python and the -//! component is executed using the `wasmtime` PyPI package which is bindings to -//! the `wasmtime` C API which is built on the `wasmtime` Rust API. -//! -//! The generated structure of the bindings looks like follows: -//! -//! ```ignore -//! out_dir/ -//! __init__.py -//! types.py # types shared by all imports/exports -//! imports/ # only present if interfaces are imported -//! __init__.py # reexports `Foo` protocols for each interface -//! foo.py # types and definitions specific to interface `foo` -//! .. -//! exports/ # only present with exported interfaces -//! __init__.py # empty file -//! bar.py # contains `Bar` as the exported interface -//! .. -//! ``` -//! -//! The top-level `__init__.py` contains a `class Foo` where `Foo` is the name -//! of the component. It contains top-level functions for all top-level exports -//! and exported instances are modeled as a method which returns a struct from -//! `exports/*.py`. - -use crate::files::Files; -use crate::ns::Ns; -use crate::source::{self, Source}; -use anyhow::{bail, Context, Result}; -use heck::*; -use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::fmt::Write; -use std::mem; -use wasmtime_environ::component::{ - CanonicalOptionsDataModel, Component, ComponentTypes, ComponentTypesBuilder, CoreDef, - CoreExport, Export, ExportItem, GlobalInitializer, InstantiateModule, InterfaceType, - LoweredIndex, OptionsIndex, RuntimeImportIndex, RuntimeInstanceIndex, StaticModuleIndex, - StringEncoding, Trampoline, TrampolineIndex, Translator, TypeFuncIndex, TypeResourceTableIndex, -}; -use wasmtime_environ::prelude::*; -use wasmtime_environ::{EntityIndex, ModuleTranslation, PrimaryMap, ScopeVec, Tunables}; -use wit_bindgen_core::abi::{self, AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType}; -use wit_component::DecodedWasm; -use wit_parser::*; - -/// The name under which to group "bare" host functions (i.e. those imported -/// directly by the world rather than via an interface). -const BARE_FUNCTION_NAMESPACE: &str = "host"; - -#[derive(Default)] -pub struct WasmtimePy { - // `$out_dir/__init__.py` - init: Source, - // `$out_dir/types.py` - types: Source, - // `$out_dir/intrinsics.py` - intrinsics: Source, - // `$out_dir/imports/__init__.py` - imports_init: Source, - // `$out_dir/exports/$name.py` - exports: BTreeMap, - - /// Known imported interfaces to have as an argument to construction of the - /// main component. - imports: Vec, - - /// All intrinsics emitted to `self.intrinsics` so far. - all_intrinsics: BTreeSet<&'static str>, - - sizes: SizeAlign, - - imported_interfaces: HashMap, - exported_interfaces: HashMap, - - lowerings: PrimaryMap, - resource_trampolines: Vec<(TrampolineIndex, Trampoline)>, -} - -pub type ResourceMap = BTreeMap; - -pub struct ResourceTable { - pub data: ResourceData, -} - -pub enum ResourceData { - Host { tid: TypeResourceTableIndex }, -} - -pub fn dealias(resolve: &Resolve, mut id: TypeId) -> TypeId { - loop { - match &resolve.types[id].kind { - TypeDefKind::Type(Type::Id(that_id)) => id = *that_id, - _ => break id, - } - } -} - -impl WasmtimePy { - /// Generate bindings to load and instantiate the specific binary component - /// provided. - pub fn generate(&mut self, _name: &str, binary: &[u8], files: &mut Files) -> Result<()> { - // Use the `wit-component` crate here to parse `binary` and discover - // the type-level descriptions and `Interface`s corresponding to the - // component binary. This is effectively a step that infers a "world" of - // a component. Right now `interfaces` is a world-like thing and this - // will likely change as worlds are iterated on in the component model - // standard. Regardless though this is the step where types are learned - // and `Interface`s are constructed for further code generation below. - let (resolve, id) = match wit_component::decode(binary) - .context("failed to extract interface information from component")? - { - DecodedWasm::Component(resolve, world) => (resolve, world), - DecodedWasm::WitPackage(..) => bail!("expected a component"), - }; - self.sizes.fill(&resolve); - - // 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_u64(); - let mut validator = wasmtime_environ::wasmparser::Validator::new(); - let mut types = ComponentTypesBuilder::new(&validator); - let (component, modules) = Translator::new(&tunables, &mut validator, &mut types, &scope) - .translate(binary) - .context("failed to parse the input component")?; - let world = &resolve.worlds[id]; - - // 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(&self.core_file_name(&world.name, i.as_u32()), module.wasm); - } - - // With all that prep work delegate to `generate` here - // to generate all the type-level descriptions for this component now - // that the interfaces in/out are understood. - let mut root_functions = Vec::new(); - for (world_key, world_item) in world.imports.clone().into_iter() { - match world_item { - WorldItem::Function(function) => root_functions.push(function), - WorldItem::Interface { id, .. } => { - let interface = &resolve.interfaces[id]; - let iface_name = match world_key { - WorldKey::Name(name) => name, - WorldKey::Interface(_) => interface.name.as_ref().unwrap().to_string(), - }; - self.import_interface(&resolve, &iface_name, id, files) - } - WorldItem::Type(_) => unimplemented!(), - } - } - if !root_functions.is_empty() { - self.import_functions(&resolve, &root_functions); - } - - for (world_key, export) in world.exports.clone().into_iter() { - match export { - WorldItem::Function(_) => {} - WorldItem::Interface { id, .. } => { - let interface = &resolve.interfaces[id]; - let iface_name = match world_key { - WorldKey::Name(name) => name, - WorldKey::Interface(_) => interface.name.as_ref().unwrap().to_string(), - }; - self.export_interface(&resolve, &iface_name, id, files) - } - WorldItem::Type(_) => unreachable!(), - } - } - self.finish_interfaces(&world, !root_functions.is_empty(), files); - - for (trampoline_index, trampoline) in component.trampolines.iter() { - match trampoline { - Trampoline::LowerImport { - index, - lower_ty, - options, - } => { - let i = self - .lowerings - .push((trampoline_index, *lower_ty, options.clone())); - assert_eq!(i, *index); - } - Trampoline::Transcoder { .. } => unimplemented!(), - Trampoline::AlwaysTrap => unimplemented!(), - Trampoline::ResourceNew { ty, instance } => { - self.resource_trampolines.push(( - trampoline_index, - Trampoline::ResourceNew { - ty: *ty, - instance: *instance, - }, - )); - } - Trampoline::ResourceRep { ty, instance } => { - self.resource_trampolines.push(( - trampoline_index, - Trampoline::ResourceRep { - ty: *ty, - instance: *instance, - }, - )); - } - Trampoline::ResourceDrop { ty, instance } => { - self.resource_trampolines.push(( - trampoline_index, - Trampoline::ResourceDrop { - ty: *ty, - instance: *instance, - }, - )); - } - Trampoline::ResourceEnterCall => unimplemented!(), - Trampoline::ResourceExitCall => unimplemented!(), - Trampoline::ResourceTransferOwn => unimplemented!(), - Trampoline::ResourceTransferBorrow => unimplemented!(), - Trampoline::BackpressureSet { .. } => unimplemented!(), - Trampoline::TaskReturn { .. } => unimplemented!(), - Trampoline::WaitableSetWait { .. } => unimplemented!(), - Trampoline::WaitableSetPoll { .. } => unimplemented!(), - Trampoline::WaitableSetNew { .. } => unimplemented!(), - Trampoline::WaitableSetDrop { .. } => unimplemented!(), - Trampoline::WaitableJoin { .. } => unimplemented!(), - Trampoline::ThreadYield { .. } => unimplemented!(), - Trampoline::SubtaskDrop { .. } => unimplemented!(), - Trampoline::StreamNew { .. } => unimplemented!(), - Trampoline::StreamRead { .. } => unimplemented!(), - Trampoline::StreamWrite { .. } => unimplemented!(), - Trampoline::StreamCancelRead { .. } => unimplemented!(), - Trampoline::StreamCancelWrite { .. } => unimplemented!(), - Trampoline::StreamDropReadable { .. } => unimplemented!(), - Trampoline::StreamDropWritable { .. } => unimplemented!(), - Trampoline::FutureNew { .. } => unimplemented!(), - Trampoline::FutureRead { .. } => unimplemented!(), - Trampoline::FutureWrite { .. } => unimplemented!(), - Trampoline::FutureCancelRead { .. } => unimplemented!(), - Trampoline::FutureCancelWrite { .. } => unimplemented!(), - Trampoline::FutureDropReadable { .. } => unimplemented!(), - Trampoline::FutureDropWritable { .. } => unimplemented!(), - Trampoline::ErrorContextNew { .. } => unimplemented!(), - Trampoline::ErrorContextDebugMessage { .. } => unimplemented!(), - Trampoline::ErrorContextDrop { .. } => unimplemented!(), - Trampoline::FutureTransfer => unimplemented!(), - Trampoline::StreamTransfer => unimplemented!(), - Trampoline::ErrorContextTransfer => unimplemented!(), - Trampoline::TaskCancel { .. } => unimplemented!(), - Trampoline::SubtaskCancel { .. } => unimplemented!(), - Trampoline::PrepareCall { .. } => unimplemented!(), - Trampoline::SyncStartCall { .. } => unimplemented!(), - Trampoline::AsyncStartCall { .. } => unimplemented!(), - Trampoline::ContextGet { .. } => unimplemented!(), - Trampoline::ContextSet { .. } => unimplemented!(), - Trampoline::BackpressureInc { .. } => unimplemented!(), - Trampoline::BackpressureDec { .. } => unimplemented!(), - } - } - - let comp_types = types.finish(&Component::default()).0; - // And finally generate the code necessary to instantiate the given - // component to this method using the `Component` that - // `wasmtime-environ` parsed. - self.instantiate(&resolve, id, &component.component, &modules, &comp_types); - - self.finish_component(&world.name, files); - - Ok(()) - } - - fn interface<'a>(&'a mut self, resolve: &'a Resolve) -> InterfaceGenerator<'a> { - InterfaceGenerator { - gen: self, - resolve, - src: Source::default(), - interface: None, - at_root: false, - } - } - - fn print_some(&mut self) { - if !self.all_intrinsics.insert("some_type") { - return; - } - - self.types.pyimport("dataclasses", "dataclass"); - self.types.pyimport("typing", "TypeVar"); - self.types.pyimport("typing", "Generic"); - self.types.push_str( - " - S = TypeVar('S') - @dataclass - class Some(Generic[S]): - value: S - ", - ); - } - - fn print_result(&mut self) { - if !self.all_intrinsics.insert("result_type") { - return; - } - - self.types.pyimport("dataclasses", "dataclass"); - self.types.pyimport("typing", "TypeVar"); - self.types.pyimport("typing", "Generic"); - self.types.pyimport("typing", "Union"); - self.types.push_str( - " - T = TypeVar('T') - @dataclass - class Ok(Generic[T]): - value: T - E = TypeVar('E') - @dataclass - class Err(Generic[E]): - value: E - - Result = Union[Ok[T], Err[E]] - ", - ); - } - - fn instantiate( - &mut self, - resolve: &Resolve, - id: WorldId, - component: &Component, - modules: &PrimaryMap>, - types: &ComponentTypes, - ) { - self.init.pyimport("wasmtime", None); - - let world = &resolve.worlds[id]; - let camel = world.name.to_upper_camel_case().escape(); - let imports = if !component.import_types.is_empty() { - self.init - .pyimport(".imports", format!("{camel}Imports").as_str()); - format!(", import_object: {camel}Imports") - } else { - String::new() - }; - - uwriteln!(self.init, "class {camel}:"); - self.init.indent(); - - self.init.push_str("\n"); - - uwriteln!( - self.init, - "def __init__(self, store: wasmtime.Store{imports}) -> None:" - ); - self.init.indent(); - let mut i = Instantiator { - name: &world.name, - gen: self, - resolve: &resolve, - modules, - component, - world: id, - instances: PrimaryMap::default(), - lifts: 0, - resource_tables_initialized: vec![false; component.num_runtime_instances as usize], - types, - }; - i.gen_canon_resources(); - for init in component.initializers.iter() { - i.global_initializer(init); - } - if component.initializers.len() == 0 { - i.gen.init.push_str("pass\n"); - } - let (lifts, nested, resource_map) = i.exports(&component); - i.gen.init.dedent(); - - i.generate_lifts(&camel, None, &lifts, &resource_map); - for (package_name, lifts) in nested { - let name = match package_name.find("/") { - Some(pos) => package_name.split_at(pos + 1).1, - None => &package_name, - }; - i.generate_lifts(&camel, Some(name), &lifts, &resource_map); - } - i.gen.init.dedent(); - } - - fn core_file_name(&mut self, name: &str, idx: u32) -> String { - format!("{name}.core{idx}.wasm") - } - - fn finish_component(&mut self, _name: &str, files: &mut Files) { - if !self.imports_init.is_empty() { - files.push("imports/__init__.py", self.imports_init.finish().as_bytes()); - } - if !self.types.is_empty() { - files.push("types.py", self.types.finish().as_bytes()); - } - if !self.intrinsics.is_empty() { - files.push("intrinsics.py", self.intrinsics.finish().as_bytes()); - } - - for (name, src) in self.exports.iter() { - let snake = name.to_snake_case().escape(); - files.push(&format!("exports/{snake}.py"), src.finish().as_bytes()); - } - if !self.exports.is_empty() { - files.push("exports/__init__.py", b""); - } - - files.push("__init__.py", self.init.finish().as_bytes()); - } - - fn import_interface( - &mut self, - resolve: &Resolve, - name: &str, - iface: InterfaceId, - files: &mut Files, - ) { - self.imported_interfaces.insert(iface, name.to_string()); - let mut gen = self.interface(resolve); - gen.interface = Some(iface); - gen.types(iface); - - // Generate a "protocol" class which I'm led to believe is the rough - // equivalent of a Rust trait in Python for this imported interface. - // This will be referenced in the constructor for the main component. - let camel = format!("Host{}", name.to_upper_camel_case()).escape(); - let snake = name.to_snake_case().escape(); - gen.src.pyimport("typing", "Protocol"); - uwriteln!(gen.src, "class {camel}(Protocol):"); - gen.src.indent(); - for (_, func) in resolve.interfaces[iface].functions.iter() { - gen.src.pyimport("abc", "abstractmethod"); - gen.src.push_str("@abstractmethod\n"); - gen.print_sig(func, true); - gen.src.push_str(":\n"); - gen.src.indent(); - gen.src.push_str("raise NotImplementedError\n"); - gen.src.dedent(); - } - if resolve.interfaces[iface].functions.is_empty() { - gen.src.push_str("pass\n"); - } - gen.src.dedent(); - gen.src.push_str("\n"); - - let src = gen.src.finish(); - files.push(&format!("imports/{snake}.py"), src.as_bytes()); - self.imports.push(name.to_string()); - } - - fn import_functions(&mut self, resolve: &Resolve, functions: &[Function]) { - let src = mem::take(&mut self.imports_init); - let mut gen = self.interface(resolve); - gen.src = src; - - let camel = BARE_FUNCTION_NAMESPACE.to_upper_camel_case(); - gen.src.pyimport("typing", "Protocol"); - gen.src.pyimport("abc", "abstractmethod"); - uwriteln!(gen.src, "class {camel}(Protocol):"); - gen.src.indent(); - for func in functions.iter() { - gen.src.push_str("@abstractmethod\n"); - gen.print_sig(func, true); - gen.src.push_str(":\n"); - gen.src.indent(); - gen.src.push_str("raise NotImplementedError\n"); - gen.src.dedent(); - } - gen.src.dedent(); - gen.src.push_str("\n"); - - self.imports_init = gen.src; - } - - fn export_interface( - &mut self, - resolve: &Resolve, - name: &str, - iface: InterfaceId, - _files: &mut Files, - ) { - self.exported_interfaces.insert(iface, name.to_string()); - let mut gen = self.interface(resolve); - gen.interface = Some(iface); - gen.types(iface); - - // Only generate types for exports and this will get finished later on - // as lifted functions need to be inserted into these files as they're - // discovered. - let src = gen.src; - self.exports.insert(name.to_string(), src); - } - - fn finish_interfaces(&mut self, world: &World, has_root_imports: bool, _files: &mut Files) { - if has_root_imports || !self.imports.is_empty() { - let camel = world.name.to_upper_camel_case().escape(); - self.imports_init.pyimport("dataclasses", "dataclass"); - uwriteln!(self.imports_init, "@dataclass"); - uwriteln!(self.imports_init, "class {camel}Imports:"); - self.imports_init.indent(); - if has_root_imports { - let camel = BARE_FUNCTION_NAMESPACE.to_upper_camel_case(); - let snake = BARE_FUNCTION_NAMESPACE.to_snake_case(); - uwriteln!(self.imports_init, "{snake}: {camel}"); - } - for import in self.imports.iter() { - let snake = import.to_snake_case().escape(); - let camel = format!("Host{}", import.to_upper_camel_case()).escape(); - self.imports_init - .pyimport(&format!(".{snake}"), camel.as_str()); - uwriteln!(self.imports_init, "{snake}: {camel}"); - } - self.imports_init.dedent(); - } - } -} - -fn array_ty(resolve: &Resolve, ty: &Type) -> Option<&'static str> { - match ty { - Type::Bool => None, - Type::U8 => Some("c_uint8"), - Type::S8 => Some("c_int8"), - Type::U16 => Some("c_uint16"), - Type::S16 => Some("c_int16"), - Type::U32 => Some("c_uint32"), - Type::S32 => Some("c_int32"), - Type::U64 => Some("c_uint64"), - Type::S64 => Some("c_int64"), - Type::F32 => Some("c_float"), - Type::F64 => Some("c_double"), - Type::Char => None, - Type::String => None, - Type::ErrorContext => None, - Type::Id(id) => match &resolve.types[*id].kind { - TypeDefKind::Type(t) => array_ty(resolve, t), - _ => None, - }, - } -} - -struct Instantiator<'a> { - name: &'a str, - gen: &'a mut WasmtimePy, - modules: &'a PrimaryMap>, - instances: PrimaryMap, - world: WorldId, - component: &'a Component, - lifts: usize, - resolve: &'a Resolve, - resource_tables_initialized: Vec, - types: &'a ComponentTypes, -} - -struct Lift<'a> { - callee: String, - opts: OptionsIndex, - func: &'a Function, - interface: Option, -} - -impl<'a> Instantiator<'a> { - fn 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::ExtractMemory(m) => { - let def = self.core_export(&m.export); - let i = m.index.as_u32(); - uwriteln!(self.gen.init, "core_memory{i} = {def}"); - uwriteln!( - self.gen.init, - "assert(isinstance(core_memory{i}, wasmtime.Memory))" - ); - uwriteln!(self.gen.init, "self._core_memory{i} = core_memory{i}",); - } - GlobalInitializer::ExtractRealloc(r) => { - let def = self.core_def(&r.def); - let i = r.index.as_u32(); - uwriteln!(self.gen.init, "realloc{i} = {def}"); - uwriteln!( - self.gen.init, - "assert(isinstance(realloc{i}, wasmtime.Func))" - ); - uwriteln!(self.gen.init, "self._realloc{i} = realloc{i}",); - } - GlobalInitializer::ExtractPostReturn(p) => { - let def = self.core_def(&p.def); - let i = p.index.as_u32(); - uwriteln!(self.gen.init, "post_return{i} = {def}"); - uwriteln!( - self.gen.init, - "assert(isinstance(post_return{i}, wasmtime.Func))" - ); - uwriteln!(self.gen.init, "self._post_return{i} = post_return{i}",); - } - - GlobalInitializer::LowerImport { index, import } => self.lower_import(*index, *import), - - GlobalInitializer::Resource(_) => {} - - GlobalInitializer::ExtractCallback(_) => unimplemented!(), - GlobalInitializer::ExtractTable(_) => unimplemented!(), - } - } - - fn gen_canon_resources(&mut self) { - self.gen_resource_handle_tables(); - for (tid, trampoline) in self.gen.resource_trampolines.iter() { - let tidx = tid.as_u32(); - match trampoline { - Trampoline::ResourceNew { ty: rid, .. } => { - let resource_id = rid.as_u32(); - let handle_add = &format!("_handle_add_{resource_id}"); - uwriteln!(self.gen.init, "def _resource_new_{resource_id}(rep):"); - self.gen.init.indent(); - uwriteln!(self.gen.init, "return {handle_add}((rep, True))"); - self.gen.init.dedent(); - uwriteln!( - self.gen.init, - "_resource_new_{resource_id}_ty = wasmtime.FuncType([wasmtime.ValType.i32()], [wasmtime.ValType.i32()])" - ); - uwriteln!( - self.gen.init, - "trampoline{tidx} = wasmtime.Func(store, _resource_new_{resource_id}_ty, _resource_new_{resource_id})" - ) - } - Trampoline::ResourceDrop { ty: rid, .. } => { - let resource_id = rid.as_u32(); - uwriteln!(self.gen.init, "def _resource_drop_{resource_id}(rep):"); - self.gen.init.indent(); - uwriteln!(self.gen.init, "_handle_remove_{resource_id}(rep)"); - self.gen.init.dedent(); - uwriteln!( - self.gen.init, - "_resource_drop_{resource_id}_ty = wasmtime.FuncType([wasmtime.ValType.i32()], [])" - ); - uwriteln!( - self.gen.init, - "trampoline{tidx} = wasmtime.Func(store, _resource_drop_{resource_id}_ty, _resource_drop_{resource_id})" - ); - } - Trampoline::ResourceRep { ty: rid, .. } => { - let resource_id = rid.as_u32(); - uwriteln!(self.gen.init, "def _resource_rep_{resource_id}(handle):"); - self.gen.init.indent(); - uwriteln!(self.gen.init, "return _handle_get_{resource_id}(handle)[0]"); - self.gen.init.dedent(); - uwriteln!( - self.gen.init, - "_resource_rep_{resource_id}_ty = wasmtime.FuncType([wasmtime.ValType.i32()], [wasmtime.ValType.i32()])" - ); - uwriteln!( - self.gen.init, - "trampoline{tidx} = wasmtime.Func(store, _resource_rep_{resource_id}_ty, _resource_rep_{resource_id})" - ); - } - _ => unreachable!(), - } - } - } - - fn gen_resource_handle_tables(&mut self) { - let table_indices = self - .gen - .resource_trampolines - .iter() - .map(|(_, trampoline)| match trampoline { - Trampoline::ResourceNew { ty, .. } - | Trampoline::ResourceRep { ty, .. } - | Trampoline::ResourceDrop { ty, .. } => *ty, - _ => unreachable!(), - }) - .collect::>(); - for table_idx in table_indices { - self.ensure_resource_table(&table_idx); - } - } - - fn ensure_resource_table(&mut self, rid: &TypeResourceTableIndex) { - let resource_id = rid.as_u32(); - if self.resource_tables_initialized[resource_id as usize] { - return; - } - self.resource_tables_initialized[resource_id as usize] = true; - self.gen.init.pyimport("typing", "Tuple"); - self.gen.init.pyimport("typing", "List"); - self.gen.init.pyimport("typing", "Optional"); - let table_name = &format!("_handle_table_{resource_id}"); - let free_list = &format!("_handle_table_free{resource_id}"); - uwriteln!( - self.gen.init, - "{table_name}: List[Optional[Tuple[int, bool]]] = []" - ); - uwriteln!(self.gen.init, "{free_list}: List[int] = []"); - let add_entry = &format!( - " - def _handle_add_{resource_id}(entry): - if {free_list}: - idx = {free_list}.pop() - {table_name}[idx] = entry - return idx - else: - {table_name}.append(entry) - return len({table_name}) - 1 - self._handle_add_{resource_id} = _handle_add_{resource_id}" - ); - self.gen.init.push_str(add_entry); - let remove_entry = &format!( - " - def _handle_remove_{resource_id}(i): - entry = {table_name}[i] - {table_name}[i] = None - {free_list}.append(i) - return entry - self._handle_remove_{resource_id} = _handle_remove_{resource_id} - " - ); - self.gen.init.push_str(remove_entry); - let get_entry = &format!( - " - def _handle_get_{resource_id}(i): - return {table_name}[i] - self._handle_get_{resource_id} = _handle_get_{resource_id} - " - ); - self.gen.init.push_str(get_entry); - } - - fn instantiate_static_module(&mut self, idx: StaticModuleIndex, args: &[CoreDef]) { - let i = self.instances.push(idx); - let core_file_name = self.gen.core_file_name(&self.name, idx.as_u32()); - self.gen.init.pyimport("pathlib", None); - self.gen.init.pyimport("importlib_resources", None); - - uwriteln!( - self.gen.init, - "file = importlib_resources.files() / ('{}')", - core_file_name, - ); - uwriteln!(self.gen.init, "if isinstance(file, pathlib.Path):"); - self.gen.init.indent(); - uwriteln!( - self.gen.init, - "module = wasmtime.Module.from_file(store.engine, file)" - ); - self.gen.init.dedent(); - uwriteln!(self.gen.init, "else:"); - self.gen.init.indent(); - uwriteln!( - self.gen.init, - "module = wasmtime.Module(store.engine, file.read_bytes())" - ); - self.gen.init.dedent(); - uwrite!( - self.gen.init, - "instance{} = wasmtime.Instance(store, module, [", - i.as_u32() - ); - if !args.is_empty() { - self.gen.init.push_str("\n"); - self.gen.init.indent(); - for arg in args { - let def = self.core_def(arg); - uwriteln!(self.gen.init, "{def},"); - } - self.gen.init.dedent(); - } - uwriteln!(self.gen.init, "]).exports(store)"); - } - - fn lower_import( - &mut self, - index: LoweredIndex, - import: RuntimeImportIndex, - // lower_ty: TypeFuncIndex, - // options: CanonicalOptions, - ) { - // 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]; - let item = &self.resolve.worlds[self.world].imports[import_index.as_u32() as usize]; - let (func, interface, import_name) = match item { - WorldItem::Function(f) => { - assert_eq!(path.len(), 0); - (f, None, BARE_FUNCTION_NAMESPACE.to_snake_case()) - } - WorldItem::Interface { id, .. } => { - assert_eq!(path.len(), 1); - let (import_name, _import_ty) = &self.component.import_types[*import_index]; - let import_name = import_name.replace(":", "."); - let import_name = match import_name.find("/") { - Some(pos) => import_name.split_at(pos + 1).1, - None => &import_name, - } - .to_snake_case() - .escape(); - ( - &self.resolve.interfaces[*id].functions[&path[0]], - Some(*id), - import_name, - ) - } - WorldItem::Type(_) => unimplemented!(), - }; - - let (trampoline_index, _ty, options) = self.gen.lowerings[index].clone(); - let trampoline_index = trampoline_index.as_u32(); - let index = index.as_u32(); - let callee = format!( - "import_object.{import_name}.{}", - func.name.to_snake_case().escape() - ); - - // Generate an inline function "closure" which will capture the - // `imports` argument provided to the constructor of this class and have - // the core wasm signature for this function. Using prior local - // variables the function here will perform all liftings/lowerings. - uwrite!( - self.gen.init, - "def lowering{index}_callee(caller: wasmtime.Caller" - ); - let sig = self.resolve.wasm_signature(AbiVariant::GuestImport, func); - let mut params = Vec::new(); - for (i, param_ty) in sig.params.iter().enumerate() { - self.gen.init.push_str(", "); - let param = format!("arg{i}"); - uwrite!(self.gen.init, "{param}: {}", wasm_ty_typing(*param_ty)); - params.push(param); - } - self.gen.init.push_str(") -> "); - match sig.results.len() { - 0 => self.gen.init.push_str("None"), - 1 => self.gen.init.push_str(wasm_ty_typing(sig.results[0])), - _ => unimplemented!(), - } - self.gen.init.push_str(":\n"); - self.gen.init.indent(); - - self.bindgen( - params, - callee, - options, - func, - AbiVariant::GuestImport, - "self", - interface, - true, - // None is being passed for the resource_map because - // support for importing resources isn't implemented yet. - None, - ); - self.gen.init.dedent(); - - // Use the `wasmtime` package's embedder methods of creating a wasm - // function to finish the construction here. - uwrite!(self.gen.init, "lowering{index}_ty = wasmtime.FuncType(["); - for param in sig.params.iter() { - self.gen.init.push_str(wasm_ty_ctor(*param)); - self.gen.init.push_str(", "); - } - self.gen.init.push_str("], ["); - for param in sig.results.iter() { - self.gen.init.push_str(wasm_ty_ctor(*param)); - self.gen.init.push_str(", "); - } - self.gen.init.push_str("])\n"); - uwriteln!( - self.gen.init, - "trampoline{trampoline_index} = wasmtime.Func(store, lowering{index}_ty, lowering{index}_callee, access_caller = True)" - ); - } - - fn bindgen( - &mut self, - params: Vec, - callee: String, - opts: OptionsIndex, - func: &Function, - abi: AbiVariant, - this: &str, - interface: Option, - at_root: bool, - resource_map: Option<&ResourceMap>, - ) { - let opts = &self.component.options[opts]; - // Technically it wouldn't be the hardest thing in the world to support - // other string encodings, but for now the code generator was originally - // written to support utf-8 so let's just leave it at that for now. In - // the future when it's easier to produce components with non-utf-8 this - // can be plumbed through to string lifting/lowering below. - assert_eq!(opts.string_encoding, StringEncoding::Utf8); - - let model = match opts.data_model { - CanonicalOptionsDataModel::LinearMemory(opts) => opts, - CanonicalOptionsDataModel::Gc { .. } => todo!(), - }; - let memory = match model.memory { - Some(idx) => Some(format!("{this}._core_memory{}", idx.as_u32())), - None => None, - }; - let realloc = match model.realloc { - Some(idx) => Some(format!("{this}._realloc{}", idx.as_u32())), - None => None, - }; - let post_return = match opts.post_return { - Some(idx) => Some(format!("{this}._post_return{}", idx.as_u32())), - None => None, - }; - - let mut locals = Ns::default(); - locals.insert("len").unwrap(); // python built-in - locals.insert("base").unwrap(); // may be used as loop var - locals.insert("i").unwrap(); // may be used as loop var - let mut gen = InterfaceGenerator { - // Generate source directly onto `init` - src: mem::take(&mut self.gen.init), - gen: self.gen, - resolve: self.resolve, - interface, - at_root, - }; - let mut f = FunctionBindgen { - locals, - payloads: Vec::new(), - block_storage: Vec::new(), - blocks: Vec::new(), - gen: &mut gen, - callee, - memory, - realloc, - params, - post_return, - resource_map, - }; - abi::call( - self.resolve, - abi, - match abi { - AbiVariant::GuestImport => LiftLower::LiftArgsLowerResults, - AbiVariant::GuestExport => LiftLower::LowerArgsLiftResults, - AbiVariant::GuestImportAsync => unimplemented!(), - AbiVariant::GuestExportAsync => unimplemented!(), - AbiVariant::GuestExportAsyncStackful => unimplemented!(), - }, - func, - &mut f, - false, - ); - - // Swap the printed source back into the destination of our `init`, and - // at this time `f.src` should be empty. - mem::swap(&mut f.gen.src, &mut f.gen.gen.init); - assert!(f.gen.src.is_empty()); - } - - fn core_def(&self, def: &CoreDef) -> String { - match def { - CoreDef::Export(e) => self.core_export(e), - CoreDef::Trampoline(i) => format!("trampoline{}", i.as_u32()), - CoreDef::InstanceFlags(_) => 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; - format!("instance{i}[\"{name}\"]") - } - - /// Extract the `LiftedFunction` exports to a format that's easier to - /// process for this generator. For now all lifted functions are either - /// "root" lifted functions or one-level-nested for an exported interface. - /// - /// As worlds shape up and more of a component's structure is expressible in - /// `*.wit` this method will likely need to change. - fn exports( - &mut self, - component: &'a Component, - ) -> (Vec>, BTreeMap<&'a str, Vec>>, ResourceMap) { - let mut toplevel = Vec::new(); - let mut nested = BTreeMap::new(); - let mut resource_map = ResourceMap::new(); - let world_exports_by_string = self.resolve.worlds[self.world] - .exports - .iter() - .map(|(k, v)| (self.resolve.name_world_key(k), v)) - .collect::>(); - for (name, export) in component.exports.raw_iter() { - let name = name.as_str(); - match &component.export_items[*export] { - Export::LiftedFunction { - ty: _, - func, - options, - } => { - let callee = self.gen_lift_callee(func); - let func = match world_exports_by_string[name] { - WorldItem::Function(f) => f, - WorldItem::Interface { .. } | WorldItem::Type(_) => unreachable!(), - }; - toplevel.push(Lift { - callee, - opts: *options, - func, - interface: None, - }); - } - - Export::Instance { exports, ty: _ } => { - let id = match world_exports_by_string[name] { - WorldItem::Interface { id, .. } => *id, - WorldItem::Function(_) | WorldItem::Type(_) => unreachable!(), - }; - let mut lifts = Vec::new(); - for (name, export) in exports.raw_iter() { - let export = &component.export_items[*export]; - let (callee, options, func_ty) = match export { - Export::LiftedFunction { func, options, ty } => (func, options, ty), - Export::Type(_) => continue, - Export::ModuleStatic { .. } - | Export::ModuleImport { .. } - | Export::Instance { .. } => unreachable!(), - }; - let callee = self.gen_lift_callee(callee); - let func = &self.resolve.interfaces[id].functions[name]; - self.create_resource_fn_map(func, func_ty, &mut resource_map); - lifts.push(Lift { - callee, - opts: *options, - func, - interface: Some(id), - }); - } - - let prev = nested.insert(name, lifts); - assert!(prev.is_none()); - } - - // ignore type exports for now - Export::Type(_) => {} - - // This can't be tested at this time so leave it unimplemented - Export::ModuleStatic { .. } | Export::ModuleImport { .. } => unimplemented!(), - } - } - (toplevel, nested, resource_map) - } - - fn create_resource_fn_map( - &mut self, - func: &Function, - ty_func_idx: &TypeFuncIndex, - resource_map: &mut ResourceMap, - ) { - let params_ty = &self.types[self.types[*ty_func_idx].params]; - let modeled_types = func.params.iter().map(|(_, ty)| ty); - for (modeled_ty, runtime_ty) in modeled_types.zip(params_ty.types.iter()) { - if let Type::Id(id) = modeled_ty { - self.connect_resource_types(*id, runtime_ty, resource_map); - } - } - } - - fn connect_resource_types( - &mut self, - id: TypeId, - iface_ty: &InterfaceType, - resource_map: &mut ResourceMap, - ) { - let kind = &self.resolve.types[id].kind; - match (kind, iface_ty) { - (TypeDefKind::Flags(_), InterfaceType::Flags(_)) - | (TypeDefKind::Enum(_), InterfaceType::Enum(_)) => {} - (TypeDefKind::Record(t1), InterfaceType::Record(t2)) => { - let t2 = &self.types[*t2]; - for (f1, f2) in t1.fields.iter().zip(t2.fields.iter()) { - if let Type::Id(id) = f1.ty { - self.connect_resource_types(id, &f2.ty, resource_map); - } - } - } - ( - TypeDefKind::Handle(Handle::Own(t1) | Handle::Borrow(t1)), - InterfaceType::Own(t2) | InterfaceType::Borrow(t2), - ) => { - self.connect_resources(*t1, *t2, resource_map); - } - (TypeDefKind::Tuple(t1), InterfaceType::Tuple(t2)) => { - let t2 = &self.types[*t2]; - for (f1, f2) in t1.types.iter().zip(t2.types.iter()) { - if let Type::Id(id) = f1 { - self.connect_resource_types(*id, f2, resource_map); - } - } - } - (TypeDefKind::Variant(t1), InterfaceType::Variant(t2)) => { - let t2 = &self.types[*t2]; - for (f1, f2) in t1.cases.iter().zip(t2.cases.iter()) { - if let Some(Type::Id(id)) = &f1.ty { - self.connect_resource_types(*id, f2.1.as_ref().unwrap(), resource_map); - } - } - } - (TypeDefKind::Option(t1), InterfaceType::Option(t2)) => { - let t2 = &self.types[*t2]; - if let Type::Id(id) = t1 { - self.connect_resource_types(*id, &t2.ty, resource_map); - } - } - (TypeDefKind::Result(t1), InterfaceType::Result(t2)) => { - let t2 = &self.types[*t2]; - if let Some(Type::Id(id)) = &t1.ok { - self.connect_resource_types(*id, &t2.ok.unwrap(), resource_map); - } - if let Some(Type::Id(id)) = &t1.err { - self.connect_resource_types(*id, &t2.err.unwrap(), resource_map); - } - } - (TypeDefKind::List(t1), InterfaceType::List(t2)) => { - let t2 = &self.types[*t2]; - if let Type::Id(id) = t1 { - self.connect_resource_types(*id, &t2.element, resource_map); - } - } - (TypeDefKind::Type(ty), _) => { - if let Type::Id(id) = ty { - self.connect_resource_types(*id, iface_ty, resource_map); - } - } - (_, _) => unreachable!(), - } - } - - fn connect_resources( - &mut self, - t: TypeId, - tid: TypeResourceTableIndex, - resource_map: &mut ResourceMap, - ) { - self.ensure_resource_table(&tid); - let entry = ResourceTable { - data: ResourceData::Host { tid }, - }; - resource_map.insert(t, entry); - } - - fn gen_lift_callee(&mut self, callee: &CoreDef) -> String { - // For each lifted function the callee `wasmtime.Func` is - // saved into a per-instance field which is then referenced - // as the callee when the relevant function is invoked. - let def = self.core_def(callee); - let callee = format!("lift_callee{}", self.lifts); - self.lifts += 1; - uwriteln!(self.gen.init, "{callee} = {def}"); - uwriteln!(self.gen.init, "assert(isinstance({callee}, wasmtime.Func))"); - uwriteln!(self.gen.init, "self.{callee} = {callee}"); - callee - } - - fn generate_lifts( - &mut self, - camel_component: &str, - ns: Option<&str>, - lifts: &[Lift<'_>], - resource_map: &ResourceMap, - ) { - let mut this = "self".to_string(); - - // If these exports are going into a non-default interface then a new - // `class` is generated in the corresponding file which will be - // constructed with the "root" class. Generate the class here, its one - // field of the root class, and then an associated constructor for the - // root class to have. Finally the root class grows a method here as - // well to return the nested instance. - // Any resources and corresponding methods will be generated in separate, individual - // classes from the class associated with the non-default interface. This provides a more - // idiomatic mapping to resources in Python. - let mut resource_lifts: IndexMap>> = IndexMap::default(); - for lift in lifts { - if let FunctionKind::Constructor(ty) - | FunctionKind::Method(ty) - | FunctionKind::Static(ty) = lift.func.kind - { - resource_lifts.entry(ty).or_default().push(lift); - } - } - if let Some(ns) = ns { - let src = self.gen.exports.get_mut(ns).unwrap(); - let camel = ns.to_upper_camel_case().escape(); - let snake = ns.to_snake_case().escape(); - uwriteln!(src, "class {camel}:"); - src.indent(); - src.typing_import("..", camel_component); - uwriteln!(src, "component: '{camel_component}'\n"); - uwriteln!( - src, - "def __init__(self, component: '{camel_component}') -> None:" - ); - src.indent(); - uwriteln!(src, "self.component = component"); - // Bind any resource types here in the init so they're available - // via `self.MyResourceName`. - for (ty, _) in resource_lifts.iter() { - let resource_name = self.resolve.types[*ty].name.as_ref().unwrap(); - let cls_name = resource_name.to_upper_camel_case().escape(); - let create_closure_name = - format!("_create_{}", resource_name.to_snake_case().escape()); - uwriteln!(src, "self.{cls_name} = {create_closure_name}(component)"); - } - src.dedent(); - - this.push_str(".component"); - - self.gen.init.pyimport(".exports", snake.as_str()); - uwriteln!(self.gen.init, "def {snake}(self) -> {snake}.{camel}:"); - self.gen.init.indent(); - uwriteln!(self.gen.init, "return {snake}.{camel}(self)"); - self.gen.init.dedent(); - - // Swap the two sources so the generation into `init` will go into - // the right place - mem::swap(&mut self.gen.init, src); - } - - for lift in lifts { - if let FunctionKind::Constructor(_) - | FunctionKind::Method(_) - | FunctionKind::Static(_) = lift.func.kind - { - // Function kinds on resources get generated in a separate class - // specific to the corresponding resource type so we skip them - // in the top level class for the interface. - continue; - } - // Go through some small gymnastics to print the function signature - // here. - let mut gen = InterfaceGenerator { - resolve: self.resolve, - src: mem::take(&mut self.gen.init), - gen: self.gen, - interface: lift.interface, - at_root: ns.is_none(), - }; - let params = gen.print_sig(lift.func, false); - let src = mem::take(&mut gen.src); - self.gen.init = src; - self.gen.init.push_str(":\n"); - - // Defer to `self.bindgen` for the body of the function. - self.gen.init.indent(); - self.gen.init.docstring(&lift.func.docs); - self.bindgen( - params, - format!("{this}.{}", lift.callee), - lift.opts, - lift.func, - AbiVariant::GuestExport, - &this, - lift.interface, - ns.is_none(), - Some(resource_map), - ); - self.gen.init.dedent(); - } - - for (resource_ty, lifts) in resource_lifts { - // - let resource_name = self.resolve.types[resource_ty].name.as_ref().unwrap(); - let cls_name = resource_name.to_upper_camel_case().escape(); - let create_closure_name = format!("_create_{}", resource_name.to_snake_case().escape()); - self.gen.init.dedent(); - self.gen.init.pyimport("typing", "Type"); - self.gen.init.push_str(&format!( - "\n\ndef {create_closure_name}(component: 'Root') -> Type[{cls_name}]:\n" - )); - self.gen.init.indent(); - self.gen.init.push_str(&format!("class _{cls_name}:\n")); - self.gen.init.indent(); - for lift in lifts { - let mut gen = InterfaceGenerator { - resolve: self.resolve, - src: mem::take(&mut self.gen.init), - gen: self.gen, - interface: lift.interface, - at_root: ns.is_none(), - }; - let params = gen.print_sig(lift.func, false); - let src = mem::take(&mut gen.src); - self.gen.init = src; - self.gen.init.push_str(":\n"); - - // Defer to `self.bindgen` for the body of the function. - self.gen.init.indent(); - self.gen.init.docstring(&lift.func.docs); - if let FunctionKind::Constructor(_) = lift.func.kind { - // Bind the component from the closure so we can reference it other - // methods for this resource. - self.gen.init.push_str("self.component = component\n"); - } - self.bindgen( - params, - format!("self.component.{}", lift.callee), - lift.opts, - lift.func, - AbiVariant::GuestExport, - &this, - lift.interface, - ns.is_none(), - Some(resource_map), - ); - self.gen.init.dedent(); - } - self.gen.init.dedent(); - self.gen.init.push_str(&format!("return _{cls_name}")); - } - - // Undo the swap done above. - if let Some(ns) = ns { - mem::swap(&mut self.gen.init, self.gen.exports.get_mut(ns).unwrap()); - } - } -} - -struct InterfaceGenerator<'a> { - src: Source, - gen: &'a mut WasmtimePy, - resolve: &'a Resolve, - interface: Option, - at_root: bool, -} - -impl InterfaceGenerator<'_> { - fn import_result_type(&mut self) { - self.gen.print_result(); - self.import_shared_type("Result"); - } - - fn import_some_type(&mut self) { - self.gen.print_some(); - self.import_shared_type("Some"); - } - - fn print_ty(&mut self, ty: &Type, forward_ref: bool) { - match ty { - Type::Bool => self.src.push_str("bool"), - Type::U8 - | Type::S8 - | Type::U16 - | Type::S16 - | Type::U32 - | Type::S32 - | Type::U64 - | Type::S64 => self.src.push_str("int"), - Type::F32 | Type::F64 => self.src.push_str("float"), - Type::Char => self.src.push_str("str"), - Type::String => self.src.push_str("str"), - Type::ErrorContext => unimplemented!(), - Type::Id(id) => { - let ty = &self.resolve.types[*id]; - if let Some(name) = &ty.name { - let owner = match ty.owner { - TypeOwner::Interface(id) => id, - _ => unreachable!(), - }; - if Some(owner) == self.interface { - let name = self.name_of(name); - self.src.push_str(&name); - } else { - let (module, iface) = match self.gen.imported_interfaces.get(&owner) { - Some(name) => ("imports", name), - None => ( - "exports", - self.gen.exported_interfaces.get(&owner).unwrap_or(name), - ), - }; - let module = if self.at_root { - format!(".{module}") - } else { - format!("..{module}") - }; - let iface = iface.to_snake_case().escape(); - self.src.pyimport(&module, iface.as_str()); - uwrite!( - self.src, - "{iface}.{name}", - name = name.to_upper_camel_case().escape() - ) - } - return; - } - match &ty.kind { - TypeDefKind::Type(t) => self.print_ty(t, forward_ref), - TypeDefKind::Tuple(t) => self.print_tuple(t), - TypeDefKind::Record(_) - | TypeDefKind::Flags(_) - | TypeDefKind::Enum(_) - | TypeDefKind::Variant(_) => { - unreachable!() - } - TypeDefKind::Option(t) => { - let nesting = is_option(self.resolve, *t); - if nesting { - self.import_some_type(); - } - - self.src.pyimport("typing", "Optional"); - self.src.push_str("Optional["); - if nesting { - self.src.push_str("Some["); - } - self.print_ty(t, true); - if nesting { - self.src.push_str("]"); - } - self.src.push_str("]"); - } - TypeDefKind::Result(r) => { - self.import_result_type(); - self.src.push_str("Result["); - self.print_optional_ty(r.ok.as_ref(), true); - self.src.push_str(", "); - self.print_optional_ty(r.err.as_ref(), true); - self.src.push_str("]"); - } - TypeDefKind::List(t) => self.print_list(t), - TypeDefKind::Future(_) => unimplemented!(), - TypeDefKind::Stream(_) => unimplemented!(), - TypeDefKind::Resource => unimplemented!(), - TypeDefKind::Handle(Handle::Own(t) | Handle::Borrow(t)) => { - let ty = &self.resolve.types[*t]; - // Assuming that handles always refer to a resource type. - if let Some(name) = &ty.name { - self.src.push_str(&name.to_upper_camel_case().escape()); - } - } - TypeDefKind::FixedSizeList(..) => unimplemented!(), - TypeDefKind::Unknown => unreachable!(), - } - } - } - } - - fn print_optional_ty(&mut self, ty: Option<&Type>, forward_ref: bool) { - match ty { - Some(ty) => self.print_ty(ty, forward_ref), - None => self.src.push_str("None"), - } - } - - fn print_tuple(&mut self, tuple: &Tuple) { - if tuple.types.is_empty() { - return self.src.push_str("None"); - } - self.src.pyimport("typing", "Tuple"); - self.src.push_str("Tuple["); - for (i, t) in tuple.types.iter().enumerate() { - if i > 0 { - self.src.push_str(", "); - } - self.print_ty(t, true); - } - self.src.push_str("]"); - } - - fn print_list(&mut self, element: &Type) { - match element { - Type::U8 => self.src.push_str("bytes"), - t => { - self.src.pyimport("typing", "List"); - self.src.push_str("List["); - self.print_ty(t, true); - self.src.push_str("]"); - } - } - } - - fn print_sig(&mut self, func: &Function, in_import: bool) -> Vec { - self.src.push_str("def "); - let func_name = func.item_name().to_snake_case().escape(); - let py_name = match func.kind { - FunctionKind::Constructor(_) => "__init__", - _ => &func_name, - }; - self.src.push_str(py_name); - if in_import { - self.src.push_str("(self"); - } else { - self.src.pyimport("wasmtime", None); - self.src.push_str("(self, caller: wasmtime.Store"); - } - let mut params = Vec::new(); - let func_params = match func.kind { - FunctionKind::Method(_) => { - // Methods are generated as a separate class where the `self` is already bound - // to the resource type, so we don't want to add in the signature. - // However, we still want this as part of the params that are used in - // FunctionBindgen when generating the lowering to core wasm. - let self_param = &func.params[0].0; - params.push(self_param.to_snake_case().escape()); - &func.params[1..] - } - _ => &func.params, - }; - for (param, ty) in func_params.iter() { - self.src.push_str(", "); - self.src.push_str(¶m.to_snake_case().escape()); - params.push(param.to_snake_case().escape()); - self.src.push_str(": "); - self.print_ty(ty, true); - } - self.src.push_str(") -> "); - if func.result.is_some() { - match func.kind { - // The return type of `__init__` in Python is always `None`. - FunctionKind::Constructor(_) => self.src.push_str("None"), - _ => self.print_ty(&func.result.unwrap(), true), - } - } else { - self.src.push_str("None") - } - params - } - - fn types(&mut self, interface: InterfaceId) { - for (name, id) in self.resolve.interfaces[interface].types.iter() { - let id = *id; - let ty = &self.resolve.types[id]; - match &ty.kind { - TypeDefKind::Record(record) => self.type_record(id, name, record, &ty.docs), - TypeDefKind::Flags(flags) => self.type_flags(id, name, flags, &ty.docs), - TypeDefKind::Tuple(tuple) => self.type_tuple(id, name, tuple, &ty.docs), - TypeDefKind::Enum(enum_) => self.type_enum(id, name, enum_, &ty.docs), - TypeDefKind::Variant(variant) => self.type_variant(id, name, variant, &ty.docs), - TypeDefKind::Option(t) => self.type_option(id, name, t, &ty.docs), - TypeDefKind::Result(r) => self.type_result(id, name, r, &ty.docs), - TypeDefKind::List(t) => self.type_list(id, name, t, &ty.docs), - TypeDefKind::Type(t) => self.type_alias(id, name, t, &ty.docs), - TypeDefKind::Future(_) => todo!("generate for future"), - TypeDefKind::Stream(_) => todo!("generate for stream"), - TypeDefKind::Resource => self.type_resource(ty, id, name, interface), - TypeDefKind::Handle(_) => unimplemented!(), - TypeDefKind::FixedSizeList(..) => unimplemented!(), - TypeDefKind::Unknown => unreachable!(), - } - } - } - - fn type_record(&mut self, _id: TypeId, name: &str, record: &Record, docs: &Docs) { - self.src.pyimport("dataclasses", "dataclass"); - self.src.push_str("@dataclass\n"); - self.src - .push_str(&format!("class {}:\n", name.to_upper_camel_case().escape())); - self.src.indent(); - self.src.docstring(docs); - for field in record.fields.iter() { - self.src.comment(&field.docs); - let field_name = field.name.to_snake_case().escape(); - self.src.push_str(&format!("{field_name}: ")); - self.print_ty(&field.ty, true); - self.src.push_str("\n"); - } - if record.fields.is_empty() { - self.src.push_str("pass\n"); - } - self.src.dedent(); - self.src.push_str("\n"); - } - - fn type_tuple(&mut self, _id: TypeId, name: &str, tuple: &Tuple, docs: &Docs) { - self.src.comment(docs); - self.src - .push_str(&format!("{} = ", name.to_upper_camel_case().escape())); - self.print_tuple(tuple); - self.src.push_str("\n"); - } - - fn type_flags(&mut self, _id: TypeId, name: &str, flags: &Flags, docs: &Docs) { - self.src.pyimport("enum", "Flag"); - self.src.pyimport("enum", "auto"); - self.src.push_str(&format!( - "class {}(Flag):\n", - name.to_upper_camel_case().escape() - )); - self.src.indent(); - self.src.docstring(docs); - for flag in flags.flags.iter() { - let flag_name = flag.name.to_shouty_snake_case(); - self.src.comment(&flag.docs); - self.src.push_str(&format!("{flag_name} = auto()\n")); - } - if flags.flags.is_empty() { - self.src.push_str("pass\n"); - } - self.src.dedent(); - self.src.push_str("\n"); - } - - fn type_variant(&mut self, _id: TypeId, name: &str, variant: &Variant, docs: &Docs) { - self.src.pyimport("dataclasses", "dataclass"); - let mut cases = Vec::new(); - for case in variant.cases.iter() { - self.src.docstring(&case.docs); - self.src.push_str("@dataclass\n"); - let case_name = format!( - "{}{}", - name.to_upper_camel_case().escape(), - case.name.to_upper_camel_case().escape() - ); - self.src.push_str(&format!("class {case_name}:\n")); - self.src.indent(); - match &case.ty { - Some(ty) => { - self.src.push_str("value: "); - self.print_ty(ty, true); - } - None => self.src.push_str("pass"), - } - self.src.push_str("\n"); - self.src.dedent(); - self.src.push_str("\n"); - cases.push(case_name); - } - - self.src.pyimport("typing", "Union"); - self.src.comment(docs); - self.src.push_str(&format!( - "{} = Union[{}]\n", - name.to_upper_camel_case().escape(), - cases.join(", "), - )); - self.src.push_str("\n"); - } - - fn type_option(&mut self, _id: TypeId, name: &str, payload: &Type, docs: &Docs) { - let nesting = is_option(self.resolve, *payload); - if nesting { - self.import_some_type(); - } - - self.src.pyimport("typing", "Optional"); - self.src.comment(docs); - self.src.push_str(&name.to_upper_camel_case().escape()); - self.src.push_str(" = Optional["); - if nesting { - self.src.push_str("Some["); - } - self.print_ty(payload, true); - if nesting { - self.src.push_str("]"); - } - self.src.push_str("]\n\n"); - } - - fn type_result(&mut self, _id: TypeId, name: &str, result: &Result_, docs: &Docs) { - self.import_result_type(); - - self.src.comment(docs); - self.src.push_str(&format!( - "{} = Result[", - name.to_upper_camel_case().escape() - )); - self.print_optional_ty(result.ok.as_ref(), true); - self.src.push_str(", "); - self.print_optional_ty(result.err.as_ref(), true); - self.src.push_str("]\n\n"); - } - - fn type_enum(&mut self, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { - self.src.pyimport("enum", "Enum"); - self.src.push_str(&format!( - "class {}(Enum):\n", - name.to_upper_camel_case().escape() - )); - self.src.indent(); - self.src.docstring(docs); - for (i, case) in enum_.cases.iter().enumerate() { - self.src.comment(&case.docs); - - // TODO this handling of digits should be more general and - // shouldn't be here just to fix the one case in wasi where an - // enum variant is "2big" and doesn't generate valid Python. We - // should probably apply this to all generated Python - // identifiers. - let mut name = case.name.to_shouty_snake_case(); - if name.chars().next().unwrap().is_digit(10) { - name = format!("_{}", name); - } - self.src.push_str(&format!("{} = {}\n", name, i)); - } - self.src.dedent(); - self.src.push_str("\n"); - } - - fn type_alias(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - self.src.comment(docs); - self.src - .push_str(&format!("{} = ", name.to_upper_camel_case().escape())); - self.print_ty(ty, false); - self.src.push_str("\n"); - } - - fn type_list(&mut self, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { - self.src.comment(docs); - self.src - .push_str(&format!("{} = ", name.to_upper_camel_case().escape())); - self.print_list(ty); - self.src.push_str("\n"); - } - - fn type_resource(&mut self, _ty: &TypeDef, id: TypeId, name: &str, interface: InterfaceId) { - self.src.pyimport("typing", "Protocol"); - let cls_name = name.to_upper_camel_case().escape(); - let methods = self.resolve.interfaces[interface] - .functions - .iter() - .filter_map(|(_, func)| match func.kind { - FunctionKind::Method(t) - | FunctionKind::Static(t) - | FunctionKind::Constructor(t) => { - if t == id { - Some(func) - } else { - None - } - } - _ => None, - }) - .collect::>(); - self.src - .push_str(&format!("class {}(Protocol):\n", cls_name)); - self.src.indent(); - for method in &methods { - self.print_sig(method, false); - self.src.push_str(": ...\n"); - } - if methods.is_empty() { - self.src.push_str("pass\n\n"); - } else { - self.src.push_str("\n\n"); - } - self.src.dedent(); - } - - fn import_shared_type(&mut self, ty: &str) { - let path = if self.at_root { ".types" } else { "..types" }; - self.src.pyimport(path, ty); - } - - fn name_of(&mut self, ty: &str) -> String { - let ty = ty.to_upper_camel_case().escape(); - if !self.at_root { - return ty; - } - let id = self.interface.unwrap(); - let (module, iface) = match self.gen.imported_interfaces.get(&id) { - Some(name) => (".imports", name), - None => (".exports", self.gen.exported_interfaces.get(&id).unwrap()), - }; - let iface = iface.to_snake_case().escape(); - self.src.pyimport(&module, iface.as_str()); - format!("{iface}.{ty}") - } - - fn fqn_name(&mut self, name: &str, type_id: &TypeId) -> String { - let ty = &self.resolve.types[*type_id]; - if let Some(name) = &ty.name { - let owner = match ty.owner { - TypeOwner::Interface(id) => id, - _ => unreachable!(), - }; - if Some(owner) != self.interface { - let iface = match self.gen.imported_interfaces.get(&owner) { - Some(name) => name, - None => &self.gen.exported_interfaces[&owner], - }; - return format!( - "{}.{}", - iface.to_snake_case().escape(), - name.to_upper_camel_case().escape() - ); - } - }; - self.name_of(name).to_string() - } -} - -struct FunctionBindgen<'a, 'b> { - gen: &'a mut InterfaceGenerator<'b>, - locals: Ns, - block_storage: Vec, - blocks: Vec<(String, Vec)>, - params: Vec, - payloads: Vec, - - memory: Option, - realloc: Option, - post_return: Option, - callee: String, - resource_map: Option<&'a ResourceMap>, -} - -impl FunctionBindgen<'_, '_> { - fn clamp(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) - where - T: std::fmt::Display, - { - let clamp = self.print_clamp(); - results.push(format!("{clamp}({}, {min}, {max})", operands[0])); - } - - fn load( - &mut self, - ty: &str, - offset: ArchitectureSize, - operands: &[String], - results: &mut Vec, - ) { - let offset = offset.size_wasm32(); - let load = self.print_load(); - let memory = self.memory.as_ref().unwrap(); - let tmp = self.locals.tmp("load"); - self.gen.src.pyimport("ctypes", None); - uwriteln!( - self.gen.src, - "{tmp} = {load}(ctypes.{ty}, {memory}, caller, {}, {offset})", - operands[0], - ); - results.push(tmp); - } - - fn store(&mut self, ty: &str, offset: ArchitectureSize, operands: &[String]) { - let store = self.print_store(); - let memory = self.memory.as_ref().unwrap(); - self.gen.src.pyimport("ctypes", None); - let offset = offset.size_wasm32(); - uwriteln!( - self.gen.src, - "{store}(ctypes.{ty}, {memory}, caller, {}, {offset}, {})", - operands[1], - operands[0] - ); - } - - fn print_ty(&mut self, ty: &Type) { - self.gen.print_ty(ty, false) - } - - fn print_list(&mut self, element: &Type) { - self.gen.print_list(element) - } - - fn print_intrinsic( - &mut self, - name: &'static str, - gen: impl FnOnce(&str, &mut Source), - ) -> &'static str { - let path = if self.gen.at_root { - ".intrinsics" - } else { - "..intrinsics" - }; - self.gen.src.pyimport(path, name); - if !self.gen.gen.all_intrinsics.insert(name) { - return name; - } - gen(name, &mut self.gen.gen.intrinsics); - return name; - } - - fn print_validate_guest_char(&mut self) -> &'static str { - self.print_intrinsic("_validate_guest_char", |name, src| { - uwriteln!( - src, - " - def {name}(i: int) -> str: - if i > 0x10ffff or (i >= 0xd800 and i <= 0xdfff): - raise TypeError('not a valid char') - return chr(i) - ", - ); - }) - } - - fn print_i32_to_f32(&mut self) -> &'static str { - self.print_i32_to_f32_cvts(); - self.print_intrinsic("_i32_to_f32", |name, src| { - uwriteln!( - src, - " - def {name}(i: int) -> float: - _i32_to_f32_i32[0] = i - return _i32_to_f32_f32[0] - ", - ); - }) - } - - fn print_f32_to_i32(&mut self) -> &'static str { - self.print_i32_to_f32_cvts(); - self.print_intrinsic("_f32_to_i32", |name, src| { - uwriteln!( - src, - " - def {name}(i: float) -> int: - _i32_to_f32_f32[0] = i - return _i32_to_f32_i32[0] - ", - ); - }) - } - - fn print_i32_to_f32_cvts(&mut self) { - if !self.gen.gen.all_intrinsics.insert("i32_to_f32_cvts") { - return; - } - self.gen.gen.intrinsics.pyimport("ctypes", None); - self.gen - .gen - .intrinsics - .push_str("_i32_to_f32_i32 = ctypes.pointer(ctypes.c_int32(0))\n"); - self.gen.gen.intrinsics.push_str( - "_i32_to_f32_f32 = ctypes.cast(_i32_to_f32_i32, ctypes.POINTER(ctypes.c_float))\n", - ); - } - - fn print_i64_to_f64(&mut self) -> &'static str { - self.print_i64_to_f64_cvts(); - self.print_intrinsic("_i64_to_f64", |name, src| { - uwriteln!( - src, - " - def {name}(i: int) -> float: - _i64_to_f64_i64[0] = i - return _i64_to_f64_f64[0] - ", - ); - }) - } - - fn print_f64_to_i64(&mut self) -> &'static str { - self.print_i64_to_f64_cvts(); - self.print_intrinsic("_f64_to_i64", |name, src| { - uwriteln!( - src, - " - def {name}(i: float) -> int: - _i64_to_f64_f64[0] = i - return _i64_to_f64_i64[0] - ", - ); - }) - } - - fn print_i64_to_f64_cvts(&mut self) { - if !self.gen.gen.all_intrinsics.insert("i64_to_f64_cvts") { - return; - } - self.gen.gen.intrinsics.pyimport("ctypes", None); - self.gen - .gen - .intrinsics - .push_str("_i64_to_f64_i64 = ctypes.pointer(ctypes.c_int64(0))\n"); - self.gen.gen.intrinsics.push_str( - "_i64_to_f64_f64 = ctypes.cast(_i64_to_f64_i64, ctypes.POINTER(ctypes.c_double))\n", - ); - } - - fn print_clamp(&mut self) -> &'static str { - self.print_intrinsic("_clamp", |name, src| { - uwriteln!( - src, - " - def {name}(i: int, min: int, max: int) -> int: - if i < min or i > max: - raise OverflowError(f'must be between {{min}} and {{max}}') - return i - ", - ); - }) - } - - fn print_load(&mut self) -> &'static str { - self.print_intrinsic("_load", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Any"); - uwriteln!( - src, - " - def {name}(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int) -> Any: - ptr = (base & 0xffffffff) + offset - if ptr + ctypes.sizeof(ty) > mem.data_len(store): - raise IndexError('out-of-bounds store') - raw_base = mem.data_ptr(store) - c_ptr = ctypes.POINTER(ty)( - ty.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) - return c_ptr[0] - ", - ); - }) - } - - fn print_store(&mut self) -> &'static str { - self.print_intrinsic("_store", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Any"); - uwriteln!( - src, - " - def {name}(ty: Any, mem: wasmtime.Memory, store: wasmtime.Storelike, base: int, offset: int, val: Any) -> None: - ptr = (base & 0xffffffff) + offset - if ptr + ctypes.sizeof(ty) > mem.data_len(store): - raise IndexError('out-of-bounds store') - raw_base = mem.data_ptr(store) - c_ptr = ctypes.POINTER(ty)( - ty.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) - c_ptr[0] = val - ", - ); - }) - } - - fn print_decode_utf8(&mut self) -> &'static str { - self.print_intrinsic("_decode_utf8", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Tuple"); - uwriteln!( - src, - " - def {name}(mem: wasmtime.Memory, store: wasmtime.Storelike, ptr: int, len: int) -> str: - ptr = ptr & 0xffffffff - len = len & 0xffffffff - if ptr + len > mem.data_len(store): - raise IndexError('string out of bounds') - base = mem.data_ptr(store) - base = ctypes.POINTER(ctypes.c_ubyte)( - ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr) - ) - return ctypes.string_at(base, len).decode('utf-8') - ", - ); - }) - } - - fn print_encode_utf8(&mut self) -> &'static str { - self.print_intrinsic("_encode_utf8", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Tuple"); - uwriteln!( - src, - " - def {name}(val: str, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]: - bytes = val.encode('utf8') - ptr = realloc(store, 0, 0, 1, len(bytes)) - assert(isinstance(ptr, int)) - ptr = ptr & 0xffffffff - if ptr + len(bytes) > mem.data_len(store): - raise IndexError('string out of bounds') - base = mem.data_ptr(store) - base = ctypes.POINTER(ctypes.c_ubyte)( - ctypes.c_ubyte.from_address(ctypes.addressof(base.contents) + ptr) - ) - ctypes.memmove(base, bytes, len(bytes)) - return (ptr, len(bytes)) - ", - ); - }) - } - - fn print_canon_lift(&mut self) -> &'static str { - self.print_intrinsic("_list_canon_lift", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "List"); - src.pyimport("typing", "Any"); - // TODO: this is doing a native-endian read, not a little-endian - // read - uwriteln!( - src, - " - def {name}(ptr: int, len: int, size: int, ty: Any, mem: wasmtime.Memory ,store: wasmtime.Storelike) -> Any: - ptr = ptr & 0xffffffff - len = len & 0xffffffff - if ptr + len * size > mem.data_len(store): - raise IndexError('list out of bounds') - raw_base = mem.data_ptr(store) - base = ctypes.POINTER(ty)( - ty.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) - if ty == ctypes.c_uint8: - return ctypes.string_at(base, len) - return base[:len] - ", - ); - }) - } - - fn print_canon_lower(&mut self) -> &'static str { - self.print_intrinsic("_list_canon_lower", |name, src| { - src.pyimport("wasmtime", None); - src.pyimport("ctypes", None); - src.pyimport("typing", "Tuple"); - src.pyimport("typing", "List"); - src.pyimport("typing", "Any"); - // TODO: is there a faster way to memcpy other than iterating over - // the input list? - // TODO: this is doing a native-endian write, not a little-endian - // write - uwriteln!( - src, - " - def {name}(list: Any, ty: Any, size: int, align: int, realloc: wasmtime.Func, mem: wasmtime.Memory, store: wasmtime.Storelike) -> Tuple[int, int]: - total_size = size * len(list) - ptr = realloc(store, 0, 0, align, total_size) - assert(isinstance(ptr, int)) - ptr = ptr & 0xffffffff - if ptr + total_size > mem.data_len(store): - raise IndexError('list realloc return of bounds') - raw_base = mem.data_ptr(store) - base = ctypes.POINTER(ty)( - ty.from_address(ctypes.addressof(raw_base.contents) + ptr) - ) - for i, val in enumerate(list): - base[i] = val - return (ptr, len(list)) - ", - ); - }) - } - - fn name_of(&mut self, ty: &str) -> String { - self.gen.name_of(ty) - } - - fn bitcast(&mut self, cast: &Bitcast, op: &str) -> String { - match cast { - Bitcast::I32ToF32 => { - let cvt = self.print_i32_to_f32(); - format!("{cvt}({})", op) - } - Bitcast::F32ToI32 => { - let cvt = self.print_f32_to_i32(); - format!("{cvt}({})", op) - } - Bitcast::I64ToF64 => { - let cvt = self.print_i64_to_f64(); - format!("{cvt}({})", op) - } - Bitcast::F64ToI64 => { - let cvt = self.print_f64_to_i64(); - format!("{cvt}({})", op) - } - Bitcast::I64ToF32 => { - let cvt = self.print_i32_to_f32(); - format!("{cvt}(({}) & 0xffffffff)", op) - } - Bitcast::F32ToI64 => { - let cvt = self.print_f32_to_i32(); - format!("{cvt}({})", op) - } - Bitcast::I32ToI64 - | Bitcast::I64ToI32 - | Bitcast::None - | Bitcast::P64ToI64 - | Bitcast::I64ToP64 - | Bitcast::P64ToP - | Bitcast::PToP64 - | Bitcast::I32ToP - | Bitcast::PToI32 - | Bitcast::PToL - | Bitcast::LToP - | Bitcast::I32ToL - | Bitcast::LToI32 - | Bitcast::I64ToL - | Bitcast::LToI64 => op.to_string(), - Bitcast::Sequence(seq) => { - let [a, b] = &**seq; - let a = self.bitcast(a, op); - self.bitcast(b, &a) - } - } - } -} - -impl Bindgen for FunctionBindgen<'_, '_> { - type Operand = String; - - fn sizes(&self) -> &SizeAlign { - &self.gen.gen.sizes - } - - fn push_block(&mut self) { - self.block_storage.push(self.gen.src.take_body()); - } - - fn finish_block(&mut self, operands: &mut Vec) { - let to_restore = self.block_storage.pop().unwrap(); - let src = self.gen.src.replace_body(to_restore); - self.blocks.push((src, mem::take(operands))); - } - - fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) -> String { - unimplemented!() - } - - fn is_list_canonical(&self, resolve: &Resolve, ty: &Type) -> bool { - 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::I32 - | WasmType::I64 - | WasmType::Length - | WasmType::Pointer - | WasmType::PointerOrI64 => results.push("0".to_string()), - WasmType::F32 | WasmType::F64 => results.push("0.0".to_string()), - } - } - } - - // The representation of i32 in Python 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(results, operands, u8::MIN, u8::MAX), - Instruction::S8FromI32 => self.clamp(results, operands, i8::MIN, i8::MAX), - Instruction::U16FromI32 => self.clamp(results, operands, u16::MIN, u16::MAX), - Instruction::S16FromI32 => self.clamp(results, operands, i16::MIN, i16::MAX), - // Ensure the bits of the number are treated as unsigned. - Instruction::U32FromI32 => { - results.push(format!("{} & 0xffffffff", 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!("{} & 0xffffffffffffffff", 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 => self.clamp(results, operands, u8::MIN, u8::MAX), - Instruction::I32FromS8 => self.clamp(results, operands, i8::MIN, i8::MAX), - Instruction::I32FromU16 => self.clamp(results, operands, u16::MIN, u16::MAX), - Instruction::I32FromS16 => self.clamp(results, operands, i16::MIN, i16::MAX), - // TODO: need to do something to get this to be represented as signed? - Instruction::I32FromU32 => { - self.clamp(results, operands, u32::MIN, u32::MAX); - } - Instruction::I32FromS32 => self.clamp(results, operands, i32::MIN, i32::MAX), - // TODO: need to do something to get this to be represented as signed? - Instruction::I64FromU64 => self.clamp(results, operands, u64::MIN, u64::MAX), - Instruction::I64FromS64 => self.clamp(results, operands, i64::MIN, i64::MAX), - - // Python uses `float` for f32/f64, so everything is equivalent - // here. - Instruction::CoreF32FromF32 - | Instruction::CoreF64FromF64 - | Instruction::F32FromCoreF32 - | Instruction::F64FromCoreF64 => results.push(operands.pop().unwrap()), - - // Validate that i32 values coming from wasm are indeed valid code - // points. - Instruction::CharFromI32 => { - let validate = self.print_validate_guest_char(); - results.push(format!("{validate}({})", operands[0])); - } - - Instruction::I32FromChar => { - results.push(format!("ord({})", operands[0])); - } - - Instruction::Bitcasts { casts } => { - for (cast, op) in casts.iter().zip(operands) { - results.push(self.bitcast(cast, op)); - } - } - - Instruction::BoolFromI32 => { - let op = self.locals.tmp("operand"); - let ret = self.locals.tmp("boolean"); - - uwriteln!(self.gen.src, "{op} = {}", operands[0]); - uwriteln!(self.gen.src, "if {op} == 0:"); - self.gen.src.indent(); - uwriteln!(self.gen.src, "{ret} = False"); - self.gen.src.dedent(); - uwriteln!(self.gen.src, "elif {op} == 1:"); - self.gen.src.indent(); - uwriteln!(self.gen.src, "{ret} = True"); - self.gen.src.dedent(); - uwriteln!(self.gen.src, "else:"); - self.gen.src.indent(); - uwriteln!( - self.gen.src, - "raise TypeError(\"invalid variant discriminant for bool\")" - ); - self.gen.src.dedent(); - results.push(ret); - } - Instruction::I32FromBool => { - results.push(format!("int({})", operands[0])); - } - - Instruction::RecordLower { record, .. } => { - if record.fields.is_empty() { - return; - } - let tmp = self.locals.tmp("record"); - uwriteln!(self.gen.src, "{tmp} = {}", operands[0]); - for field in record.fields.iter() { - let name = self.locals.tmp("field"); - uwriteln!( - self.gen.src, - "{name} = {tmp}.{}", - field.name.to_snake_case().escape(), - ); - results.push(name); - } - } - - Instruction::RecordLift { name, ty, .. } => { - let fqn_name = &self.gen.fqn_name(name, ty); - results.push(format!("{}({})", fqn_name, operands.join(", "))); - } - Instruction::TupleLower { tuple, .. } => { - if tuple.types.is_empty() { - return; - } - self.gen.src.push_str("("); - for _ in 0..tuple.types.len() { - let name = self.locals.tmp("tuplei"); - uwrite!(self.gen.src, "{name},"); - results.push(name); - } - uwriteln!(self.gen.src, ") = {}", operands[0]); - } - Instruction::TupleLift { .. } => { - if operands.is_empty() { - results.push("None".to_string()); - } else { - results.push(format!("({},)", operands.join(", "))); - } - } - Instruction::FlagsLift { name, .. } => { - let operand = match operands.len() { - 1 => operands[0].clone(), - _ => { - let tmp = self.locals.tmp("bits"); - uwriteln!(self.gen.src, "{tmp} = 0"); - for (i, op) in operands.iter().enumerate() { - let i = 32 * i; - uwriteln!(self.gen.src, "{tmp} |= ({op} & 0xffffffff) << {i}\n"); - } - tmp - } - }; - results.push(format!("{}({operand})", self.name_of(name))); - } - Instruction::FlagsLower { flags, .. } => match flags.repr().count() { - 1 => results.push(format!("({}).value", operands[0])), - n => { - let tmp = self.locals.tmp("bits"); - self.gen - .src - .push_str(&format!("{tmp} = ({}).value\n", operands[0])); - for i in 0..n { - let i = 32 * i; - results.push(format!("({tmp} >> {i}) & 0xffffffff")); - } - } - }, - - Instruction::VariantPayloadName => { - let name = self.locals.tmp("payload"); - results.push(name.clone()); - self.payloads.push(name); - } - - Instruction::VariantLower { - variant, - results: result_types, - name, - ty, - .. - } => { - let blocks = self - .blocks - .drain(self.blocks.len() - variant.cases.len()..) - .collect::>(); - let payloads = self - .payloads - .drain(self.payloads.len() - variant.cases.len()..) - .collect::>(); - - for _ in 0..result_types.len() { - results.push(self.locals.tmp("variant")); - } - - for (i, ((case, (block, block_results)), payload)) in - variant.cases.iter().zip(blocks).zip(payloads).enumerate() - { - if i == 0 { - self.gen.src.push_str("if "); - } else { - self.gen.src.push_str("elif "); - } - uwrite!(self.gen.src, "isinstance({}, ", operands[0]); - self.print_ty(&Type::Id(*ty)); - uwriteln!( - self.gen.src, - "{}):", - case.name.to_upper_camel_case().escape() - ); - - self.gen.src.indent(); - if case.ty.is_some() { - uwriteln!(self.gen.src, "{payload} = {}.value", operands[0]); - } - self.gen.src.push_str(&block); - - for (i, result) in block_results.iter().enumerate() { - uwriteln!(self.gen.src, "{} = {result}", results[i]); - } - self.gen.src.dedent(); - } - let variant_name = name.to_upper_camel_case().escape(); - self.gen.src.push_str("else:\n"); - self.gen.src.indent(); - uwriteln!( - self.gen.src, - "raise TypeError(\"invalid variant specified for {variant_name}\")", - ); - self.gen.src.dedent(); - } - - Instruction::VariantLift { - variant, name, ty, .. - } => { - let blocks = self - .blocks - .drain(self.blocks.len() - variant.cases.len()..) - .collect::>(); - - let result = self.locals.tmp("variant"); - uwrite!(self.gen.src, "{result}: "); - self.print_ty(&Type::Id(*ty)); - self.gen.src.push_str("\n"); - for (i, (case, (block, block_results))) in - variant.cases.iter().zip(blocks).enumerate() - { - if i == 0 { - self.gen.src.push_str("if "); - } else { - self.gen.src.push_str("elif "); - } - uwriteln!(self.gen.src, "{} == {i}:", operands[0]); - self.gen.src.indent(); - self.gen.src.push_str(&block); - - uwrite!(self.gen.src, "{result} = "); - self.print_ty(&Type::Id(*ty)); - uwrite!( - self.gen.src, - "{}(", - case.name.to_upper_camel_case().escape() - ); - if block_results.len() > 0 { - assert!(block_results.len() == 1); - self.gen.src.push_str(&block_results[0]); - } - self.gen.src.push_str(")\n"); - self.gen.src.dedent(); - } - self.gen.src.push_str("else:\n"); - self.gen.src.indent(); - let variant_name = name.to_upper_camel_case().escape(); - uwriteln!( - self.gen.src, - "raise TypeError(\"invalid variant discriminant for {variant_name}\")", - ); - self.gen.src.dedent(); - results.push(result); - } - - Instruction::OptionLower { - results: result_types, - ty, - .. - } => { - let TypeDefKind::Option(some_type) = &self.gen.resolve.types[*ty].kind else { - unreachable!() - }; - let nesting = is_option(self.gen.resolve, *some_type); - if nesting { - self.gen.import_shared_type("Some"); - } - - let (some, some_results) = self.blocks.pop().unwrap(); - let (none, none_results) = self.blocks.pop().unwrap(); - let some_payload = self.payloads.pop().unwrap(); - let _none_payload = self.payloads.pop().unwrap(); - - for _ in 0..result_types.len() { - results.push(self.locals.tmp("variant")); - } - - let op0 = &operands[0]; - uwriteln!(self.gen.src, "if {op0} is None:"); - - self.gen.src.indent(); - self.gen.src.push_str(&none); - for (dst, result) in results.iter().zip(&none_results) { - uwriteln!(self.gen.src, "{dst} = {result}"); - } - self.gen.src.dedent(); - self.gen.src.push_str("else:\n"); - self.gen.src.indent(); - uwriteln!( - self.gen.src, - "{some_payload} = {op0}{}", - if nesting { ".value" } else { "" } - ); - self.gen.src.push_str(&some); - for (dst, result) in results.iter().zip(&some_results) { - uwriteln!(self.gen.src, "{dst} = {result}"); - } - self.gen.src.dedent(); - } - - Instruction::OptionLift { ty, .. } => { - let TypeDefKind::Option(some_type) = &self.gen.resolve.types[*ty].kind else { - unreachable!() - }; - let nesting = is_option(self.gen.resolve, *some_type); - if nesting { - self.gen.import_shared_type("Some"); - } - - 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 result = self.locals.tmp("option"); - uwrite!(self.gen.src, "{result}: "); - self.print_ty(&Type::Id(*ty)); - self.gen.src.push_str("\n"); - - let op0 = &operands[0]; - uwriteln!(self.gen.src, "if {op0} == 0:"); - self.gen.src.indent(); - self.gen.src.push_str(&none); - uwriteln!(self.gen.src, "{result} = None"); - self.gen.src.dedent(); - uwriteln!(self.gen.src, "elif {op0} == 1:"); - self.gen.src.indent(); - self.gen.src.push_str(&some); - if nesting { - uwriteln!(self.gen.src, "{result} = Some({some_result})"); - } else { - uwriteln!(self.gen.src, "{result} = {some_result}"); - } - self.gen.src.dedent(); - - self.gen.src.push_str("else:\n"); - self.gen.src.indent(); - self.gen - .src - .push_str("raise TypeError(\"invalid variant discriminant for option\")\n"); - self.gen.src.dedent(); - - results.push(result); - } - - Instruction::ResultLower { - results: result_types, - .. - } => { - let (err, err_results) = self.blocks.pop().unwrap(); - let (ok, ok_results) = self.blocks.pop().unwrap(); - let err_payload = self.payloads.pop().unwrap(); - let ok_payload = self.payloads.pop().unwrap(); - self.gen.import_shared_type("Ok"); - self.gen.import_shared_type("Err"); - - for _ in 0..result_types.len() { - results.push(self.locals.tmp("variant")); - } - - let op0 = &operands[0]; - uwriteln!(self.gen.src, "if isinstance({op0}, Ok):"); - - self.gen.src.indent(); - uwriteln!(self.gen.src, "{ok_payload} = {op0}.value"); - self.gen.src.push_str(&ok); - for (dst, result) in results.iter().zip(&ok_results) { - uwriteln!(self.gen.src, "{dst} = {result}"); - } - self.gen.src.dedent(); - uwriteln!(self.gen.src, "elif isinstance({op0}, Err):"); - self.gen.src.indent(); - uwriteln!(self.gen.src, "{err_payload} = {op0}.value"); - self.gen.src.push_str(&err); - for (dst, result) in results.iter().zip(&err_results) { - uwriteln!(self.gen.src, "{dst} = {result}"); - } - self.gen.src.dedent(); - self.gen.src.push_str("else:\n"); - self.gen.src.indent(); - self.gen.src.push_str(&format!( - "raise TypeError(\"invalid variant specified for expected\")\n", - )); - self.gen.src.dedent(); - } - - Instruction::ResultLift { ty, .. } => { - let (err, err_results) = self.blocks.pop().unwrap(); - let (ok, ok_results) = self.blocks.pop().unwrap(); - let none = String::from("None"); - let err_result = err_results.get(0).unwrap_or(&none); - let ok_result = ok_results.get(0).unwrap_or(&none); - - self.gen.import_shared_type("Ok"); - self.gen.import_shared_type("Err"); - - let result = self.locals.tmp("expected"); - uwrite!(self.gen.src, "{result}: "); - self.print_ty(&Type::Id(*ty)); - self.gen.src.push_str("\n"); - - let op0 = &operands[0]; - uwriteln!(self.gen.src, "if {op0} == 0:"); - self.gen.src.indent(); - self.gen.src.push_str(&ok); - uwriteln!(self.gen.src, "{result} = Ok({ok_result})"); - self.gen.src.dedent(); - uwriteln!(self.gen.src, "elif {op0} == 1:"); - self.gen.src.indent(); - self.gen.src.push_str(&err); - uwriteln!(self.gen.src, "{result} = Err({err_result})"); - self.gen.src.dedent(); - - self.gen.src.push_str("else:\n"); - self.gen.src.indent(); - self.gen - .src - .push_str("raise TypeError(\"invalid variant discriminant for expected\")\n"); - self.gen.src.dedent(); - - results.push(result); - } - - Instruction::EnumLower { .. } => results.push(format!("({}).value", operands[0])), - - Instruction::EnumLift { name, ty, .. } => { - let fqn_name = &self.gen.fqn_name(name, ty); - results.push(format!("{}({})", fqn_name, operands[0])); - } - - Instruction::ListCanonLower { element, .. } => { - let lower = self.print_canon_lower(); - let realloc = self.realloc.as_ref().unwrap(); - let memory = self.memory.as_ref().unwrap(); - - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - let array_ty = array_ty(resolve, element).unwrap(); - let size = self.gen.gen.sizes.size(element).size_wasm32(); - let align = self.gen.gen.sizes.align(element).align_wasm32(); - uwriteln!( - self.gen.src, - "{ptr}, {len} = {lower}({}, ctypes.{array_ty}, {size}, {align}, {realloc}, {memory}, caller)", - operands[0], - ); - results.push(ptr); - results.push(len); - } - Instruction::ListCanonLift { element, .. } => { - let lift = self.print_canon_lift(); - let memory = self.memory.as_ref().unwrap(); - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - uwriteln!(self.gen.src, "{ptr} = {}", operands[0]); - uwriteln!(self.gen.src, "{len} = {}", operands[1]); - let array_ty = array_ty(resolve, element).unwrap(); - self.gen.src.pyimport("ctypes", None); - let lift = format!( - "{lift}({ptr}, {len}, {}, ctypes.{array_ty}, {memory}, caller)", - self.gen.gen.sizes.size(element).size_wasm32(), - ); - self.gen.src.pyimport("typing", "cast"); - let list = self.locals.tmp("list"); - uwrite!(self.gen.src, "{list} = cast("); - self.print_list(element); - uwriteln!(self.gen.src, ", {lift})"); - results.push(list); - } - Instruction::StringLower { .. } => { - let encode = self.print_encode_utf8(); - let realloc = self.realloc.as_ref().unwrap(); - let memory = self.memory.as_ref().unwrap(); - - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - uwriteln!( - self.gen.src, - "{ptr}, {len} = {encode}({}, {realloc}, {memory}, caller)", - operands[0], - ); - results.push(ptr); - results.push(len); - } - Instruction::StringLift => { - let decode = self.print_decode_utf8(); - let memory = self.memory.as_ref().unwrap(); - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - uwriteln!(self.gen.src, "{ptr} = {}", operands[0]); - uwriteln!(self.gen.src, "{len} = {}", operands[1]); - let list = self.locals.tmp("list"); - uwriteln!( - self.gen.src, - "{list} = {decode}({memory}, caller, {ptr}, {len})" - ); - results.push(list); - } - - Instruction::ListLower { element, .. } => { - let base = self.payloads.pop().unwrap(); - let e = self.payloads.pop().unwrap(); - let realloc = self.realloc.as_ref().unwrap(); - let (body, body_results) = self.blocks.pop().unwrap(); - assert!(body_results.is_empty()); - let vec = self.locals.tmp("vec"); - let result = self.locals.tmp("result"); - let len = self.locals.tmp("len"); - let size = self.gen.gen.sizes.size(element).size_wasm32(); - let align = self.gen.gen.sizes.align(element).align_wasm32(); - - // first store our vec-to-lower in a temporary since we'll - // reference it multiple times. - uwriteln!(self.gen.src, "{vec} = {}", operands[0]); - uwriteln!(self.gen.src, "{len} = len({vec})"); - - // ... then realloc space for the result in the guest module - uwriteln!( - self.gen.src, - "{result} = {realloc}(caller, 0, 0, {align}, {len} * {size})", - ); - uwriteln!(self.gen.src, "assert(isinstance({result}, int))"); - - // ... then consume the vector and use the block to lower the - // result. - let i = self.locals.tmp("i"); - uwriteln!(self.gen.src, "for {i} in range(0, {len}):"); - self.gen.src.indent(); - uwriteln!(self.gen.src, "{e} = {vec}[{i}]"); - uwriteln!(self.gen.src, "{base} = {result} + {i} * {size}"); - self.gen.src.push_str(&body); - self.gen.src.dedent(); - - results.push(result); - results.push(len); - } - - Instruction::ListLift { element, .. } => { - let (body, body_results) = self.blocks.pop().unwrap(); - let base = self.payloads.pop().unwrap(); - let size = self.gen.gen.sizes.size(element).size_wasm32(); - let ptr = self.locals.tmp("ptr"); - let len = self.locals.tmp("len"); - uwriteln!(self.gen.src, "{ptr} = {}", operands[0]); - uwriteln!(self.gen.src, "{len} = {}", operands[1]); - let result = self.locals.tmp("result"); - uwrite!(self.gen.src, "{result}: "); - self.print_list(element); - uwriteln!(self.gen.src, " = []"); - - let i = self.locals.tmp("i"); - assert_eq!(body_results.len(), 1); - let body_result0 = &body_results[0]; - - uwriteln!(self.gen.src, "for {i} in range(0, {len}):"); - self.gen.src.indent(); - uwriteln!(self.gen.src, "{base} = {ptr} + {i} * {size}"); - self.gen.src.push_str(&body); - uwriteln!(self.gen.src, "{result}.append({body_result0})"); - self.gen.src.dedent(); - results.push(result); - } - - Instruction::IterElem { .. } => { - let name = self.locals.tmp("e"); - results.push(name.clone()); - self.payloads.push(name); - } - Instruction::IterBasePointer => { - let name = self.locals.tmp("base"); - results.push(name.clone()); - self.payloads.push(name); - } - Instruction::CallWasm { sig, .. } => { - if sig.results.len() > 0 { - for i in 0..sig.results.len() { - if i > 0 { - self.gen.src.push_str(", "); - } - let ret = self.locals.tmp("ret"); - self.gen.src.push_str(&ret); - results.push(ret); - } - self.gen.src.push_str(" = "); - } - self.gen.src.push_str(&self.callee); - self.gen.src.push_str("(caller"); - if operands.len() > 0 { - self.gen.src.push_str(", "); - } - self.gen.src.push_str(&operands.join(", ")); - self.gen.src.push_str(")\n"); - for (ty, name) in sig.results.iter().zip(results.iter()) { - let ty = wasm_ty_typing(*ty); - self.gen - .src - .push_str(&format!("assert(isinstance({}, {}))\n", name, ty)); - } - } - Instruction::CallInterface { func, async_ } => { - assert!(!async_); - if func.result.is_some() { - let result = self.locals.tmp("ret"); - self.gen.src.push_str(&result); - results.push(result); - self.gen.src.push_str(" = "); - } - match &func.kind { - FunctionKind::Freestanding => { - self.gen - .src - .push_str(&format!("{}({})", self.callee, operands.join(", "),)); - } - FunctionKind::AsyncFreestanding - | FunctionKind::AsyncMethod(_) - | FunctionKind::AsyncStatic(_) - | FunctionKind::Method(_) - | FunctionKind::Static(_) - | FunctionKind::Constructor(_) => { - unimplemented!() - } - } - self.gen.src.push_str("\n"); - } - - Instruction::Return { amt, func } => { - if let Some(s) = &self.post_return { - self.gen.src.push_str(&format!("{s}(caller, ret)\n")); - } - match func.kind { - FunctionKind::Constructor(_) => {} // No return value for __init__ - _ => match amt { - 0 => {} - 1 => self.gen.src.push_str(&format!("return {}\n", operands[0])), - _ => { - self.gen - .src - .push_str(&format!("return ({})\n", operands.join(", "))); - } - }, - } - } - - Instruction::I32Load { offset } - | Instruction::LengthLoad { offset } - | Instruction::PointerLoad { offset } => { - self.load("c_int32", *offset, operands, results) - } - Instruction::I64Load { offset } => self.load("c_int64", *offset, operands, results), - Instruction::F32Load { offset } => self.load("c_float", *offset, operands, results), - Instruction::F64Load { offset } => self.load("c_double", *offset, operands, results), - Instruction::I32Load8U { offset } => self.load("c_uint8", *offset, operands, results), - Instruction::I32Load8S { offset } => self.load("c_int8", *offset, operands, results), - Instruction::I32Load16U { offset } => self.load("c_uint16", *offset, operands, results), - Instruction::I32Load16S { offset } => self.load("c_int16", *offset, operands, results), - Instruction::I32Store { offset } - | Instruction::LengthStore { offset } - | Instruction::PointerStore { offset } => self.store("c_uint32", *offset, operands), - Instruction::I64Store { offset } => self.store("c_uint64", *offset, operands), - Instruction::F32Store { offset } => self.store("c_float", *offset, operands), - Instruction::F64Store { offset } => self.store("c_double", *offset, operands), - Instruction::I32Store8 { offset } => self.store("c_uint8", *offset, operands), - Instruction::I32Store16 { offset } => self.store("c_uint16", *offset, operands), - - Instruction::Malloc { size, align, .. } => { - let realloc = self.realloc.as_ref().unwrap(); - let ptr = self.locals.tmp("ptr"); - let align = align.align_wasm32(); - let size = size.size_wasm32(); - uwriteln!( - self.gen.src, - "{ptr} = {realloc}(caller, 0, 0, {align}, {size})" - ); - uwriteln!(self.gen.src, "assert(isinstance({ptr}, int))"); - results.push(ptr); - } - Instruction::HandleLift { handle, .. } => { - let resource_map = match self.resource_map { - Some(resource_map) => resource_map, - None => unimplemented!("imported resources not yet supported"), - }; - let (Handle::Own(ty) | Handle::Borrow(ty)) = handle; - let resource_ty = &dealias(self.gen.resolve, *ty); - let ResourceTable { data, .. } = &resource_map[resource_ty]; - let ResourceData::Host { tid, .. } = data; - let table_id = tid.as_u32(); - let handle_remove = &format!("_handle_remove_{table_id}"); - uwriteln!(self.gen.src, "entry = self.component.{handle_remove}(ret)"); - uwriteln!(self.gen.src, "self._rep = entry[0]"); - return results.push("".to_string()); - } - Instruction::HandleLower { .. } => { - uwriteln!(self.gen.src, "rep = self._rep"); - results.push("rep".to_string()); - } - Instruction::Flush { amt } => { - for i in 0..*amt { - let tmp = self.locals.tmp("tmp"); - uwriteln!(self.gen.src, "{tmp} = {}", operands[i]); - results.push(tmp); - } - } - - i => unimplemented!("{:?}", i), - } - } -} - -fn wasm_ty_ctor(ty: WasmType) -> &'static str { - match ty { - WasmType::I32 | WasmType::Pointer | WasmType::Length => "wasmtime.ValType.i32()", - WasmType::I64 | WasmType::PointerOrI64 => "wasmtime.ValType.i64()", - WasmType::F32 => "wasmtime.ValType.f32()", - WasmType::F64 => "wasmtime.ValType.f64()", - } -} - -fn wasm_ty_typing(ty: WasmType) -> &'static str { - match ty { - WasmType::I32 - | WasmType::I64 - | WasmType::Pointer - | WasmType::Length - | WasmType::PointerOrI64 => "int", - WasmType::F32 | WasmType::F64 => "float", - } -} - -fn is_option(resolve: &Resolve, ty: Type) -> bool { - if let Type::Id(id) = ty { - match &resolve.types[id].kind { - TypeDefKind::Option(_) => true, - TypeDefKind::Type(ty) => is_option(resolve, *ty), - _ => false, - } - } else { - false - } -} - -trait Escape: ToOwned { - fn escape(&self) -> Self::Owned; -} - -impl Escape for str { - fn escape(&self) -> String { - // Escape Python keywords - // Source: https://docs.python.org/3/reference/lexical_analysis.html#keywords - match self { - "False" | "None" | "True" | "and" | "as" | "assert" | "async" | "await" | "break" - | "class" | "continue" | "def" | "del" | "elif" | "else" | "except" | "finally" - | "for" | "from" | "global" | "if" | "import" | "in" | "is" | "lambda" | "nonlocal" - | "not" | "or" | "pass" | "raise" | "return" | "try" | "while" | "with" | "yield" => { - format!("{self}_") - } - _ => self.to_owned(), - } - } -} diff --git a/rust/src/files.rs b/rust/src/files.rs deleted file mode 100644 index afb404ad..00000000 --- a/rust/src/files.rs +++ /dev/null @@ -1,23 +0,0 @@ -use std::collections::btree_map::{BTreeMap, Entry}; - -#[derive(Default)] -pub struct Files { - files: BTreeMap>, -} - -impl Files { - pub fn push(&mut self, name: &str, contents: &[u8]) { - match self.files.entry(name.to_owned()) { - Entry::Vacant(entry) => { - entry.insert(contents.to_owned()); - } - Entry::Occupied(ref mut entry) => { - entry.get_mut().extend_from_slice(contents); - } - } - } - - pub fn iter(&self) -> impl Iterator { - self.files.iter().map(|p| (p.0.as_str(), p.1.as_slice())) - } -} diff --git a/rust/src/imports.rs b/rust/src/imports.rs deleted file mode 100644 index 9c8c1eea..00000000 --- a/rust/src/imports.rs +++ /dev/null @@ -1,121 +0,0 @@ -use std::collections::{BTreeMap, BTreeSet}; -use std::fmt::Write; - -/// Tracks all of the import and intrinsics that a given codegen -/// requires and how to generate them when needed. -#[derive(Default)] -pub struct PyImports { - pyimports: BTreeMap>>, - typing_imports: BTreeMap>>, -} - -impl PyImports { - /// Record that a Python import is required - pub fn pyimport<'a>(&mut self, module: &str, name: impl Into>) { - push(&mut self.pyimports, module, name.into()) - } - - pub fn typing_import<'a>(&mut self, module: &str, name: impl Into>) { - push(&mut self.typing_imports, module, name.into()) - } - - pub fn is_empty(&self) -> bool { - self.pyimports.is_empty() - } - - pub fn finish(&self) -> String { - let mut result = render(&self.pyimports); - - if !self.typing_imports.is_empty() { - result.push_str("from typing import TYPE_CHECKING\n"); - result.push_str("if TYPE_CHECKING:\n"); - for line in render(&self.typing_imports).lines() { - if !line.is_empty() { - result.push_str(" "); - result.push_str(line); - } - result.push_str("\n"); - } - } - - result - } -} - -fn push( - imports: &mut BTreeMap>>, - module: &str, - name: Option<&str>, -) { - let list = imports.entry(module.to_string()).or_insert(match name { - Some(_) => Some(BTreeSet::new()), - None => None, - }); - match name { - Some(name) => { - assert!(list.is_some()); - list.as_mut().unwrap().insert(name.to_string()); - } - None => assert!(list.is_none()), - } -} - -fn render(imports: &BTreeMap>>) -> String { - let mut result = String::new(); - for (k, list) in imports.iter() { - match list { - Some(list) => { - let list = list.iter().cloned().collect::>().join(", "); - uwriteln!(result, "from {k} import {list}"); - } - None => uwriteln!(result, "import {k}"), - } - } - - if !imports.is_empty() { - result.push_str("\n"); - } - result -} - -#[cfg(test)] -mod test { - use std::collections::{BTreeMap, BTreeSet}; - - use super::PyImports; - - #[test] - fn test_pyimport_only_contents() { - let mut deps = PyImports::default(); - deps.pyimport("typing", None); - deps.pyimport("typing", None); - assert_eq!(deps.pyimports, BTreeMap::from([("typing".into(), None)])); - } - - #[test] - fn test_pyimport_only_module() { - let mut deps = PyImports::default(); - deps.pyimport("typing", "Union"); - deps.pyimport("typing", "List"); - deps.pyimport("typing", "NamedTuple"); - assert_eq!( - deps.pyimports, - BTreeMap::from([( - "typing".into(), - Some(BTreeSet::from([ - "Union".into(), - "List".into(), - "NamedTuple".into() - ])) - )]) - ); - } - - #[test] - #[should_panic] - fn test_pyimport_conflicting() { - let mut deps = PyImports::default(); - deps.pyimport("typing", "NamedTuple"); - deps.pyimport("typing", None); - } -} diff --git a/rust/src/lib.rs b/rust/src/lib.rs deleted file mode 100644 index 6306f3b6..00000000 --- a/rust/src/lib.rs +++ /dev/null @@ -1,59 +0,0 @@ -/// Calls [`write!`] with the passed arguments and unwraps the result. -/// -/// Useful for writing to things with infallible `Write` implementations like -/// `Source` and `String`. -/// -/// [`write!`]: std::write -#[macro_export] -macro_rules! uwrite { - ($dst:expr, $($arg:tt)*) => { - write!($dst, $($arg)*).unwrap() - }; -} - -/// Calls [`writeln!`] with the passed arguments and unwraps the result. -/// -/// Useful for writing to things with infallible `Write` implementations like -/// `Source` and `String`. -/// -/// [`writeln!`]: std::writeln -#[macro_export] -macro_rules! uwriteln { - ($dst:expr, $($arg:tt)*) => { - writeln!($dst, $($arg)*).unwrap() - }; -} - -mod bindgen; -mod files; -mod imports; -mod ns; -mod source; - -pub use bindgen::WasmtimePy; -pub use files::Files; - -#[cfg(target_arch = "wasm32")] -mod bindings { - wit_bindgen::generate!({ - world: "bindgen", - path: "./bindgen.wit", - }); - - struct PythonBindings; - - impl Guest for PythonBindings { - fn generate(name: String, component: Vec) -> Result)>, String> { - let mut gen = crate::WasmtimePy::default(); - let mut files = Default::default(); - gen.generate(&name, &component, &mut files) - .map_err(|e| format!("{e:?}"))?; - Ok(files - .iter() - .map(|(name, bytes)| (name.to_string(), bytes.to_vec())) - .collect()) - } - } - - export!(PythonBindings); -} diff --git a/rust/src/ns.rs b/rust/src/ns.rs deleted file mode 100644 index 4fb05516..00000000 --- a/rust/src/ns.rs +++ /dev/null @@ -1,27 +0,0 @@ -use std::collections::HashSet; - -#[derive(Default)] -pub struct Ns { - defined: HashSet, - tmp: usize, -} - -impl Ns { - pub fn insert(&mut self, name: &str) -> Result<(), String> { - if self.defined.insert(name.to_string()) { - Ok(()) - } else { - Err(format!("name `{}` already defined", name)) - } - } - - pub fn tmp(&mut self, name: &str) -> String { - let mut ret = name.to_string(); - while self.defined.contains(&ret) { - ret = format!("{}{}", name, self.tmp); - self.tmp += 1; - } - self.defined.insert(ret.clone()); - return ret; - } -} diff --git a/rust/src/source.rs b/rust/src/source.rs deleted file mode 100644 index 29e959d3..00000000 --- a/rust/src/source.rs +++ /dev/null @@ -1,175 +0,0 @@ -use crate::imports::PyImports; -use std::fmt::{self, Write}; -use std::mem; -use wit_parser::*; - -/// A [Source] represents some unit of Python code -/// and keeps track of its indent. -#[derive(Default)] -pub struct Source { - body: Body, - imports: PyImports, -} - -#[derive(Default)] -pub struct Body { - contents: String, - indent: usize, -} - -impl Source { - /// Appends a string slice to this [Source]. - /// - /// Strings without newlines, they are simply appended. - /// Strings with newlines are appended and also new lines - /// are indented based on the current indent level. - pub fn push_str(&mut self, src: &str) { - let lines = src.lines().collect::>(); - let mut trim = None; - for (i, line) in lines.iter().enumerate() { - self.body.contents.push_str(if lines.len() == 1 { - line - } else { - let trim = match trim { - Some(n) => n, - None => { - let val = line.len() - line.trim_start().len(); - if !line.is_empty() { - trim = Some(val); - } - val - } - }; - line.get(trim..).unwrap_or("") - }); - if i != lines.len() - 1 || src.ends_with("\n") { - self.newline(); - } - } - } - - /// Prints the documentation as comments - /// e.g. - /// > \# Line one of docs node - /// > - /// > \# Line two of docs node - pub fn comment(&mut self, docs: &Docs) { - let docs = match &docs.contents { - Some(docs) => docs, - None => return, - }; - for line in docs.lines() { - self.push_str(&format!("# {}\n", line)); - } - } - - /// Prints the documentation as comments - /// e.g. - /// > """ - /// > - /// > Line one of docs node - /// > - /// > Line two of docs node - /// > - /// > """ - pub fn docstring(&mut self, docs: &Docs) { - let docs = match &docs.contents { - Some(docs) => docs, - None => return, - }; - let triple_quote = r#"""""#; - self.push_str(triple_quote); - self.newline(); - for line in docs.lines() { - self.push_str(line); - self.newline(); - } - self.push_str(triple_quote); - self.newline(); - } - - /// Indent the source one level. - pub fn indent(&mut self) { - self.body.indent += 4; - self.body.contents.push_str(" "); - } - - /// Unindent, or in Python terms "dedent", - /// the source one level. - pub fn dedent(&mut self) { - self.body.indent -= 4; - assert!(self.body.contents.ends_with(" ")); - self.body.contents.pop(); - self.body.contents.pop(); - self.body.contents.pop(); - self.body.contents.pop(); - } - - /// Go to the next line and apply any indent. - pub fn newline(&mut self) { - self.body.contents.push_str("\n"); - for _ in 0..self.body.indent { - self.body.contents.push_str(" "); - } - } - - pub fn pyimport<'a>(&mut self, module: &str, name: impl Into>) { - self.imports.pyimport(module, name.into()) - } - - pub fn typing_import<'a>(&mut self, module: &str, name: impl Into>) { - self.imports.typing_import(module, name.into()) - } - - pub fn finish(&self) -> String { - let mut ret = self.imports.finish(); - ret.push_str(&self.body.contents); - return ret; - } - - pub fn is_empty(&self) -> bool { - self.imports.is_empty() && self.body.contents.is_empty() - } - - pub fn take_body(&mut self) -> Body { - mem::take(&mut self.body) - } - - pub fn replace_body(&mut self, body: Body) -> String { - mem::replace(&mut self.body, body).contents - } -} - -impl Write for Source { - fn write_str(&mut self, s: &str) -> fmt::Result { - self.push_str(s); - Ok(()) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn simple_append() { - let mut s = Source::default(); - s.push_str("x"); - assert_eq!(s.body.contents, "x"); - s.push_str("y"); - assert_eq!(s.body.contents, "xy"); - s.push_str("z "); - assert_eq!(s.body.contents, "xyz "); - s.push_str(" a "); - assert_eq!(s.body.contents, "xyz a "); - s.push_str("\na"); - assert_eq!(s.body.contents, "xyz a \na"); - } - - #[test] - fn trim_ws() { - let mut s = Source::default(); - s.push_str("def foo():\n return 1\n"); - assert_eq!(s.body.contents, "def foo():\n return 1\n"); - } -} diff --git a/tests/bindgen/__init__.py b/tests/bindgen/__init__.py deleted file mode 100644 index 037b6622..00000000 --- a/tests/bindgen/__init__.py +++ /dev/null @@ -1,9 +0,0 @@ -import sys -import pytest -import platform - -is_win_arm64 = platform.system() == 'Windows' and platform.machine() == 'ARM64' - -# componentize-py requires Python 3.10, and doesn't support Windows on ARM64 -if sys.version_info < (3, 10) or is_win_arm64: - pytest.skip("skipping componentize-py tests", allow_module_level=True) diff --git a/tests/bindgen/bare_funcs/app.py b/tests/bindgen/bare_funcs/app.py deleted file mode 100644 index 00058708..00000000 --- a/tests/bindgen/bare_funcs/app.py +++ /dev/null @@ -1,3 +0,0 @@ -class WitWorld: - def foo(self, a): - return a + 1 diff --git a/tests/bindgen/bare_funcs/component.wit b/tests/bindgen/bare_funcs/component.wit deleted file mode 100644 index d5b8da8a..00000000 --- a/tests/bindgen/bare_funcs/component.wit +++ /dev/null @@ -1,5 +0,0 @@ -package component:barefuncs; - -world barefuncs { - export foo: func(a: s32) -> s32; -} diff --git a/tests/bindgen/bare_funcs/test_bare_funcs.py b/tests/bindgen/bare_funcs/test_bare_funcs.py deleted file mode 100644 index bd551447..00000000 --- a/tests/bindgen/bare_funcs/test_bare_funcs.py +++ /dev/null @@ -1,9 +0,0 @@ -from pathlib import Path - - -def test_bare_funcs(bindgen_testcase): - store, root = bindgen_testcase( - guest_code_dir=Path(__file__).parent, - world_name='barefuncs', - ) - assert root.foo(store, 10) == 11 diff --git a/tests/bindgen/conftest.py b/tests/bindgen/conftest.py deleted file mode 100644 index f8afb6d4..00000000 --- a/tests/bindgen/conftest.py +++ /dev/null @@ -1,146 +0,0 @@ -"""Fixtures to define test suites for generated code Python guest code. - -These tests work by allowing you to write a WIT file, implement the guest -code in Python via componentize-py, and then test the generated Python -bindings. To add a new test, first create the needed fixtures: - -* Create a new sub directory. -* Within that directory create a `.wit` file. -* Create an `app.py` file in that directory implementing the guest code. - -Then to write the test itself: - -* Create a `test_.py` in the same directory. -* Use the `bindgest_testcase` in your test to create the wasm component - and generate python bindings for this component. - -## Example - -Given this directory: - -``` -bare_funcs/ -├── app.py <-- guest code implementation -├── barefuncs <-- componentize-py bindings -│ ├── __init__.py -│ └── types.py -├── component.wit <-- test .wit file -└── test_mycomp.py <-- pytest test case of bindings -``` - -With a `component.wit` file of: - -```wit -package component:barefuncs; - -world barefuncs { - export foo: func(a: s32) -> s32; -} -``` - -And guest code of: - -```python -class Barefuncs: - def foo(self, a: int) -> int: - return a + 1 -``` - -You can write a testcase for this using: - -```python -from pathlib import Path - - -def test_bare_funcs(bindgen_testcase): - testcase = bindgen_testcase( - guest_code_dir=Path(__file__).parent, - world_name='barefuncs', - ) - store, root = generate_bindings(testcase) - assert root.foo(store, 10) == 11 -``` - -""" -from pathlib import Path -from dataclasses import dataclass, field -import importlib -import tempfile -import subprocess -import shutil - -from pytest import fixture - -import wasmtime -from wasmtime.bindgen import generate - - -TEST_ROOT = Path(__file__).parent -BINDGEN_DIR = TEST_ROOT / 'generated' - - -@dataclass -class BindgenTestCase: - guest_code_dir: Path - world_name: str - wit_filename: str = 'component.wit' - app_dir: Path = field(init=False) - app_name: str = field(init=False, default='app', repr=False) - - def __post_init__(self): - self.app_dir = Path(self.guest_code_dir).resolve() - - @property - def wit_full_path(self): - return self.guest_code_dir.joinpath(self.wit_filename) - - @property - def testsuite_name(self): - # The name of the directory that contains the - # guest Python code is used as the identifier for - # package names, etc. - return self.guest_code_dir.name - - -def generate_bindings(guest_code_dir: Path, - world_name: str, - wit_filename: str = 'component.wit'): - tc = BindgenTestCase( - guest_code_dir=guest_code_dir, - world_name=world_name, - wit_filename=wit_filename) - return _generate_bindings(tc) - - -def _generate_bindings(testcase: BindgenTestCase): - wit_path = testcase.wit_full_path - componentize_py = shutil.which('componentize-py') - if componentize_py is None: - raise RuntimeError("Could not find componentize-py executable.") - with tempfile.NamedTemporaryFile('w') as f: - output_wasm = str(f.name + '.wasm') - subprocess.run([ - componentize_py, '-d', str(wit_path), '-w', testcase.world_name, - 'componentize', '--stub-wasi', testcase.app_name, - '-o', output_wasm - ], check=True, cwd=testcase.guest_code_dir) - # Once we've done that now generate the python bindings. - testsuite_name = testcase.testsuite_name - with open(output_wasm, 'rb') as out: - # Mapping of filename -> content_bytes - results = generate(testsuite_name, out.read()) - for filename, contents in results.items(): - path = BINDGEN_DIR / testsuite_name / filename - path.parent.mkdir(parents=True, exist_ok=True) - path.write_bytes(contents) - # Return an instantiated module for the caller to test. - pkg = importlib.import_module(f'.generated.{testsuite_name}', - package=__package__) - store = wasmtime.Store() - root = pkg.Root(store) - return store, root - - -@fixture -def bindgen_testcase(): - return generate_bindings diff --git a/tests/bindgen/export_resources/app.py b/tests/bindgen/export_resources/app.py deleted file mode 100644 index 248818f2..00000000 --- a/tests/bindgen/export_resources/app.py +++ /dev/null @@ -1,30 +0,0 @@ -import sys -from types import ModuleType - - -class MyInterfaceName: - def interface_func(self, foo: str) -> str: - return f"hello {foo}" - - -# componentize-py expects that resources within an interface are defined -# as a class in a separate module that matches the interface name. -# -# Normally, you'd want to go the more typical route of running -# -# componentize-py -d component.wit -w testworld bindings . -# -# to generate the types and protocols to help you write guest code, -# and then split the code into multiple files, but we're taking a -# shortcut here so we can write all the guest code in a single file. -class DemoResourceClass: - def __init__(self, name: str) -> None: - self.name = name - - def greet(self, greeting: str) -> str: - return f'{greeting}, {self.name}!' - - -mod = ModuleType("my_interface_name") -mod.DemoResourceClass = DemoResourceClass -sys.modules['my_interface_name'] = mod diff --git a/tests/bindgen/export_resources/component.wit b/tests/bindgen/export_resources/component.wit deleted file mode 100644 index 63398ccb..00000000 --- a/tests/bindgen/export_resources/component.wit +++ /dev/null @@ -1,13 +0,0 @@ -package component:basicresource; - -interface my-interface-name { - interface-func: func(foo: string) -> string; - resource demo-resource-class { - constructor(name: string); - greet: func(greeting: string) -> string; - } -} - -world testworld { - export my-interface-name; -} diff --git a/tests/bindgen/export_resources/test_export_resources.py b/tests/bindgen/export_resources/test_export_resources.py deleted file mode 100644 index 2fd9d7f8..00000000 --- a/tests/bindgen/export_resources/test_export_resources.py +++ /dev/null @@ -1,12 +0,0 @@ -from pathlib import Path - - -def test_bare_funcs(bindgen_testcase): - store, root = bindgen_testcase( - guest_code_dir=Path(__file__).parent, - world_name='testworld', - ) - interface = root.my_interface_name() - instance = interface.DemoResourceClass(store, 'myname') - result = instance.greet(store, 'Hello there') - assert result == 'Hello there, myname!' diff --git a/tests/bindgen/list_types/app.py b/tests/bindgen/list_types/app.py deleted file mode 100644 index 99f3ad28..00000000 --- a/tests/bindgen/list_types/app.py +++ /dev/null @@ -1,15 +0,0 @@ -from typing import List - - -class WitWorld: - def strings(self, a: str) -> str: - return a - - def bytes(self, a: bytes) -> bytes: - return a - - def ints(self, a: List[int]) -> List[int]: - return a - - def string_list(self, a: List[str]) -> List[str]: - return a diff --git a/tests/bindgen/list_types/component.wit b/tests/bindgen/list_types/component.wit deleted file mode 100644 index c3a91622..00000000 --- a/tests/bindgen/list_types/component.wit +++ /dev/null @@ -1,8 +0,0 @@ -package component:lists; - -world lists { - export strings: func(a: string) -> string; - export bytes: func(a: list) -> list; - export ints: func(a: list) -> list; - export string-list: func(a: list) -> list; -} diff --git a/tests/bindgen/list_types/test_lists.py b/tests/bindgen/list_types/test_lists.py deleted file mode 100644 index 130c38e0..00000000 --- a/tests/bindgen/list_types/test_lists.py +++ /dev/null @@ -1,26 +0,0 @@ -from pathlib import Path - - -def test_lists(bindgen_testcase): - store, root = bindgen_testcase( - guest_code_dir=Path(__file__).parent, - world_name='lists', - ) - assert root.strings(store, '') == '' - assert root.strings(store, 'a') == 'a' - assert root.strings(store, 'hello world') == 'hello world' - assert root.strings(store, 'hello âš‘ world') == 'hello âš‘ world' - - assert root.bytes(store, b'') == b'' - assert root.bytes(store, b'a') == b'a' - assert root.bytes(store, b'\x01\x02') == b'\x01\x02' - - assert root.ints(store, []) == [] - assert root.ints(store, [1]) == [1] - assert root.ints(store, [1, 2, 100, 10000]) == [1, 2, 100, 10000] - - assert root.string_list(store, []) == [] - assert root.string_list(store, ['']) == [''] - assert root.string_list( - store, ['a', 'b', '', 'd', 'hello'] - ) == ['a', 'b', '', 'd', 'hello'] diff --git a/tests/codegen/__init__.py b/tests/codegen/__init__.py deleted file mode 100644 index 08a301b4..00000000 --- a/tests/codegen/__init__.py +++ /dev/null @@ -1,137 +0,0 @@ -# The `codegen` directory here is intended to test the bindings generator for -# components in Python. Each file represents a distinct test where an input -# wasm file is bound and then the generated bindings are imported dynamically. -# -# This structure is done so a general `pytest` will execute everything, -# generating bindings during test collection and otherwise setting up everything -# to be naturally checked with mypy and other tests configured. - -from wasmtime import wat2wasm -from wasmtime.bindgen import generate -from pathlib import Path -import os - - -# Helper function to generate bindings for the `wat` specified into the -# `generated` sub-folder. After calling this method the bindings can be -# imported with: -# -# from .generated.name import Name -# -# and then used to type-check everything. -def bindgen(name: str, wat: str) -> None: - files = generate(name, wat2wasm(wat)) - root = Path(__file__).parent.joinpath('generated') - dst = root.joinpath(name) - for name, contents in files.items(): - # If the file already has the desired contents then skip writing. This - # is an attempt to fix CI issues on windows. - file = dst.joinpath(name) - if file.exists(): - with open(file, 'rb') as f: - if f.read() == contents: - continue - - # Write the contents to a temporary file and then attempt to atomically - # replace the previous file, if any, with the new contents. This - # is done to hopefully fix an apparent issue in `pytest` where it seems - # that there are multiple threads of the python interpreter, perhaps for - # pytest itself, mypy, and flake8, and overwriting files in-place causes - # issues are partial files may be seen. - tmp_file = file.with_suffix('.tmp') - if not file.parent.exists(): - file.parent.mkdir(parents=True) - tmp_file.write_bytes(contents) - os.replace(tmp_file, file) - - -REALLOC = """ - (global $last (mut i32) (i32.const 1000)) - (func $realloc (export "realloc") - (param $old_ptr i32) - (param $old_size i32) - (param $align i32) - (param $new_size i32) - (result i32) - - (local $ret i32) - - ;; Test if the old pointer is non-null - local.get $old_ptr - if - ;; If the old size is bigger than the new size then - ;; this is a shrink and transparently allow it - local.get $old_size - local.get $new_size - i32.gt_u - if - local.get $old_ptr - return - end - - ;; otherwise fall through to allocate a new chunk which will later - ;; copy data over - end - - ;; align up `$last` - (global.set $last - (i32.and - (i32.add - (global.get $last) - (i32.add - (local.get $align) - (i32.const -1))) - (i32.xor - (i32.add - (local.get $align) - (i32.const -1)) - (i32.const -1)))) - - ;; save the current value of `$last` as the return value - global.get $last - local.set $ret - - ;; bump our pointer - (global.set $last - (i32.add - (global.get $last) - (local.get $new_size))) - - ;; while `memory.size` is less than `$last`, grow memory - ;; by one page - (loop $loop - (if - (i32.lt_u - (i32.mul (memory.size) (i32.const 65536)) - (global.get $last)) - (then - i32.const 1 - memory.grow - ;; test to make sure growth succeeded - i32.const -1 - i32.eq - if unreachable end - - br $loop))) - - - ;; ensure anything necessary is set to valid data by spraying a bit - ;; pattern that is invalid - local.get $ret - i32.const 0xde - local.get $new_size - memory.fill - - ;; If the old pointer is present then that means this was a reallocation - ;; of an existing chunk which means the existing data must be copied. - local.get $old_ptr - if - local.get $ret ;; destination - local.get $old_ptr ;; source - local.get $old_size ;; size - memory.copy - end - - local.get $ret - ) -""" diff --git a/tests/codegen/foo.wat b/tests/codegen/foo.wat deleted file mode 100644 index fb2073ef..00000000 --- a/tests/codegen/foo.wat +++ /dev/null @@ -1,17 +0,0 @@ - - (component - (import "i" (instance $i - (export "f1" (func)) - (export "f2" (func)) - )) - - (core func $f1 (canon lower (func $i "f1"))) - (core func $f2 (canon lower (func $i "f2"))) - - (func $f1' (canon lift (core func $f1))) - (func $f2' (canon lift (core func $f2))) - - (instance (export "i1") - (export "f1" (func $f1'))) - ;;(export "i2" (instance (export "f2" (func $f2)))) - ) diff --git a/tests/codegen/test_bare_funcs.py b/tests/codegen/test_bare_funcs.py deleted file mode 100644 index c309d45c..00000000 --- a/tests/codegen/test_bare_funcs.py +++ /dev/null @@ -1,42 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ - (component - (import "foo-import" (func $foo-import (param "a" s32) (result s32))) - - (core func $foo-import-lowered (canon lower (func $foo-import))) - - (core module $m - (import "" "foo" (func $foo (param i32) (result i32))) - - (func (export "foo") (param i32) (result i32) - (call $foo (local.get 0)) - ) - ) - - (core instance $i (instantiate $m - (with "" (instance - (export "foo" (func $foo-import-lowered)) - )) - )) - - (func $foo-export-lifted (param "a" s32) (result s32) (canon lift (core func $i "foo"))) - - (export "foo-export" (func $foo-export-lifted)) - ) -""" -bindgen('bare_funcs', module) - -from .generated.bare_funcs import Root, RootImports, imports - - -class Host(imports.Host): - def foo_import(self, a): - return a + 1 - - -def test_bindings(): - store = Store() - bindings = Root(store, RootImports(host=Host())) - assert 101 == bindings.foo_export(store, 100) diff --git a/tests/codegen/test_empty.py b/tests/codegen/test_empty.py deleted file mode 100644 index ba57a4f6..00000000 --- a/tests/codegen/test_empty.py +++ /dev/null @@ -1,13 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ - (component) -""" -bindgen('empty', module) - -from .generated.empty import Root - - -def test_bindings(tmp_path): - Root(Store()) diff --git a/tests/codegen/test_empty_import.py b/tests/codegen/test_empty_import.py deleted file mode 100644 index 9043cb10..00000000 --- a/tests/codegen/test_empty_import.py +++ /dev/null @@ -1,16 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ - (component - (import "host" (instance)) - ) -""" -bindgen('empty_import', module) - -from .generated.empty_import import Root -from .generated.empty_import.imports import RootImports - - -def test_bindings(tmp_path) -> None: - Root(Store(), RootImports(host={})) diff --git a/tests/codegen/test_export_resources.py b/tests/codegen/test_export_resources.py deleted file mode 100644 index 781bbc16..00000000 --- a/tests/codegen/test_export_resources.py +++ /dev/null @@ -1,88 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ -(component - (core module $core-mod - (import "[export]component:basicresource/my-interface-name" "[resource-drop]demo-resource-class" (func $resource-drop (param i32))) - (import "[export]component:basicresource/my-interface-name" "[resource-new]demo-resource-class" (func $resource-new (param i32) (result i32))) - (import "[export]component:basicresource/my-interface-name" "[resource-rep]demo-resource-class" (func $resource-rep (param i32) (result i32))) - (func $core-create-demo-resource (param i32 i32) (result i32) - unreachable - ) - (func $core-demo-resource-greet (param i32 i32 i32) (result i32) - unreachable - ) - (func $core-cabi-realloc (param i32 i32 i32 i32) (result i32) - unreachable - ) - (memory (;0;) 0) - (export "component:basicresource/my-interface-name#[constructor]demo-resource-class" (func $core-create-demo-resource)) - (export "component:basicresource/my-interface-name#[method]demo-resource-class.greet" (func $core-demo-resource-greet)) - (export "memory" (memory 0)) - (export "cabi_realloc" (func $core-cabi-realloc)) - ) - (type $demo-resource-type (resource (rep i32))) - (core func $core-resource-drop (canon resource.drop $demo-resource-type)) - (core func $core-resource-rep (canon resource.rep $demo-resource-type)) - (core func $core-resource-new (canon resource.new $demo-resource-type)) - (core instance $canon-instance - (export "[resource-drop]demo-resource-class" (func $core-resource-drop)) - (export "[resource-new]demo-resource-class" (func $core-resource-new)) - (export "[resource-rep]demo-resource-class" (func $core-resource-rep)) - ) - (core instance $core-instance (instantiate $core-mod - (with "[export]component:basicresource/my-interface-name" (instance $canon-instance)) - ) - ) - (alias core export $core-instance "memory" (core memory (;0;))) - (alias core export $core-instance "cabi_realloc" (core func $cabi-realloc)) - (type $constructor-type (func (param "name" string) (result (own $demo-resource-type)))) - (alias core export $core-instance "component:basicresource/my-interface-name#[constructor]demo-resource-class" (core func $core-constructor)) - (func $lift-demo-resource-constructor (type $constructor-type) (canon lift (core func $core-constructor) (memory 0) (realloc $cabi-realloc) string-encoding=utf8)) - (type $greet-type (func (param "self" (borrow $demo-resource-type)) (param "greeting" string) (result string))) - (alias core export $core-instance "component:basicresource/my-interface-name#[method]demo-resource-class.greet" (core func $core-greet)) - (func $lift-demo-resource-greet (type $greet-type) (canon lift (core func $core-greet) (memory 0) (realloc $cabi-realloc) string-encoding=utf8)) - (component $comp-api - (import "import-type-demo-resource-class" (type $demo-resource (sub resource))) - (type $constructor-type (func (param "name" string) (result (own $demo-resource)))) - (import "import-constructor-demo-resource-class" (func $constructor-import (type $constructor-type))) - (type $greet-type (func (param "self" (borrow $demo-resource)) (param "greeting" string) (result string))) - (import "import-method-demo-resource-class-greet" (func $greet-import (type $greet-type))) - (export $demo-resource-export "demo-resource-class" (type $demo-resource)) - (type $constructor-type-export (func (param "name" string) (result (own $demo-resource-export)))) - (export "[constructor]demo-resource-class" (func $constructor-import) (func (type $constructor-type-export))) - (type $greet-type-export (func (param "self" (borrow $demo-resource-export)) (param "greeting" string) (result string))) - (export "[method]demo-resource-class.greet" (func $greet-import) (func (type $greet-type-export))) - ) - (instance $api-instance (instantiate $comp-api - (with "import-constructor-demo-resource-class" (func $lift-demo-resource-constructor)) - (with "import-method-demo-resource-class-greet" (func $lift-demo-resource-greet)) - (with "import-type-demo-resource-class" (type $demo-resource-type)) - ) - ) - (export "component:basicresource/my-interface-name" (instance $api-instance)) -) -""" - -bindgen("export_resources", module) - -from .generated.export_resources import Root -from .generated.export_resources import my_interface_name - - -def test_bindings(): - store = Store() - root = Root(store) - interface = root.my_interface_name() - # We can't test round tripping until support for resource imports - # is added. For now, we can check that the structure of the - # generated code looks right. - assert hasattr(interface, "DemoResourceClass") - assert hasattr(my_interface_name, "DemoResourceClass") - resource_cls = my_interface_name.DemoResourceClass - assert resource_cls.greet.__annotations__ == { - "caller": Store, - "greeting": str, - "return": str, - } diff --git a/tests/codegen/test_external_types.py b/tests/codegen/test_external_types.py deleted file mode 100644 index 927a0b3d..00000000 --- a/tests/codegen/test_external_types.py +++ /dev/null @@ -1,84 +0,0 @@ -from . import bindgen, REALLOC -from wasmtime import Store - -module = """ -(component $OuterComp - - (type $types - (instance - (type $runtime-value (variant - (case "id" string) (case "id2" string))) - (export "runtime-value" - (type $runtime-value-export (eq $runtime-value))) - ) - ) - (import "types" (instance $types (type $types))) - (alias export $types "runtime-value" (type $runtime-value-export)) - - (import "host" (instance $inst - (alias outer $OuterComp 1 (type $runtime-value)) - (export "runtime-value" (type $export (eq $runtime-value))) - (export "some-fn" (func (param "v" $export) (result $export))) - )) - - (core module $libc - (memory (export "mem") 1) - {} - ) - (core instance $libc (instantiate $libc)) - - (core module $mod - (import "libc" "mem" (memory 1)) - (import "" "lowered-fn-import" - (func $lowered-fn (param i32 i32 i32 i32))) - - (func (export "core-fn") (param i32 i32 i32) (result i32) - (call $lowered-fn - (local.get 0) (local.get 1) (local.get 2) (local.get 2)) - (local.get 2)) - ) - - (core func $lowered-fn - (canon lower (func $inst "some-fn") (memory $libc "mem") - (realloc (func $libc "realloc")))) - - (core instance $inst - (instantiate $mod - (with "libc" (instance $libc)) - (with "" (instance - (export "lowered-fn-import" (func $lowered-fn)) - )) - ) - ) - - (type $runtime-value (variant (case "id" string) (case "id2" string))) - (func $lifted-core-fn (param "a" $runtime-value) (result $runtime-value) - (canon lift (core func $inst "core-fn") (memory $libc "mem") - (realloc (func $libc "realloc")))) - - (instance (export "e") - (export "runtime-value" (type $runtime-value)) - (export "some-fn" (func $lifted-core-fn)) - ) - -) -""".format(REALLOC) -bindgen('external_types', module) - -from .generated.external_types import Root, RootImports, imports -from .generated.external_types.imports import host -from .generated.external_types import e - - -class Host(imports.HostHost): - def some_fn(self, v: host.RuntimeValue) -> host.RuntimeValue: - return v - - -def test_bindings(tmp_path): - store = Store() - wasm = Root(store, RootImports(None, host=Host())) - - exports = wasm.e() - rt_id = exports.some_fn(store, e.RuntimeValueId('1234')) - assert rt_id == e.RuntimeValueId('1234') diff --git a/tests/codegen/test_keywords.py b/tests/codegen/test_keywords.py deleted file mode 100644 index 560b2149..00000000 --- a/tests/codegen/test_keywords.py +++ /dev/null @@ -1,87 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ - (component - (import "false" (instance $i - (type $c1 (variant (case "break" s32) (case "class" s64) (case "true" s64))) - (export "none" (type $c1' (eq $c1))) - (export "as" (func (param "import" $c1') (result s64))) - - (type $r1 (record (field "else" u8) (field "not" u8) (field "except" u8))) - (export "true" (type $r1' (eq $r1))) - (export "lambda" (func (param "def" $r1') (result u32))) - )) - - (core func $as (canon lower (func $i "as"))) - (core func $lambda (canon lower (func $i "lambda"))) - - (core module $m - (import "" "as" (func $as (param i32 i64) (result i64))) - (import "" "lambda" (func $lambda (param i32 i32 i32) (result i32))) - - (func (export "await") (result i32) - i32.const 100) - - (func (export "as") (param i32 i64) (result i64) - (call $as (local.get 0) (local.get 1))) - - (func (export "lambda") (param i32 i32 i32) (result i32) - (call $lambda (local.get 0) (local.get 1) (local.get 2))) - ) - - (core instance $i (instantiate $m - (with "" (instance - (export "as" (func $as)) - (export "lambda" (func $lambda)) - )) - )) - - (type $c1 (variant (case "break" s32) (case "class" s64) (case "true" s64))) - (type $r1 (record (field "else" u8) (field "not" u8) (field "except" u8))) - - (func $await-export (result u8) (canon lift (core func $i "await"))) - (func $as-export (param "import" $c1) (result s64) - (canon lift (core func $i "as"))) - (func $lambda-export (param "def" $r1) (result u32) - (canon lift (core func $i "lambda"))) - - (instance (export "for") - (export "none" (type $c1)) - (export "true" (type $r1)) - (export "await" (func $await-export)) - (export "as" (func $as-export)) - (export "lambda" (func $lambda-export)) - ) - ) -""" -bindgen('keywords', module) - -from .generated.keywords import Root, RootImports, imports -from .generated.keywords import for_ -from .generated.keywords.imports import false - - -class Host(imports.HostFalse): - def as_(self, import_): - if isinstance(import_, false.None_Break): - return import_.value + 1 - if isinstance(import_, false.None_Class): - return import_.value + 2 - if isinstance(import_, false.None_True_): - return import_.value + 3 - else: - raise ValueError("Invalid input value!") - - def lambda_(self, def_): - return def_.else_ + def_.not_ + def_.except_ - - -def test_bindings(): - store = Store() - bindings = Root(store, RootImports(false=Host())) - assert 100 == bindings.for_().await_(store) - assert 101 == bindings.for_().as_(store, for_.None_Break(100)) - assert 102 == bindings.for_().as_(store, for_.None_Class(100)) - assert 103 == bindings.for_().as_(store, for_.None_True_(100)) - assert 24 == bindings.for_().lambda_(store, for_.True_(7, 8, 9)) diff --git a/tests/codegen/test_lists.py b/tests/codegen/test_lists.py deleted file mode 100644 index f0692105..00000000 --- a/tests/codegen/test_lists.py +++ /dev/null @@ -1,112 +0,0 @@ -from . import bindgen, REALLOC -from wasmtime import Store -from typing import List - -module = """ - (component - (import "host" (instance $i - (export "strings" (func (param "a" string) (result string))) - (export "bytes" (func (param "a" (list u8)) (result (list u8)))) - (export "ints" (func (param "a" (list u32)) (result (list u32)))) - (export "string-list" (func (param "a" (list string)) (result (list string)))) - )) - - (core module $libc - (memory (export "mem") 1) - {} - ) - (core instance $libc (instantiate $libc)) - - (core func $strings (canon lower (func $i "strings") - (memory $libc "mem") (realloc (func $libc "realloc")))) - (core func $bytes (canon lower (func $i "bytes") - (memory $libc "mem") (realloc (func $libc "realloc")))) - (core func $ints (canon lower (func $i "ints") - (memory $libc "mem") (realloc (func $libc "realloc")))) - (core func $string-list (canon lower (func $i "string-list") - (memory $libc "mem") (realloc (func $libc "realloc")))) - - (core module $m - (import "libc" "mem" (memory 1)) - (import "" "strings" (func $strings (param i32 i32 i32))) - (import "" "bytes" (func $bytes (param i32 i32 i32))) - (import "" "ints" (func $ints (param i32 i32 i32))) - (import "" "string-list" (func $string-list (param i32 i32 i32))) - - (func (export "strings") (param i32 i32) (result i32) - (call $strings (local.get 0) (local.get 1) (i32.const 8)) - i32.const 8) - (func (export "bytes") (param i32 i32) (result i32) - (call $bytes (local.get 0) (local.get 1) (i32.const 8)) - i32.const 8) - (func (export "ints") (param i32 i32) (result i32) - (call $ints (local.get 0) (local.get 1) (i32.const 8)) - i32.const 8) - (func (export "string-list") (param i32 i32) (result i32) - (call $string-list (local.get 0) (local.get 1) (i32.const 8)) - i32.const 8) - ) - - (core instance $i (instantiate $m - (with "libc" (instance $libc)) - (with "" (instance - (export "strings" (func $strings)) - (export "bytes" (func $bytes)) - (export "ints" (func $ints)) - (export "string-list" (func $string-list)) - )) - )) - - (func (export "strings") (param "a" string) (result string) - (canon lift (core func $i "strings") - (memory $libc "mem") (realloc (func $libc "realloc")))) - (func (export "bytes") (param "a" (list u8)) (result (list u8)) - (canon lift (core func $i "bytes") - (memory $libc "mem") (realloc (func $libc "realloc")))) - (func (export "ints") (param "a" (list u32)) (result (list u32)) - (canon lift (core func $i "ints") - (memory $libc "mem") (realloc (func $libc "realloc")))) - (func (export "string-list") (param "a" (list string)) (result (list string)) - (canon lift (core func $i "string-list") - (memory $libc "mem") (realloc (func $libc "realloc")))) - ) -""".format(REALLOC) -bindgen('lists', module) - -from .generated.lists import Root, RootImports, imports - - -class Host(imports.HostHost): - def strings(self, a: str) -> str: - return a - - def bytes(self, a: bytes) -> bytes: - return a - - def ints(self, a: List[int]) -> List[int]: - return a - - def string_list(self, a: List[str]) -> List[str]: - return a - - -def test_bindings(): - store = Store() - wasm = Root(store, RootImports(host=Host())) - - assert wasm.strings(store, '') == '' - assert wasm.strings(store, 'a') == 'a' - assert wasm.strings(store, 'hello world') == 'hello world' - assert wasm.strings(store, 'hello âš‘ world') == 'hello âš‘ world' - - assert wasm.bytes(store, b'') == b'' - assert wasm.bytes(store, b'a') == b'a' - assert wasm.bytes(store, b'\x01\x02') == b'\x01\x02' - - assert wasm.ints(store, []) == [] - assert wasm.ints(store, [1]) == [1] - assert wasm.ints(store, [1, 2, 100, 10000]) == [1, 2, 100, 10000] - - assert wasm.string_list(store, []) == [] - assert wasm.string_list(store, ['']) == [''] - assert wasm.string_list(store, ['a', 'b', '', 'd', 'hello']) == ['a', 'b', '', 'd', 'hello'] diff --git a/tests/codegen/test_many_arguments.py b/tests/codegen/test_many_arguments.py deleted file mode 100644 index 011c8bc9..00000000 --- a/tests/codegen/test_many_arguments.py +++ /dev/null @@ -1,124 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ - (component - (import "host" (instance $i - (export "many-arguments" (func - (param "a1" u64) - (param "a2" u64) - (param "a3" u64) - (param "a4" u64) - (param "a5" u64) - (param "a6" u64) - (param "a7" u64) - (param "a8" u64) - (param "a9" u64) - (param "a10" u64) - (param "a11" u64) - (param "a12" u64) - (param "a13" u64) - (param "a14" u64) - (param "a15" u64) - (param "a16" u64) - )) - )) - (core module $m - (import "" "" (func $f (param - i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 - ))) - - (func (export "") - (param - i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 i64 - ) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - local.get 6 - local.get 7 - local.get 8 - local.get 9 - local.get 10 - local.get 11 - local.get 12 - local.get 13 - local.get 14 - local.get 15 - call $f - ) - ) - (core func $f (canon lower (func $i "many-arguments"))) - - (core instance $i (instantiate $m - (with "" (instance (export "" (func $f)))) - )) - - (func (export "many-arguments") - (param "a1" u64) - (param "a2" u64) - (param "a3" u64) - (param "a4" u64) - (param "a5" u64) - (param "a6" u64) - (param "a7" u64) - (param "a8" u64) - (param "a9" u64) - (param "a10" u64) - (param "a11" u64) - (param "a12" u64) - (param "a13" u64) - (param "a14" u64) - (param "a15" u64) - (param "a16" u64) - (canon lift (core func $i ""))) - ) -""" -bindgen('many_arguments', module) - -from .generated.many_arguments import Root, RootImports, imports - - -class MyImports(imports.HostHost): - def many_arguments(self, - a1: int, - a2: int, - a3: int, - a4: int, - a5: int, - a6: int, - a7: int, - a8: int, - a9: int, - a10: int, - a11: int, - a12: int, - a13: int, - a14: int, - a15: int, - a16: int) -> None: - assert(a1 == 1) - assert(a2 == 2) - assert(a3 == 3) - assert(a4 == 4) - assert(a5 == 5) - assert(a6 == 6) - assert(a7 == 7) - assert(a8 == 8) - assert(a9 == 9) - assert(a10 == 10) - assert(a11 == 11) - assert(a12 == 12) - assert(a13 == 13) - assert(a14 == 14) - assert(a15 == 15) - assert(a16 == 16) - - -def test_bindings(): - store = Store() - wasm = Root(store, RootImports(MyImports())) - wasm.many_arguments(store, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) diff --git a/tests/codegen/test_records.py b/tests/codegen/test_records.py deleted file mode 100644 index 0af0f912..00000000 --- a/tests/codegen/test_records.py +++ /dev/null @@ -1,207 +0,0 @@ -from . import bindgen -from typing import Tuple -from wasmtime import Store - -module = """ - (component - (type $tuple (tuple u8 u32)) - - (type $flag1 (flags "a" "b")) - (type $flag2 (flags "a" "b" "c")) - (type $flag8 (flags "a1" "a2" "a3" "a4" "a5" "a6" "a7" "a8")) - (type $flag16 (flags - "a1" "a2" "a3" "a4" "a5" "a6" "a7" "a8" "a9" "a10" "a11" "a12" "a13" - "a14" "a15" "a16" - )) - (type $flag32 (flags - "a1" "a2" "a3" "a4" "a5" "a6" "a7" "a8" "a9" "a10" "a11" "a12" "a13" - "a14" "a15" "a16" "a17" "a18" "a19" "a20" "a21" "a22" "a23" "a24" - "a25" "a26" "a27" "a28" "a29" "a30" "a31" "a32" - )) - - (type $r1 (record (field "a" u8) (field "b" $flag1))) - - (import "host" (instance $i - (export "multiple-results" (func (result (tuple u8 u16)))) - (export "swap" (func (param "a" $tuple) (result $tuple))) - - (export "flag1" (type $f1 (eq $flag1))) - (export "flag2" (type $f2 (eq $flag2))) - (export "flag8" (type $f8 (eq $flag8))) - (export "flag16" (type $f16 (eq $flag16))) - (export "flag32" (type $f32 (eq $flag32))) - - (export "roundtrip-flag1" (func (param "a" $f1) (result $f1))) - (export "roundtrip-flag2" (func (param "a" $f2) (result $f2))) - (export "roundtrip-flag8" (func (param "a" $f8) (result $f8))) - (export "roundtrip-flag16" (func (param "a" $f16) (result $f16))) - (export "roundtrip-flag32" (func (param "a" $f32) (result $f32))) - - (type $r1 (record (field "a" u8) (field "b" $f1))) - (export "r1" (type $r1' (eq $r1))) - (export "roundtrip-r1" (func (param "a" $r1') (result $r1'))) - )) - - (core module $libc - (memory (export "mem") 1) - ) - (core instance $libc (instantiate $libc)) - - (core func $multi (canon lower (func $i "multiple-results") (memory $libc "mem"))) - (core func $swap (canon lower (func $i "swap") (memory $libc "mem"))) - (core func $r-flag1 (canon lower (func $i "roundtrip-flag1"))) - (core func $r-flag2 (canon lower (func $i "roundtrip-flag2"))) - (core func $r-flag8 (canon lower (func $i "roundtrip-flag8"))) - (core func $r-flag16 (canon lower (func $i "roundtrip-flag16"))) - (core func $r-flag32 (canon lower (func $i "roundtrip-flag32"))) - (core func $r-r1 (canon lower (func $i "roundtrip-r1") (memory $libc "mem"))) - - (core module $m - (import "" "r-flag1" (func $r-flag1 (param i32) (result i32))) - (import "" "r-flag2" (func $r-flag2 (param i32) (result i32))) - (import "" "r-flag8" (func $r-flag8 (param i32) (result i32))) - (import "" "r-flag16" (func $r-flag16 (param i32) (result i32))) - (import "" "r-flag32" (func $r-flag32 (param i32) (result i32))) - (import "" "multi" (func $multi (param i32))) - (import "" "swap" (func $swap (param i32 i32 i32))) - (import "" "r-r1" (func $r-r1 (param i32 i32 i32))) - - (import "libc" "mem" (memory 1)) - - (func (export "multi") (result i32) - (call $multi (i32.const 100)) - i32.const 100) - - (func (export "swap") (param i32 i32) (result i32) - (call $swap (local.get 0) (local.get 1) (i32.const 100)) - i32.const 100) - - (func (export "r-flag1") (param i32) (result i32) - (call $r-flag1 (local.get 0))) - (func (export "r-flag2") (param i32) (result i32) - (call $r-flag2 (local.get 0))) - (func (export "r-flag8") (param i32) (result i32) - (call $r-flag8 (local.get 0))) - (func (export "r-flag16") (param i32) (result i32) - (call $r-flag16 (local.get 0))) - (func (export "r-flag32") (param i32) (result i32) - (call $r-flag32 (local.get 0))) - (func (export "r-r1") (param i32 i32) (result i32) - (call $r-r1 (local.get 0) (local.get 1) (i32.const 100)) - i32.const 100) - ) - - (core instance $i (instantiate $m - (with "libc" (instance $libc)) - (with "" (instance - (export "multi" (func $multi)) - (export "swap" (func $swap)) - (export "r-flag1" (func $r-flag1)) - (export "r-flag2" (func $r-flag2)) - (export "r-flag8" (func $r-flag8)) - (export "r-flag16" (func $r-flag16)) - (export "r-flag32" (func $r-flag32)) - (export "r-r1" (func $r-r1)) - )) - )) - - (func $multiple-results (result (tuple u8 u16)) - (canon lift (core func $i "multi") (memory $libc "mem"))) - (func $swap (param "a" $tuple) (result $tuple) - (canon lift (core func $i "swap") (memory $libc "mem"))) - (func $roundtrip-flag1 (param "a" $flag1) (result $flag1) - (canon lift (core func $i "r-flag1"))) - (func $roundtrip-flag2 (param "a" $flag2) (result $flag2) - (canon lift (core func $i "r-flag2"))) - (func $roundtrip-flag8 (param "a" $flag8) (result $flag8) - (canon lift (core func $i "r-flag8"))) - (func $roundtrip-flag16 (param "a" $flag16) (result $flag16) - (canon lift (core func $i "r-flag16"))) - (func $roundtrip-flag32 (param "a" $flag32) (result $flag32) - (canon lift (core func $i "r-flag32"))) - (func $roundtrip-r1 (param "a" $r1) (result $r1) - (canon lift (core func $i "r-r1") (memory $libc "mem"))) - - (instance (export "e") - (export "flag1" (type $flag1)) - (export "flag2" (type $flag2)) - (export "flag8" (type $flag8)) - (export "flag16" (type $flag16)) - (export "flag32" (type $flag32)) - (export "r1" (type $r1)) - - (export "multiple-results" (func $multiple-results)) - (export "swap" (func $swap)) - (export "roundtrip-flag1" (func $roundtrip-flag1)) - (export "roundtrip-flag2" (func $roundtrip-flag2)) - (export "roundtrip-flag8" (func $roundtrip-flag8)) - (export "roundtrip-flag16" (func $roundtrip-flag16)) - (export "roundtrip-flag32" (func $roundtrip-flag32)) - (export "roundtrip-r1" (func $roundtrip-r1)) - ) - ) -""" -bindgen('records', module) - -from .generated.records import Root, RootImports, imports -from .generated.records.exports.e import Flag1, Flag2, Flag8, Flag16, Flag32, R1 -from .generated.records.imports import host - - -class Host(imports.HostHost): - def multiple_results(self) -> Tuple[int, int]: - return 1, 2 - - def swap(self, tuple: Tuple[int, int]) -> Tuple[int, int]: - a, b = tuple - return b, a - - def roundtrip_flag1(self, f: host.Flag1) -> host.Flag1: - return f - - def roundtrip_flag2(self, f: host.Flag2) -> host.Flag2: - return f - - def roundtrip_flag8(self, f: host.Flag8) -> host.Flag8: - return f - - def roundtrip_flag16(self, f: host.Flag16) -> host.Flag16: - return f - - def roundtrip_flag32(self, f: host.Flag32) -> host.Flag32: - return f - - def roundtrip_r1(self, f: host.R1) -> host.R1: - return f - - -def test_bindings(): - store = Store() - bindings = Root(store, RootImports(host=Host())) - - assert bindings.e().multiple_results(store) == (1, 2) - assert bindings.e().swap(store, (3, 4)) == (4, 3) - - assert bindings.e().roundtrip_flag1(store, Flag1(0)) == Flag1(0) - for f1 in Flag1: - assert bindings.e().roundtrip_flag1(store, f1) == f1 - assert bindings.e().roundtrip_flag2(store, Flag2(0)) == Flag2(0) - for f2 in Flag2: - assert bindings.e().roundtrip_flag2(store, f2) == f2 - assert bindings.e().roundtrip_flag8(store, Flag8(0)) == Flag8(0) - for f8 in Flag8: - assert bindings.e().roundtrip_flag8(store, f8) == f8 - assert bindings.e().roundtrip_flag16(store, Flag16(0)) == Flag16(0) - for f16 in Flag16: - assert bindings.e().roundtrip_flag16(store, f16) == f16 - assert bindings.e().roundtrip_flag32(store, Flag32(0)) == Flag32(0) - for f32 in Flag32: - assert bindings.e().roundtrip_flag32(store, f32) == f32 - - r = bindings.e().roundtrip_r1(store, R1(8, Flag1(0))) - assert r.a == 8 - assert r.b == Flag1(0) - - r = bindings.e().roundtrip_r1(store, R1(a=100, b=Flag1.A | Flag1.B)) - assert r.a == 100 - assert r.b == Flag1.A | Flag1.B diff --git a/tests/codegen/test_scalars.py b/tests/codegen/test_scalars.py deleted file mode 100644 index f275c308..00000000 --- a/tests/codegen/test_scalars.py +++ /dev/null @@ -1,221 +0,0 @@ -from . import bindgen -import math -from wasmtime import Store - -module = """ - (component - (import "host" (instance $i - (export "roundtrip-u8" (func (param "a" u8) (result u8))) - (export "roundtrip-s8" (func (param "a" s8) (result s8))) - (export "roundtrip-u16" (func (param "a" u16) (result u16))) - (export "roundtrip-s16" (func (param "a" s16) (result s16))) - (export "roundtrip-u32" (func (param "a" u32) (result u32))) - (export "roundtrip-s32" (func (param "a" s32) (result s32))) - (export "roundtrip-u64" (func (param "a" u64) (result u64))) - (export "roundtrip-s64" (func (param "a" s64) (result s64))) - (export "roundtrip-float32" (func (param "a" float32) (result float32))) - (export "roundtrip-float64" (func (param "a" float64) (result float64))) - (export "roundtrip-char" (func (param "a" char) (result char))) - (export "roundtrip-bool" (func (param "a" bool) (result bool))) - )) - (core module $m - (import "" "roundtrip-u8" (func $u8 (param i32) (result i32))) - (import "" "roundtrip-s8" (func $s8 (param i32) (result i32))) - (import "" "roundtrip-u16" (func $u16 (param i32) (result i32))) - (import "" "roundtrip-s16" (func $s16 (param i32) (result i32))) - (import "" "roundtrip-u32" (func $u32 (param i32) (result i32))) - (import "" "roundtrip-s32" (func $s32 (param i32) (result i32))) - (import "" "roundtrip-u64" (func $u64 (param i64) (result i64))) - (import "" "roundtrip-s64" (func $s64 (param i64) (result i64))) - - (import "" "roundtrip-float32" (func $float32 (param f32) (result f32))) - (import "" "roundtrip-float64" (func $float64 (param f64) (result f64))) - - (import "" "roundtrip-char" (func $char (param i32) (result i32))) - (import "" "roundtrip-bool" (func $bool (param i32) (result i32))) - - (func (export "roundtrip-u8") (param i32) (result i32) - local.get 0 call $u8) - (func (export "roundtrip-s8") (param i32) (result i32) - local.get 0 call $s8) - (func (export "roundtrip-u16") (param i32) (result i32) - local.get 0 call $u16) - (func (export "roundtrip-s16") (param i32) (result i32) - local.get 0 call $s16) - (func (export "roundtrip-u32") (param i32) (result i32) - local.get 0 call $u32) - (func (export "roundtrip-s32") (param i32) (result i32) - local.get 0 call $s32) - (func (export "roundtrip-u64") (param i64) (result i64) - local.get 0 call $u64) - (func (export "roundtrip-s64") (param i64) (result i64) - local.get 0 call $s64) - - (func (export "roundtrip-float32") (param f32) (result f32) - local.get 0 call $float32) - (func (export "roundtrip-float64") (param f64) (result f64) - local.get 0 call $float64) - - (func (export "roundtrip-char") (param i32) (result i32) - local.get 0 call $char) - (func (export "roundtrip-bool") (param i32) (result i32) - local.get 0 call $bool) - ) - (core func $u8 (canon lower (func $i "roundtrip-u8"))) - (core func $s8 (canon lower (func $i "roundtrip-s8"))) - (core func $u16 (canon lower (func $i "roundtrip-u16"))) - (core func $s16 (canon lower (func $i "roundtrip-s16"))) - (core func $u32 (canon lower (func $i "roundtrip-u32"))) - (core func $s32 (canon lower (func $i "roundtrip-s32"))) - (core func $u64 (canon lower (func $i "roundtrip-u64"))) - (core func $s64 (canon lower (func $i "roundtrip-s64"))) - (core func $float32 (canon lower (func $i "roundtrip-float32"))) - (core func $float64 (canon lower (func $i "roundtrip-float64"))) - (core func $char (canon lower (func $i "roundtrip-char"))) - (core func $bool (canon lower (func $i "roundtrip-bool"))) - - (core instance $i (instantiate $m - (with "" (instance - (export "roundtrip-u8" (func $u8)) - (export "roundtrip-s8" (func $s8)) - (export "roundtrip-u16" (func $u16)) - (export "roundtrip-s16" (func $s16)) - (export "roundtrip-u32" (func $u32)) - (export "roundtrip-s32" (func $s32)) - (export "roundtrip-u64" (func $u64)) - (export "roundtrip-s64" (func $s64)) - (export "roundtrip-float32" (func $float32)) - (export "roundtrip-float64" (func $float64)) - (export "roundtrip-char" (func $char)) - (export "roundtrip-bool" (func $bool)) - )) - )) - - (func (export "roundtrip-u8") (param "a" u8) (result u8) - (canon lift (core func $i "roundtrip-u8"))) - (func (export "roundtrip-s8") (param "a" s8) (result s8) - (canon lift (core func $i "roundtrip-s8"))) - (func (export "roundtrip-u16") (param "a" u16) (result u16) - (canon lift (core func $i "roundtrip-u16"))) - (func (export "roundtrip-s16") (param "a" s16) (result s16) - (canon lift (core func $i "roundtrip-s16"))) - (func (export "roundtrip-u32") (param "a" u32) (result u32) - (canon lift (core func $i "roundtrip-u32"))) - (func (export "roundtrip-s32") (param "a" s32) (result s32) - (canon lift (core func $i "roundtrip-s32"))) - (func (export "roundtrip-u64") (param "a" u64) (result u64) - (canon lift (core func $i "roundtrip-u64"))) - (func (export "roundtrip-s64") (param "a" s64) (result s64) - (canon lift (core func $i "roundtrip-s64"))) - (func (export "roundtrip-float32") (param "a" float32) (result float32) - (canon lift (core func $i "roundtrip-float32"))) - (func (export "roundtrip-float64") (param "a" float64) (result float64) - (canon lift (core func $i "roundtrip-float64"))) - (func (export "roundtrip-char") (param "a" char) (result char) - (canon lift (core func $i "roundtrip-char"))) - (func (export "roundtrip-bool") (param "a" bool) (result bool) - (canon lift (core func $i "roundtrip-bool"))) - ) -""" -bindgen('scalars', module) - -from .generated.scalars import Root, RootImports, imports - - -class Host(imports.HostHost): - def roundtrip_u8(self, val: int) -> int: - assert val >= 0 - assert val <= (1 << 8) - 1 - return val - - def roundtrip_s8(self, val: int) -> int: - assert val >= -(1 << (8 - 1)) - assert val <= (1 << (8 - 1)) - 1 - return val - - def roundtrip_u16(self, val: int) -> int: - assert val >= 0 - assert val <= (1 << 16) - 1 - return val - - def roundtrip_s16(self, val: int) -> int: - assert val >= -(1 << (16 - 1)) - assert val <= (1 << (16 - 1)) - 1 - return val - - def roundtrip_u32(self, val: int) -> int: - assert val >= 0 - assert val <= (1 << 32) - 1 - return val - - def roundtrip_s32(self, val: int) -> int: - assert val >= -(1 << (32 - 1)) - assert val <= (1 << (32 - 1)) - 1 - return val - - def roundtrip_u64(self, val: int) -> int: - assert val >= 0 - assert val <= (1 << 64) - 1 - return val - - def roundtrip_s64(self, val: int) -> int: - assert val >= -(1 << (64 - 1)) - assert val <= (1 << (64 - 1)) - 1 - return val - - def roundtrip_float32(self, a: float) -> float: - return a - - def roundtrip_float64(self, a: float) -> float: - return a - - def roundtrip_char(self, a: str) -> str: - return a - - def roundtrip_bool(self, a: bool) -> bool: - return a - - -def test_bindings(): - store = Store() - bindings = Root(store, RootImports(host=Host())) - - assert bindings.roundtrip_u8(store, 0) == 0 - assert bindings.roundtrip_u8(store, (1 << 8) - 1) == (1 << 8) - 1 - assert bindings.roundtrip_u16(store, 0) == 0 - assert bindings.roundtrip_u16(store, (1 << 16) - 1) == (1 << 16) - 1 - assert bindings.roundtrip_u32(store, 0) == 0 - assert bindings.roundtrip_u32(store, (1 << 32) - 1) == (1 << 32) - 1 - assert bindings.roundtrip_u64(store, 0) == 0 - assert bindings.roundtrip_u64(store, (1 << 64) - 1) == (1 << 64) - 1 - - assert bindings.roundtrip_s8(store, 0) == 0 - assert bindings.roundtrip_s8(store, (1 << (8 - 1)) - 1) == (1 << (8 - 1)) - 1 - assert bindings.roundtrip_s8(store, -(1 << (8 - 1))) == -(1 << (8 - 1)) - assert bindings.roundtrip_s16(store, 0) == 0 - assert bindings.roundtrip_s16(store, (1 << (16 - 1)) - 1) == (1 << (16 - 1)) - 1 - assert bindings.roundtrip_s16(store, -(1 << (16 - 1))) == -(1 << (16 - 1)) - assert bindings.roundtrip_s32(store, 0) == 0 - assert bindings.roundtrip_s32(store, (1 << (32 - 1)) - 1) == (1 << (32 - 1)) - 1 - assert bindings.roundtrip_s32(store, -(1 << (32 - 1))) == -(1 << (32 - 1)) - assert bindings.roundtrip_s64(store, 0) == 0 - assert bindings.roundtrip_s64(store, (1 << (64 - 1)) - 1) == (1 << (64 - 1)) - 1 - assert bindings.roundtrip_s64(store, -(1 << (64 - 1))) == -(1 << (64 - 1)) - - inf = float('inf') - assert bindings.roundtrip_float32(store, 1.0) == 1.0 - assert bindings.roundtrip_float32(store, inf) == inf - assert bindings.roundtrip_float32(store, -inf) == -inf - assert math.isnan(bindings.roundtrip_float32(store, float('nan'))) - - assert bindings.roundtrip_float64(store, 1.0) == 1.0 - assert bindings.roundtrip_float64(store, inf) == inf - assert bindings.roundtrip_float64(store, -inf) == -inf - assert math.isnan(bindings.roundtrip_float64(store, float('nan'))) - - assert bindings.roundtrip_char(store, 'a') == 'a' - assert bindings.roundtrip_char(store, ' ') == ' ' - assert bindings.roundtrip_char(store, '🚩') == '🚩' - - assert bindings.roundtrip_bool(store, True) - assert not bindings.roundtrip_bool(store, False) diff --git a/tests/codegen/test_simple_export.py b/tests/codegen/test_simple_export.py deleted file mode 100644 index f7aaa940..00000000 --- a/tests/codegen/test_simple_export.py +++ /dev/null @@ -1,25 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ - (component - (core module $m - (func (export "get") (result i32) - i32.const 100) - ) - - (core instance $i (instantiate $m)) - - (func (export "get") (result u8) (canon lift (core func $i "get"))) - ) -""" -bindgen('simple_export', module) - -from .generated.simple_export import Root - - -def test_bindings(): - store = Store() - bindings = Root(store) - result = bindings.get(store) - assert result == 100 diff --git a/tests/codegen/test_simple_import.py b/tests/codegen/test_simple_import.py deleted file mode 100644 index ac3fd892..00000000 --- a/tests/codegen/test_simple_import.py +++ /dev/null @@ -1,37 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ - (component - (import "host" (instance $host - (export "thunk" (func)) - )) - - (core module $m - (import "host" "thunk" (func $thunk)) - - (start $thunk) - ) - - (core func $thunk (canon lower (func $host "thunk"))) - (core instance $i (instantiate $m - (with "host" (instance (export "thunk" (func $thunk)))) - )) - ) -""" -bindgen('simple_import', module) - -from .generated.simple_import import Root, RootImports, imports - - -class Host(imports.HostHost): - def thunk(self): - self.hit = True - - -def test_bindings(): - store = Store() - host = Host() - Root(store, RootImports(host=host)) - - assert host.hit diff --git a/tests/codegen/test_two_exports.py b/tests/codegen/test_two_exports.py deleted file mode 100644 index e77b37d7..00000000 --- a/tests/codegen/test_two_exports.py +++ /dev/null @@ -1,42 +0,0 @@ -from . import bindgen -from wasmtime import Store - -module = """ - (component - (import "i" (instance $i - (export "f1" (func)) - (export "f2" (func)) - )) - - (core func $f1 (canon lower (func $i "f1"))) - (core func $f2 (canon lower (func $i "f2"))) - - (func $f1' (canon lift (core func $f1))) - (func $f2' (canon lift (core func $f2))) - - (instance (export "i1") (export "f1" (func $f1'))) - (instance (export "i2") (export "f2" (func $f2'))) - ) -""" -bindgen('two_exports', module) - -from .generated.two_exports import Root, RootImports, imports - - -class Host(imports.HostI): - def f1(self) -> None: - self.f1_hit = True - - def f2(self) -> None: - self.f2_hit = True - - -def test_bindings(): - store = Store() - host = Host() - wasm = Root(store, RootImports(i=host)) - - wasm.i1().f1(store) - assert(host.f1_hit) - wasm.i2().f2(store) - assert(host.f2_hit) diff --git a/tests/codegen/test_variants.py b/tests/codegen/test_variants.py deleted file mode 100644 index ce1f3dad..00000000 --- a/tests/codegen/test_variants.py +++ /dev/null @@ -1,555 +0,0 @@ -from . import bindgen -from wasmtime import Store -from typing import Optional - -module = """ - (component - (import "host" (instance $i - (type $e1 (enum "a" "b")) - - (type $c1 (variant (case "a" s32) (case "b" s64))) - (type $c2 (variant (case "a" s32) (case "b" float32))) - (type $c3 (variant (case "a" s32) (case "b" float64))) - (type $c4 (variant (case "a" s64) (case "b" float32))) - (type $c5 (variant (case "a" s64) (case "b" float64))) - (type $c6 (variant (case "a" float32) (case "b" float64))) - - (type $z1 (variant (case "a" s32) (case "b"))) - (type $z2 (variant (case "a" s64) (case "b"))) - (type $z3 (variant (case "a" float32) (case "b"))) - (type $z4 (variant (case "a" float64) (case "b"))) - - (type $all-integers (variant - (case "bool" bool) - (case "u8" u8) - (case "u16" u16) - (case "u32" u32) - (case "u64" u64) - (case "s8" s8) - (case "s16" s16) - (case "s32" s32) - (case "s64" s64) - )) - (type $all-floats (variant (case "f32" float32) (case "f64" float64))) - (type $duplicated-s32 (variant - (case "c1" s32) - (case "c2" s32) - (case "c3" s32) - )) - (type $distinguished (variant (case "s32" s32) (case "float32" float32))) - (export "distinguished" (type $distinguished' (eq $distinguished))) - - (type $nested-union (variant - (case "d" $distinguished') - (case "s32" s32) - (case "float32" float32) - )) - (type $option-in-union (variant (case "o" (option s32)) (case "i" s32))) - - (export "e1" (type $e1' (eq $e1))) - - (export "c1" (type $c1' (eq $c1))) - (export "c2" (type $c2' (eq $c2))) - (export "c3" (type $c3' (eq $c3))) - (export "c4" (type $c4' (eq $c4))) - (export "c5" (type $c5' (eq $c5))) - (export "c6" (type $c6' (eq $c6))) - (type $casts (tuple $c1' $c2' $c3' $c4' $c5' $c6')) - (export "casts" (type $casts' (eq $casts))) - - (export "z1" (type $z1' (eq $z1))) - (export "z2" (type $z2' (eq $z2))) - (export "z3" (type $z3' (eq $z3))) - (export "z4" (type $z4' (eq $z4))) - (type $zeros (tuple $z1' $z2' $z3' $z4')) - (export "zeros" (type $zeros' (eq $zeros))) - - (export "all-integers" (type $all-integers' (eq $all-integers))) - (export "all-floats" (type $all-floats' (eq $all-floats))) - (export "duplicated-s32" (type $duplicated-s32' (eq $duplicated-s32))) - (export "nested-union" (type $nested-union' (eq $nested-union))) - (export "option-in-union" (type $option-in-union' (eq $option-in-union))) - - (export "roundtrip-option" (func (param "a" (option float32)) (result (option u8)))) - (export "roundtrip-result" (func - (param "a" (result u32 (error float32))) - (result (result float64 (error u8))) - )) - (export "roundtrip-enum" (func (param "a" $e1') (result $e1'))) - (export "variant-casts" (func (param "a" $casts') (result $casts'))) - (export "variant-zeros" (func (param "a" $zeros') (result $zeros'))) - - (export "add-one-all-integers" (func (param "a" $all-integers') (result $all-integers'))) - (export "add-one-all-floats" (func (param "a" $all-floats') (result $all-floats'))) - (export "add-one-duplicated-s32" (func (param "a" $duplicated-s32') (result $duplicated-s32'))) - (export "add-one-distinguished" (func (param "a" $distinguished') (result $distinguished'))) - (export "add-one-nested-union" (func (param "a" $nested-union') (result $nested-union'))) - (export "add-one-option-in-union" (func (param "a" $option-in-union') (result $option-in-union'))) - (export "add-one-option-in-option" (func (param "a" (option (option s32))) (result (option (option s32))))) - )) - - (core module $libc (memory (export "m") 1)) - (core instance $libc (instantiate $libc)) - - (core func $r-opt (canon lower (func $i "roundtrip-option") (memory $libc "m"))) - (core func $r-result (canon lower (func $i "roundtrip-result") (memory $libc "m"))) - (core func $r-enum (canon lower (func $i "roundtrip-enum"))) - (core func $v-casts (canon lower (func $i "variant-casts") (memory $libc "m"))) - (core func $v-zeros (canon lower (func $i "variant-zeros") (memory $libc "m"))) - (core func $a-int (canon lower (func $i "add-one-all-integers") (memory $libc "m"))) - (core func $a-float (canon lower (func $i "add-one-all-floats") (memory $libc "m"))) - (core func $a-dup (canon lower (func $i "add-one-duplicated-s32") (memory $libc "m"))) - (core func $a-dist (canon lower (func $i "add-one-distinguished") (memory $libc "m"))) - (core func $a-nest (canon lower (func $i "add-one-nested-union") (memory $libc "m"))) - (core func $a-oinu (canon lower (func $i "add-one-option-in-union") (memory $libc "m"))) - (core func $a-oino (canon lower (func $i "add-one-option-in-option") (memory $libc "m"))) - - (core module $m - (import "libc" "m" (memory 1)) - (import "" "r-opt" (func $r-opt (param i32 f32 i32))) - (import "" "r-result" (func $r-result (param i32 i32 i32))) - (import "" "r-enum" (func $r-enum (param i32) (result i32))) - (import "" "v-casts" (func $v-casts - (param i32 i64 i32 i32 i32 i64 i32 i64 i32 i64 i32 i64 i32) - )) - (import "" "v-zeros" (func $v-zeros - (param i32 i32 i32 i64 i32 f32 i32 f64 i32) - )) - (import "" "a-int" (func $a-int (param i32 i64 i32))) - (import "" "a-float" (func $a-float (param i32 i64 i32))) - (import "" "a-dup" (func $a-dup (param i32 i32 i32))) - (import "" "a-dist" (func $a-dist (param i32 i32 i32))) - (import "" "a-nest" (func $a-nest (param i32 i32 i32 i32))) - (import "" "a-oinu" (func $a-oinu (param i32 i32 i32 i32))) - (import "" "a-oino" (func $a-oino (param i32 i32 i32 i32))) - - (func (export "r-opt") (param i32 f32) (result i32) - (call $r-opt (local.get 0) (local.get 1) (i32.const 100)) - i32.const 100) - (func (export "r-result") (param i32 i32) (result i32) - (call $r-result (local.get 0) (local.get 1) (i32.const 100)) - i32.const 100) - (func (export "r-enum") (param i32) (result i32) - (call $r-enum (local.get 0))) - (func (export "v-casts") - (param i32 i64 i32 i32 i32 i64 i32 i64 i32 i64 i32 i64) - (result i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - local.get 6 - local.get 7 - local.get 8 - local.get 9 - local.get 10 - local.get 11 - i32.const 80 - call $v-casts - i32.const 80) - (func (export "v-zeros") - (param i32 i32 i32 i64 i32 f32 i32 f64) - (result i32) - local.get 0 - local.get 1 - local.get 2 - local.get 3 - local.get 4 - local.get 5 - local.get 6 - local.get 7 - i32.const 80 - call $v-zeros - i32.const 80) - - (func (export "a-int") (param i32 i64) (result i32) - (call $a-int (local.get 0) (local.get 1) (i32.const 80)) - i32.const 80) - (func (export "a-float") (param i32 i64) (result i32) - (call $a-float (local.get 0) (local.get 1) (i32.const 80)) - i32.const 80) - (func (export "a-dup") (param i32 i32) (result i32) - (call $a-dup (local.get 0) (local.get 1) (i32.const 80)) - i32.const 80) - (func (export "a-dist") (param i32 i32) (result i32) - (call $a-dist (local.get 0) (local.get 1) (i32.const 80)) - i32.const 80) - (func (export "a-nest") (param i32 i32 i32) (result i32) - (call $a-nest (local.get 0) (local.get 1) (local.get 2) (i32.const 80)) - i32.const 80) - (func (export "a-oinu") (param i32 i32 i32) (result i32) - (call $a-oinu (local.get 0) (local.get 1) (local.get 2) (i32.const 80)) - i32.const 80) - (func (export "a-oino") (param i32 i32 i32) (result i32) - (call $a-oino (local.get 0) (local.get 1) (local.get 2) (i32.const 80)) - i32.const 80) - ) - - (core instance $i (instantiate $m - (with "libc" (instance $libc)) - (with "" (instance - (export "r-opt" (func $r-opt)) - (export "r-result" (func $r-result)) - (export "r-enum" (func $r-enum)) - (export "v-casts" (func $v-casts)) - (export "v-zeros" (func $v-zeros)) - (export "a-int" (func $a-int)) - (export "a-float" (func $a-float)) - (export "a-dup" (func $a-dup)) - (export "a-dist" (func $a-dist)) - (export "a-nest" (func $a-nest)) - (export "a-oinu" (func $a-oinu)) - (export "a-oino" (func $a-oino)) - )) - )) - - (type $e1 (enum "a" "b")) - - (type $c1 (variant (case "a" s32) (case "b" s64))) - (type $c2 (variant (case "a" s32) (case "b" float32))) - (type $c3 (variant (case "a" s32) (case "b" float64))) - (type $c4 (variant (case "a" s64) (case "b" float32))) - (type $c5 (variant (case "a" s64) (case "b" float64))) - (type $c6 (variant (case "a" float32) (case "b" float64))) - (type $casts (tuple $c1 $c2 $c3 $c4 $c5 $c6)) - - (type $z1 (variant (case "a" s32) (case "b"))) - (type $z2 (variant (case "a" s64) (case "b"))) - (type $z3 (variant (case "a" float32) (case "b"))) - (type $z4 (variant (case "a" float64) (case "b"))) - (type $zeros (tuple $z1 $z2 $z3 $z4)) - - (type $all-integers (variant - (case "bool" bool) - (case "u8" u8) - (case "u16" u16) - (case "u32" u32) - (case "u64" u64) - (case "s8" s8) - (case "s16" s16) - (case "s32" s32) - (case "s64" s64) - )) - (type $all-floats (variant (case "f32" float32) (case "f64" float64))) - (type $duplicated-s32 (variant - (case "c1" s32) - (case "c2" s32) - (case "c3" s32) - )) - (type $distinguished (variant (case "s32" s32) (case "float32" float32))) - (type $nested-union (variant - (case "d" $distinguished) - (case "s32" s32) - (case "float32" float32) - )) - (type $option-in-union (variant (case "o" (option s32)) (case "i" s32))) - - (func $roundtrip-option (param "a" (option float32)) (result (option u8)) - (canon lift (core func $i "r-opt") (memory $libc "m"))) - (func $roundtrip-result - (param "a" (result u32 (error float32))) - (result (result float64 (error u8))) - (canon lift (core func $i "r-result") (memory $libc "m"))) - (func $roundtrip-enum (param "a" $e1) (result $e1) - (canon lift (core func $i "r-enum"))) - (func $variant-casts (param "a" $casts) (result $casts) - (canon lift (core func $i "v-casts") (memory $libc "m"))) - (func $variant-zeros (param "a" $zeros) (result $zeros) - (canon lift (core func $i "v-zeros") (memory $libc "m"))) - - (func $add-one-all-integers (param "a" $all-integers) (result $all-integers) - (canon lift (core func $i "a-int") (memory $libc "m"))) - (func $add-one-all-floats (param "a" $all-floats) (result $all-floats) - (canon lift (core func $i "a-float") (memory $libc "m"))) - (func $add-one-duplicated-s32 (param "a" $duplicated-s32) (result $duplicated-s32) - (canon lift (core func $i "a-dup") (memory $libc "m"))) - (func $add-one-distinguished (param "a" $distinguished) (result $distinguished) - (canon lift (core func $i "a-dist") (memory $libc "m"))) - (func $add-one-nested-union (param "a" $nested-union) (result $nested-union) - (canon lift (core func $i "a-nest") (memory $libc "m"))) - (func $add-one-option-in-union (param "a" $option-in-union) (result $option-in-union) - (canon lift (core func $i "a-oinu") (memory $libc "m"))) - (func $add-one-option-in-option (param "a" (option (option s32))) (result (option (option s32))) - (canon lift (core func $i "a-oino") (memory $libc "m"))) - - (instance (export "e") - (export "e1" (type $e1)) - - (export "c1" (type $c1)) - (export "c2" (type $c2)) - (export "c3" (type $c3)) - (export "c4" (type $c4)) - (export "c5" (type $c5)) - (export "c6" (type $c6)) - (export "casts" (type $casts)) - - (export "z1" (type $z1)) - (export "z2" (type $z2)) - (export "z3" (type $z3)) - (export "z4" (type $z4)) - (export "zeros" (type $zeros)) - - (export "all-integers" (type $all-integers)) - (export "all-floats" (type $all-floats)) - (export "duplicated-s32" (type $duplicated-s32)) - (export "distinguished" (type $distinguished)) - (export "nested-union" (type $nested-union)) - (export "option-in-union" (type $option-in-union)) - - (export "roundtrip-option" (func $roundtrip-option)) - (export "roundtrip-result" (func $roundtrip-result)) - (export "roundtrip-enum" (func $roundtrip-enum)) - (export "variant-casts" (func $variant-casts)) - (export "variant-zeros" (func $variant-zeros)) - (export "add-one-all-integers" (func $add-one-all-integers)) - (export "add-one-all-floats" (func $add-one-all-floats)) - (export "add-one-duplicated-s32" (func $add-one-duplicated-s32)) - (export "add-one-distinguished" (func $add-one-distinguished)) - (export "add-one-nested-union" (func $add-one-nested-union)) - (export "add-one-option-in-union" (func $add-one-option-in-union)) - (export "add-one-option-in-option" (func $add-one-option-in-option)) - ) - ) -""" -bindgen('variants', module) - -from .generated.variants import Root, RootImports, imports -from .generated.variants import e -from .generated.variants.imports import host -from .generated.variants.types import Result, Ok, Err, Some - - -class Host(imports.HostHost): - def roundtrip_option(self, a: Optional[float]) -> Optional[int]: - if a: - return int(a) - return None - - def roundtrip_result(self, a: Result[int, float]) -> Result[float, int]: - if isinstance(a, Ok): - return Ok(float(a.value)) - return Err(int(a.value)) - - def roundtrip_enum(self, a: host.E1) -> host.E1: - return a - - def variant_casts(self, a: host.Casts) -> host.Casts: - return a - - def variant_zeros(self, a: host.Zeros) -> host.Zeros: - return a - - def add_one_all_integers(self, num: host.AllIntegers) -> host.AllIntegers: - # Bool - if isinstance(num, host.AllIntegersBool): - assert num.value in (True, False) - return host.AllIntegersBool(not num.value) - # The unsigned numbers - elif isinstance(num, host.AllIntegersU8): - lower_limit = 0 - upper_limit = 2**8 - assert lower_limit <= num.value < upper_limit - return host.AllIntegersU8((num.value + 1) % upper_limit) - elif isinstance(num, host.AllIntegersU16): - lower_limit = 0 - upper_limit = 2**16 - assert lower_limit <= num.value < upper_limit - return host.AllIntegersU16((num.value + 1) % upper_limit) - elif isinstance(num, host.AllIntegersU32): - lower_limit = 0 - upper_limit = 2**32 - assert lower_limit <= num.value < upper_limit - return host.AllIntegersU32((num.value + 1) % upper_limit) - elif isinstance(num, host.AllIntegersU64): - lower_limit = 0 - upper_limit = 2**64 - assert lower_limit <= num.value < upper_limit - return host.AllIntegersU64((num.value + 1) % upper_limit) - # The signed numbers - elif isinstance(num, host.AllIntegersS8): - lower_limit = -2**7 - upper_limit = 2**7 - assert lower_limit <= num.value < upper_limit - return host.AllIntegersS8(num.value + 1) - elif isinstance(num, host.AllIntegersS16): - lower_limit = -2**15 - upper_limit = 2**15 - assert lower_limit <= num.value < upper_limit - return host.AllIntegersS16(num.value + 1) - elif isinstance(num, host.AllIntegersS32): - lower_limit = -2**31 - upper_limit = 2**31 - assert lower_limit <= num.value < upper_limit - return host.AllIntegersS32(num.value + 1) - elif isinstance(num, host.AllIntegersS64): - lower_limit = -2**63 - upper_limit = 2**63 - assert lower_limit <= num.value < upper_limit - return host.AllIntegersS64(num.value + 1) - else: - raise ValueError("Invalid input value!") - - def add_one_all_floats(self, num: host.AllFloats) -> host.AllFloats: - if isinstance(num, host.AllFloatsF32): - return host.AllFloatsF32(num.value + 1) - if isinstance(num, host.AllFloatsF64): - return host.AllFloatsF64(num.value + 1) - else: - raise ValueError("Invalid input value!") - - def add_one_duplicated_s32(self, num: host.DuplicatedS32) -> host.DuplicatedS32: - if isinstance(num, host.DuplicatedS32C1): - return host.DuplicatedS32C1(num.value + 1) - if isinstance(num, host.DuplicatedS32C2): - return host.DuplicatedS32C2(num.value + 1) - if isinstance(num, host.DuplicatedS32C3): - return host.DuplicatedS32C3(num.value + 1) - else: - raise ValueError("Invalid input value!") - - def add_one_distinguished(self, a: host.Distinguished) -> host.Distinguished: - a.value += 1 - return a - - def add_one_nested_union(self, a: host.NestedUnion) -> host.NestedUnion: - if isinstance(a, host.NestedUnionD): - a.value.value += 1 - return host.NestedUnionD(a.value) - if isinstance(a, host.NestedUnionS32): - return host.NestedUnionS32(a.value + 1) - if isinstance(a, host.NestedUnionFloat32): - return host.NestedUnionFloat32(a.value + 1) - else: - raise ValueError("Invalid input value!") - - def add_one_option_in_union(self, a: host.OptionInUnion) -> host.OptionInUnion: - if isinstance(a, host.OptionInUnionO): - if a.value is None: - return host.OptionInUnionO(None) - else: - return host.OptionInUnionO(a.value + 1) - if isinstance(a, host.OptionInUnionI): - return host.OptionInUnionI(a.value + 1) - else: - raise ValueError("Invalid input value!") - - def add_one_option_in_option(self, a: Optional[Some[Optional[int]]]) -> Optional[Some[Optional[int]]]: - if isinstance(a, Some): - if a.value is None: - return Some(None) - else: - return Some(a.value + 1) - if a is None: - return None - else: - raise ValueError("Invalid input value!") - - -def test_bindings(): - store = Store() - wasm = Root(store, RootImports(host=Host())) - - exports = wasm.e() - assert exports.roundtrip_option(store, 1.) == 1 - assert exports.roundtrip_option(store, None) is None - assert exports.roundtrip_option(store, 2.) == 2 - - assert exports.roundtrip_result(store, Ok(2)) == Ok(2) - assert exports.roundtrip_result(store, Ok(4)) == Ok(4) - assert exports.roundtrip_result(store, Err(5)) == Err(5) - - assert exports.roundtrip_enum(store, e.E1.A) == e.E1.A - assert exports.roundtrip_enum(store, e.E1.B) == e.E1.B - - a1, a2, a3, a4, a5, a6 = exports.variant_casts(store, ( - e.C1A(1), - e.C2A(2), - e.C3A(3), - e.C4A(4), - e.C5A(5), - e.C6A(6.), - )) - assert a1 == e.C1A(1) - assert a2 == e.C2A(2) - assert a3 == e.C3A(3) - assert a4 == e.C4A(4) - assert a5 == e.C5A(5) - assert a6 == e.C6A(6.) - - b1, b2, b3, b4, b5, b6 = exports.variant_casts(store, ( - e.C1B(1), - e.C2B(2), - e.C3B(3), - e.C4B(4), - e.C5B(5), - e.C6B(6.), - )) - assert b1 == e.C1B(1) - assert b2 == e.C2B(2) - assert b3 == e.C3B(3) - assert b4 == e.C4B(4) - assert b5 == e.C5B(5) - assert b6 == e.C6B(6.) - - z1, z2, z3, z4 = exports.variant_zeros(store, ( - e.Z1A(1), - e.Z2A(2), - e.Z3A(3.), - e.Z4A(4.), - )) - assert z1 == e.Z1A(1) - assert z2 == e.Z2A(2) - assert z3 == e.Z3A(3.) - assert z4 == e.Z4A(4.) - - # All-Integers - # Booleans - assert exports.add_one_all_integers(store, e.AllIntegersBool(False)) == e.AllIntegersBool(True) - assert exports.add_one_all_integers(store, e.AllIntegersBool(True)) == e.AllIntegersBool(False) - # Unsigned integers - assert exports.add_one_all_integers(store, e.AllIntegersU8(0)) == e.AllIntegersU8(1) - assert exports.add_one_all_integers(store, e.AllIntegersU8(2**8 - 1)) == e.AllIntegersU8(0) - assert exports.add_one_all_integers(store, e.AllIntegersU16(0)) == e.AllIntegersU16(1) - assert exports.add_one_all_integers(store, e.AllIntegersU16(2**16 - 1)) == e.AllIntegersU16(0) - assert exports.add_one_all_integers(store, e.AllIntegersU32(0)) == e.AllIntegersU32(1) - assert exports.add_one_all_integers(store, e.AllIntegersU32(2**32 - 1)) == e.AllIntegersU32(0) - assert exports.add_one_all_integers(store, e.AllIntegersU64(0)) == e.AllIntegersU64(1) - assert exports.add_one_all_integers(store, e.AllIntegersU64(2**64 - 1)) == e.AllIntegersU64(0) - # Signed integers - assert exports.add_one_all_integers(store, e.AllIntegersS8(0)) == e.AllIntegersS8(1) - assert exports.add_one_all_integers(store, e.AllIntegersS8(2**7 - 2)) == e.AllIntegersS8(2**7 - 1) - assert exports.add_one_all_integers(store, e.AllIntegersS8(-8)) == e.AllIntegersS8(-7) - assert exports.add_one_all_integers(store, e.AllIntegersS16(0)) == e.AllIntegersS16(1) - assert exports.add_one_all_integers(store, e.AllIntegersS16(2**15 - 2)) == e.AllIntegersS16(2**15 - 1) - assert exports.add_one_all_integers(store, e.AllIntegersS16(-8)) == e.AllIntegersS16(-7) - assert exports.add_one_all_integers(store, e.AllIntegersS32(0)) == e.AllIntegersS32(1) - assert exports.add_one_all_integers(store, e.AllIntegersS32(2**31 - 2)) == e.AllIntegersS32(2**31 - 1) - assert exports.add_one_all_integers(store, e.AllIntegersS32(-8)) == e.AllIntegersS32(-7) - assert exports.add_one_all_integers(store, e.AllIntegersS64(0)) == e.AllIntegersS64(1) - assert exports.add_one_all_integers(store, e.AllIntegersS64(2**63 - 2)) == e.AllIntegersS64(2**63 - 1) - assert exports.add_one_all_integers(store, e.AllIntegersS64(-8)) == e.AllIntegersS64(-7) - - assert exports.add_one_all_floats(store, e.AllFloatsF32(0.0)) == e.AllFloatsF32(1.0) - assert exports.add_one_all_floats(store, e.AllFloatsF64(0.0)) == e.AllFloatsF64(1.0) - - assert exports.add_one_duplicated_s32(store, e.DuplicatedS32C1(0)) == e.DuplicatedS32C1(1) - assert exports.add_one_duplicated_s32(store, e.DuplicatedS32C2(1)) == e.DuplicatedS32C2(2) - assert exports.add_one_duplicated_s32(store, e.DuplicatedS32C3(2)) == e.DuplicatedS32C3(3) - - assert exports.add_one_distinguished(store, e.DistinguishedS32(1)) == e.DistinguishedS32(2) - assert exports.add_one_distinguished(store, e.DistinguishedFloat32(2.)) == e.DistinguishedFloat32(3.) - - assert exports.add_one_nested_union(store, e.NestedUnionD(e.DistinguishedS32(1))) == e.NestedUnionD(e.DistinguishedS32(2)) - assert exports.add_one_nested_union(store, e.NestedUnionD(e.DistinguishedFloat32(2.))) == e.NestedUnionD(e.DistinguishedFloat32(3.)) - assert exports.add_one_nested_union(store, e.NestedUnionS32(3)) == e.NestedUnionS32(4) - assert exports.add_one_nested_union(store, e.NestedUnionFloat32(4.)) == e.NestedUnionFloat32(5.) - - assert exports.add_one_option_in_union(store, e.OptionInUnionO(1)) == e.OptionInUnionO(2) - assert exports.add_one_option_in_union(store, e.OptionInUnionO(None)) == e.OptionInUnionO(None) - assert exports.add_one_option_in_union(store, e.OptionInUnionI(1)) == e.OptionInUnionI(2) - - assert exports.add_one_option_in_option(store, Some(1)) == Some(2) - assert exports.add_one_option_in_option(store, Some(None)) == Some(None) - assert exports.add_one_option_in_option(store, None) is None diff --git a/wasmtime/bindgen/__init__.py b/wasmtime/bindgen/__init__.py deleted file mode 100644 index 968e9d1b..00000000 --- a/wasmtime/bindgen/__init__.py +++ /dev/null @@ -1,160 +0,0 @@ -from .generated import Root, RootImports, Err, imports -from .generated.imports import streams -from .generated.imports.types import Descriptor, Filesize, ErrorCode, DescriptorType -from .generated.imports.terminal_input import TerminalInput -from .generated.imports.terminal_output import TerminalOutput -from .generated.imports import terminal_stderr -from .generated import types as core_types -from typing import Mapping, Tuple, List, Optional - -import sys -import os -from wasmtime import Store - - -class WasiRandom(imports.HostRandom): - def get_random_bytes(self, len: int) -> bytes: - return os.urandom(len) - - -class WasiStdin(imports.HostStdin): - def get_stdin(self) -> streams.InputStream: - return 0 - - -class WasiStdout(imports.HostStdout): - def get_stdout(self) -> streams.OutputStream: - return 1 - - -class WasiStderr(imports.HostStderr): - def get_stderr(self) -> streams.OutputStream: - return 2 - - -class WasiPreopens(imports.HostPreopens): - def get_directories(self) -> List[Tuple[Descriptor, str]]: - return [] - - -class WasiStreams(imports.HostStreams): - def drop_input_stream(self, this: streams.InputStream) -> None: - return None - - def write(self, this: streams.OutputStream, buf: bytes) -> core_types.Result[Tuple[int, streams.StreamStatus], None]: - if this == 1: - sys.stdout.buffer.write(buf) - elif this == 2: - sys.stderr.buffer.write(buf) - else: - raise NotImplementedError - return core_types.Ok((len(buf), streams.StreamStatus.OPEN)) - - def blocking_write(self, this: streams.OutputStream, buf: bytes) -> core_types.Result[Tuple[int, streams.StreamStatus], None]: - return self.write(this, buf) - - def drop_output_stream(self, this: streams.OutputStream) -> None: - return None - - -class WasiEnvironment(imports.HostEnvironment): - def get_environment(self) -> List[Tuple[str, str]]: - return [] - - -class WasiTypes(imports.HostTypes): - def write_via_stream(self, this: Descriptor, offset: Filesize) -> core_types.Result[streams.OutputStream, ErrorCode]: - raise NotImplementedError - - def append_via_stream(self, this: Descriptor) -> core_types.Result[streams.OutputStream, ErrorCode]: - raise NotImplementedError - - def get_type(self, this: Descriptor) -> core_types.Result[DescriptorType, ErrorCode]: - raise NotImplementedError - - def drop_descriptor(self, this: Descriptor) -> None: - raise NotImplementedError - - -class WasiExit(imports.HostExit): - def exit(self, status: core_types.Result[None, None]) -> None: - raise NotImplementedError - - -class WasiTerminalInput(imports.HostTerminalInput): - def drop_terminal_input(self, this: TerminalInput) -> None: - pass - - -class WasiTerminalOutput(imports.HostTerminalOutput): - def drop_terminal_output(self, this: TerminalOutput) -> None: - pass - - -class WasiTerminalStdin(imports.HostTerminalStdin): - def get_terminal_stdin(self) -> Optional[TerminalInput]: - if sys.stdin.isatty(): - return sys.stdin.fileno() - return None - - -class WasiTerminalStdout(imports.HostTerminalStdout): - def get_terminal_stdout(self) -> Optional[TerminalOutput]: - if sys.stdout.isatty(): - return sys.stdout.fileno() - return None - - -class WasiTerminalStderr(imports.HostTerminalStderr): - def get_terminal_stderr(self) -> Optional[terminal_stderr.TerminalOutput]: - if sys.stderr.isatty(): - return sys.stderr.fileno() - return None - - -root = None -store = None - - -def init() -> Tuple[Root, Store]: - global store - global root - if root is None: - store = Store() - root = Root(store, RootImports(WasiStreams(), - WasiTypes(), - WasiPreopens(), - WasiRandom(), - WasiEnvironment(), - WasiExit(), - WasiStdin(), - WasiStdout(), - WasiStderr(), - WasiTerminalInput(), - WasiTerminalOutput(), - WasiTerminalStdin(), - WasiTerminalStdout(), - WasiTerminalStderr())) - return root, store - - -# Generates Python bindings for the given component. -# -# The `name` provided is used as the name of the `component` binary provided. -# The `component` argument is expected to be the binary representation of a -# component. -# -# This function returns a mapping of filename to contents of files that are -# generated to represent the Python bindings here. -def generate(name: str, component: bytes) -> Mapping[str, bytes]: - root, store = init() - result = root.generate(store, name, component) - if isinstance(result, Err): - raise RuntimeError(result.value) - ret = {} - for name, contents in result.value: - ret[name] = contents - return ret - - -__all__ = ['generate'] diff --git a/wasmtime/bindgen/__main__.py b/wasmtime/bindgen/__main__.py deleted file mode 100644 index 85aa9965..00000000 --- a/wasmtime/bindgen/__main__.py +++ /dev/null @@ -1,40 +0,0 @@ -# This is a small utility that developers can use after installing the -# `wasmtime` package by using: -# -# python3 -m wasmtime.bindgen the-component.wasm --out-dir ./here -# -# This intentionally isn't installed as a global script at this time since I -# don't know of a great name for such a script. Otherwise it's at least -# accessible and usable after a `pip3 install wasmtime`. - -from wasmtime.bindgen import generate -from pathlib import Path -import argparse - - -def main() -> None: - parser = argparse.ArgumentParser( - prog='Wasmtime Bindings Generation', - description='Generate Python bindings for a component') - parser.add_argument('filename') - parser.add_argument('--out-dir', required=True) - parser.add_argument('--name') - args = parser.parse_args() - - name = Path(args.filename).stem - if args.name: - name = args.name - - with open(args.filename, 'rb') as f: - contents = f.read() - files = generate(name, contents) - for name, contents in files.items(): - dst = Path(args.out_dir).joinpath(name) - if not dst.parent.exists(): - dst.parent.mkdir(parents=True) - dst.write_bytes(contents) - print("Generating", dst) - - -if __name__ == '__main__': - main() diff --git a/wasmtime/loader.py b/wasmtime/loader.py deleted file mode 100644 index 612f0f0e..00000000 --- a/wasmtime/loader.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -This module is a custom loader for Python which enables importing wasm files -directly into Python programs simply through usage of the `import` statement. - -You can import this module with `import wasmtime.loader` and then afterwards you -can `import your_wasm_file` which will automatically compile and instantiate -`your_wasm_file.wasm` and hook it up into Python's module system. -""" - -from typing import NoReturn, Iterator, Mapping, Dict -import io -import re -import sys -import struct -from pathlib import Path -from importlib import import_module -from importlib.abc import Loader, MetaPathFinder -from importlib.machinery import ModuleSpec -if sys.version_info[:2] >= (3, 11): - from importlib.resources.abc import ResourceReader -else: - from importlib.abc import ResourceReader - -from wasmtime import Module, Linker, Store, WasiConfig -from wasmtime import Func, Table, Global, Memory -from wasmtime import wat2wasm, bindgen - - -predefined_modules = [] -store = Store() -linker = Linker(store.engine) -# TODO: how to configure wasi? -store.set_wasi(WasiConfig()) -predefined_modules.append("wasi_snapshot_preview1") -predefined_modules.append("wasi_unstable") -linker.define_wasi() -linker.allow_shadowing = True - - -_component_bindings: Dict[Path, Mapping[str, bytes]] = {} - - -class _CoreWasmLoader(Loader): - def create_module(self, spec): # type: ignore - return None # use default module creation semantics - - def exec_module(self, module): # type: ignore - wasm_module = Module.from_file(store.engine, module.__spec__.origin) - - for wasm_import in wasm_module.imports: - module_name = wasm_import.module - if module_name in predefined_modules: - break - field_name = wasm_import.name - imported_module = import_module(module_name) - item = imported_module.__dict__[field_name] - if not isinstance(item, (Func, Table, Global, Memory)): - item = Func(store, wasm_import.type, item) - linker.define(store, module_name, field_name, item) - - exports = linker.instantiate(store, wasm_module).exports(store) - for index, wasm_export in enumerate(wasm_module.exports): - item = exports.by_index[index] - if isinstance(item, Func): - # Partially apply `item` to `store`. - item = (lambda func: lambda *args: func(store, *args))(item) - module.__dict__[wasm_export.name] = item - - -class _PythonLoader(Loader): - def __init__(self, resource_reader: ResourceReader): - self.resource_reader = resource_reader - - def create_module(self, spec): # type: ignore - return None # use default module creation semantics - - def exec_module(self, module): # type: ignore - origin = Path(module.__spec__.origin) - for component_path, component_files in _component_bindings.items(): - try: - relative_path = str(origin.relative_to(component_path)) - except ValueError: - continue - exec(component_files[relative_path], module.__dict__) - break - - def get_resource_reader(self, fullname: str) -> ResourceReader: - return self.resource_reader - - -class _BindingsResourceReader(ResourceReader): - def __init__(self, origin: Path): - self.resources = _component_bindings[origin] - - def contents(self) -> Iterator[str]: - return iter(self.resources.keys()) - - def is_resource(self, path: str) -> bool: - return path in self.resources - - def open_resource(self, resource: str) -> io.BytesIO: - if resource not in self.resources: - raise FileNotFoundError - return io.BytesIO(self.resources[resource]) - - def resource_path(self, resource: str) -> NoReturn: - raise FileNotFoundError # all of our resources are virtual - - -class _WasmtimeMetaPathFinder(MetaPathFinder): - @staticmethod - def is_component(path: Path, *, binary: bool = True) -> bool: - if binary: - with path.open("rb") as f: - preamble = f.read(8) - if len(preamble) != 8: - return False - magic, version, layer = struct.unpack("<4sHH", preamble) - if magic != b"\x00asm": - return False - if layer != 1: # 0 for core wasm, 1 for components - return False - return True - else: - contents = path.read_text() - # Not strictly correct, but should be good enough for most cases where - # someone is using a component in the textual format. - return re.search(r"\s*\(\s*component", contents) is not None - - @staticmethod - def load_component(path: Path, *, binary: bool = True) -> Mapping[str, bytes]: - component = path.read_bytes() - if not binary: - component = wat2wasm(component) - return bindgen.generate("root", component) - - def find_spec(self, fullname, path, target=None): # type: ignore - modname = fullname.split(".")[-1] - if path is None: - path = sys.path - for entry in map(Path, path): - # Is the requested spec a Python module from generated bindings? - if entry in _component_bindings: - # Create a spec with a virtual origin pointing into generated bindings. - origin = entry / (modname + ".py") - return ModuleSpec(fullname, _PythonLoader(_BindingsResourceReader(entry)), - origin=origin) - # Is the requested spec a core Wasm module or a Wasm component? - for suffix in (".wasm", ".wat"): - is_binary = (suffix == ".wasm") - origin = entry / (modname + suffix) - if origin.exists(): - # Since the origin is on the filesystem, ensure it has an absolute path. - origin = origin.resolve() - if self.is_component(origin, binary=is_binary): - # Generate bindings for the component and remember them for later. - _component_bindings[origin] = self.load_component(origin, binary=is_binary) - # Create a spec with a virtual origin pointing into generated bindings, - # specifically the `__init__.py` file with the code for the package itself. - spec = ModuleSpec(fullname, _PythonLoader(_BindingsResourceReader(origin)), - origin=origin / '__init__.py', is_package=True) - # Set the search path to the origin. Importlib will provide both the origin - # and the search locations back to this function as-is, even regardless of - # types, but try to follow existing Python conventions. The `origin` will - # be a key in `_component_bindings`. - spec.submodule_search_locations = [origin] - return spec - else: - # Create a spec with a filesystem origin pointing to thg core Wasm module. - return ModuleSpec(fullname, _CoreWasmLoader(), origin=origin) - return None - - -sys.meta_path.append(_WasmtimeMetaPathFinder()) From 120f4ad63273f191c7aa30d4c9473888d6df7997 Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Nov 2025 13:31:30 -0800 Subject: [PATCH 2/3] Remove some more metadata --- pyproject.toml | 7 ------- pytest.ini | 5 ++--- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 963e9183..6cc74bc7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -61,7 +61,6 @@ testing = [ include-package-data = true packages = [ "wasmtime", - "wasmtime.bindgen", ] [tool.setuptools.package-data] @@ -73,12 +72,6 @@ packages = [ "*-*/*.so", "py.typed", ] -"wasmtime.bindgen" = [ - # WebAssmbly modules, python bindings. Generated by ci/build-rust.py - "generated/*.py", - "generated/*.wasm", - "generated/imports/*.py", -] [tool.setuptools-git-versioning] # https://setuptools-git-versioning.readthedocs.io/en/stable/options/index.html diff --git a/pytest.ini b/pytest.ini index 11f60732..141e0f0c 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,5 +1,4 @@ [pytest] -addopts = --doctest-modules --mypy --ignore-glob=tests/bindgen/*/app.py -norecursedirs = +addopts = --doctest-modules --mypy +norecursedirs = ci/_custom_build - tests/bindgen/generated/* From 290640976bda301c96a5dcabb8df8dbbbf3e3e2e Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Wed, 5 Nov 2025 13:34:31 -0800 Subject: [PATCH 3/3] Remove build-rust build config --- ci/_custom_build/backend.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/ci/_custom_build/backend.py b/ci/_custom_build/backend.py index a85e517f..78dbb124 100644 --- a/ci/_custom_build/backend.py +++ b/ci/_custom_build/backend.py @@ -85,9 +85,5 @@ def build_wheel(wheel_directory, config_settings=None, metadata_directory=None): [sys.executable, 'ci/download-wasmtime.py', *download_args], check=True, ) - subprocess.run( - [sys.executable, 'ci/build-rust.py'], - check=True, - ) return build_meta_orig.build_wheel(wheel_directory, config_settings, metadata_directory)