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/_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) 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/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/* 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())