diff --git a/__wasm/wit-bindgen-sample/.gitignore b/__wasm/wit-bindgen-sample/.gitignore deleted file mode 100644 index b49de6f..0000000 --- a/__wasm/wit-bindgen-sample/.gitignore +++ /dev/null @@ -1 +0,0 @@ -wit-bindgen diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/Dockerfile b/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/Dockerfile new file mode 100644 index 0000000..93f90d0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/Dockerfile @@ -0,0 +1,28 @@ +# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/python-3/.devcontainer/base.Dockerfile + +# [Choice] Python version (use -bullseye variants on local arm64/Apple Silicon): 3, 3.10, 3.9, 3.8, 3.7, 3.6, 3-bullseye, 3.10-bullseye, 3.9-bullseye, 3.8-bullseye, 3.7-bullseye, 3.6-bullseye, 3-buster, 3.10-buster, 3.9-buster, 3.8-buster, 3.7-buster, 3.6-buster +ARG VARIANT="3.10-bullseye" +FROM mcr.microsoft.com/vscode/devcontainers/python:0-${VARIANT} + +# [Choice] Node.js version: none, lts/*, 16, 14, 12, 10 +ARG NODE_VERSION="none" +RUN if [ "${NODE_VERSION}" != "none" ]; then su vscode -c "umask 0002 && . /usr/local/share/nvm/nvm.sh && nvm install ${NODE_VERSION} 2>&1"; fi + +# [Optional] If your pip requirements rarely change, uncomment this section to add them to the image. +# COPY requirements.txt /tmp/pip-tmp/ +# RUN pip3 --disable-pip-version-check --no-cache-dir install -r /tmp/pip-tmp/requirements.txt \ +# && rm -rf /tmp/pip-tmp + +# [Optional] Uncomment this section to install additional OS packages. +# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \ +# && apt-get -y install --no-install-recommends + +# [Optional] Uncomment this line to install global node packages. +# RUN su vscode -c "source /usr/local/share/nvm/nvm.sh && npm install -g " 2>&1 + +RUN cd /opt && curl -L https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-16/wasi-sdk-16.0-linux.tar.gz \ + | tar -xz +ENV WASI_SDK_PATH=/opt/wasi-sdk-16.0 + +RUN echo 'alias clang=/opt/wasi-sdk-16.0/bin/clang' >> /etc/bash.bashrc +RUN echo 'alias clang++=/opt/wasi-sdk-16.0/bin/clang++' >> /etc/bash.bashrc diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/devcontainer.json b/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/devcontainer.json new file mode 100644 index 0000000..2e87390 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/devcontainer.json @@ -0,0 +1,51 @@ +// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at: +// https://github.com/microsoft/vscode-dev-containers/tree/v0.234.0/containers/python-3 +{ + "name": "Dev", + "build": { + "dockerfile": "Dockerfile", + "context": "..", + "args": { + // Update 'VARIANT' to pick a Python version: 3, 3.10, 3.9, 3.8, 3.7, 3.6 + // Append -bullseye or -buster to pin to an OS version. + // Use -bullseye variants on local on arm64/Apple Silicon. + "VARIANT": "3.9", + // Options + "NODE_VERSION": "16" + } + }, + + // Set *default* container specific settings.json values on container create. + "settings": { + "python.defaultInterpreterPath": "/usr/local/bin/python", + "python.linting.enabled": true, + "python.linting.pylintEnabled": true, + "python.formatting.autopep8Path": "/usr/local/py-utils/bin/autopep8", + "python.formatting.blackPath": "/usr/local/py-utils/bin/black", + "python.formatting.yapfPath": "/usr/local/py-utils/bin/yapf", + "python.linting.banditPath": "/usr/local/py-utils/bin/bandit", + "python.linting.flake8Path": "/usr/local/py-utils/bin/flake8", + "python.linting.mypyPath": "/usr/local/py-utils/bin/mypy", + "python.linting.pycodestylePath": "/usr/local/py-utils/bin/pycodestyle", + "python.linting.pydocstylePath": "/usr/local/py-utils/bin/pydocstyle", + "python.linting.pylintPath": "/usr/local/py-utils/bin/pylint" + }, + + // Add the IDs of extensions you want installed when the container is created. + "extensions": [ + "ms-python.python", + "ms-python.vscode-pylance" + ], + + // Use 'forwardPorts' to make a list of ports inside the container available locally. + // "forwardPorts": [], + + // Use 'postCreateCommand' to run commands after the container is created. + "postCreateCommand": "bash .devcontainer/finalize.sh", + + // Comment out to connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root. + "remoteUser": "vscode", + "features": { + "rust": "latest" + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/finalize.sh b/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/finalize.sh new file mode 100644 index 0000000..0fa5655 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/.devcontainer/finalize.sh @@ -0,0 +1,16 @@ +printf "Running 'postCreateCommand' Script\n" + +# Install Rust Targets +printf "Installing Rust Targets\n" +rustup update stable --no-self-update +rustup default stable +rustup target add wasm32-unknown-unknown +rustup target add wasm32-wasi + +# Install Python stuff +printf "Installing Python Dependencies" +pip install mypy wasmtime + +# Install NPM dependencies +printf "Installing NPM Dependencies" +cd crates/gen-js && npm install diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/.github/workflows/main.yml b/__wasm/wit-bindgen-sample/wit-bindgen/.github/workflows/main.yml new file mode 100644 index 0000000..09754a0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/.github/workflows/main.yml @@ -0,0 +1,91 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] +defaults: + run: + shell: bash + +# Cancel any in-flight jobs for the same PR/branch so there's only one active +# at a time +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + test: + name: Test + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + mode: [debug, release] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v2 + - name: Install Rust + run: rustup update stable --no-self-update && rustup default stable + - name: Install wasm32-unknown-unknown target + run: rustup target add wasm32-unknown-unknown + - name: Install wasm32-wasi target + run: rustup target add wasm32-wasi + + - run: | + curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-16/wasi-sdk-16.0-linux.tar.gz -L | tar xzvf - + echo "WASI_SDK_PATH=`pwd`/wasi-sdk-16.0" >> $GITHUB_ENV + if : matrix.os == 'ubuntu-latest' + - run: | + curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-16/wasi-sdk-16.0-macos.tar.gz -L | tar xzvf - + echo "WASI_SDK_PATH=`pwd`/wasi-sdk-16.0" >> $GITHUB_ENV + if : matrix.os == 'macos-latest' + - run: | + curl https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-16/wasi-sdk-16.0-mingw.tar.gz -L | tar xzvf - + echo "WASI_SDK_PATH=`pwd`/wasi-sdk-16.0" >> $GITHUB_ENV + if : matrix.os == 'windows-latest' + + - uses: actions/setup-node@v2 + with: + node-version: '16' + - name: Install NPM packages + run: npm install + working-directory: crates/gen-js + - uses: actions/setup-python@v1 + with: + python-version: 3.9 + - run: pip install mypy wasmtime + - if: matrix.mode == 'release' + name: Test release build + run: cargo test --workspace --release + - if: matrix.mode != 'release' + name: Test debug build + run: cargo test --workspace + + rustfmt: + name: Rustfmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Install Rust + run: rustup update stable && rustup default stable && rustup component add rustfmt + - name: Format source code + run: cargo fmt -- --check + + demo: + name: Build wit-bindgen demo + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - run: rustup update stable --no-self-update && rustup default stable + - run: rustup target add wasm32-unknown-unknown + - run: npm install + working-directory: crates/wit-bindgen-demo + - run: ./crates/wit-bindgen-demo/build.sh + - uses: JamesIves/github-pages-deploy-action@4.1.4 + with: + branch: gh-pages + folder: static + single-commit: true + if: github.event_name == 'push' && github.ref == 'refs/heads/main' diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/.gitignore b/__wasm/wit-bindgen-sample/wit-bindgen/.gitignore new file mode 100644 index 0000000..3f7971e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/.gitignore @@ -0,0 +1,6 @@ +target/ +static +package-lock.json +node_modules +ace +*.wasm diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/Cargo.lock b/__wasm/wit-bindgen-sample/wit-bindgen/Cargo.lock new file mode 100644 index 0000000..8a25426 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/Cargo.lock @@ -0,0 +1,2259 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "addr2line" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ecd88a8c8378ca913a680cd98f0f13ac67383d35993f86c90a70e3f137816b" +dependencies = [ + "gimli", +] + +[[package]] +name = "adler" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" + +[[package]] +name = "ahash" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" +dependencies = [ + "getrandom", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e37cfd5e7657ada45f742d6e99ca5788580b5c529dc78faf11ece6dc702656f" +dependencies = [ + "memchr", +] + +[[package]] +name = "ambient-authority" +version = "0.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec8ad6edb4840b78c5c3d88de606b22252d552b55f3a4699fbb10fc070ec3049" + +[[package]] +name = "ansi_term" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" +dependencies = [ + "winapi", +] + +[[package]] +name = "anyhow" +version = "1.0.58" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bb07d2053ccdbe10e2af2995a2f116c1330396493dc1269f6a91d0ae82e19704" + +[[package]] +name = "async-trait" +version = "0.1.56" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96cf8829f67d2eab0b2dfa42c5d0ef737e0724e4a82b01b3e292456202b19716" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "atty" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" +dependencies = [ + "hermit-abi 0.1.19", + "libc", + "winapi", +] + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "backtrace" +version = "0.3.65" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11a17d453482a265fd5f8479f2a3f405566e6ca627837aaddb85af8b1ab8ef61" +dependencies = [ + "addr2line", + "cc", + "cfg-if", + "libc", + "miniz_oxide", + "object", + "rustc-demangle", +] + +[[package]] +name = "base64" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "904dfeac50f3cdaba28fc6f57fdcddb75f49ed61346676a78c4ffe55877802fd" + +[[package]] +name = "bincode" +version = "1.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1f45e9417d87227c7a56d22e471c6206462cba514c7590c09aff4cf6d1ddcad" +dependencies = [ + "serde", +] + +[[package]] +name = "bitflags" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" + +[[package]] +name = "block-buffer" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4152116fd6e9dadb291ae18fc1ec3575ed6d84c29642d97890f4b4a3417297e4" +dependencies = [ + "generic-array", +] + +[[package]] +name = "bstr" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba3569f383e8f1598449f1a423e72e99569137b47740b1da11ef19af3d5c3223" +dependencies = [ + "memchr", +] + +[[package]] +name = "byteorder" +version = "1.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610" + +[[package]] +name = "cap-fs-ext" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e54b86398b5852ddd45784b1d9b196b98beb39171821bad4b8b44534a1e87927" +dependencies = [ + "cap-primitives", + "cap-std", + "io-lifetimes", + "winapi", +] + +[[package]] +name = "cap-primitives" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb8fca3e81fae1d91a36e9784ca22a39ef623702b5f7904d89dc31f10184a178" +dependencies = [ + "ambient-authority", + "errno", + "fs-set-times", + "io-extras", + "io-lifetimes", + "ipnet", + "maybe-owned", + "rustix", + "winapi", + "winapi-util", + "winx", +] + +[[package]] +name = "cap-rand" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3b27294116983d706f4c8168f6d10c84f9f5daed0c28bc7d0296cf16bcf971" +dependencies = [ + "ambient-authority", + "rand", +] + +[[package]] +name = "cap-std" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2247568946095c7765ad2b441a56caffc08027734c634a6d5edda648f04e32eb" +dependencies = [ + "cap-primitives", + "io-extras", + "io-lifetimes", + "ipnet", + "rustix", +] + +[[package]] +name = "cap-time-ext" +version = "0.24.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50472b6ebc302af0401fa3fb939694cd8ff00e0d4c9182001e434fc822ab83a" +dependencies = [ + "cap-primitives", + "once_cell", + "rustix", + "winx", +] + +[[package]] +name = "cc" +version = "1.0.73" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2fff2a6927b3bb87f9595d67196a70493f627687a71d87a0d692242c33f58c11" +dependencies = [ + "jobserver", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clap" +version = "2.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" +dependencies = [ + "ansi_term", + "atty", + "bitflags", + "strsim 0.8.0", + "textwrap 0.11.0", + "unicode-width", + "vec_map", +] + +[[package]] +name = "clap" +version = "3.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1fe12880bae935d142c8702d500c63a4e8634b6c3c57ad72bf978fc7b6249a" +dependencies = [ + "atty", + "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "once_cell", + "strsim 0.10.0", + "termcolor", + "textwrap 0.15.0", +] + +[[package]] +name = "clap_derive" +version = "3.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed6db9e867166a43a53f7199b5e4d1f522a1e5bd626654be263c999ce59df39a" +dependencies = [ + "heck 0.4.0", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87eba3c8c7f42ef17f6c659fc7416d0f4758cd3e58861ee63c5fa4a4dde649e4" +dependencies = [ + "os_str_bytes", +] + +[[package]] +name = "cpp_demangle" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eeaa953eaad386a53111e47172c2fedba671e5684c8dd601a5f474f4f118710f" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "cpufeatures" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59a6001667ab124aebae2a495118e11d30984c3a653e99d86d58971708cf5e4b" +dependencies = [ + "libc", +] + +[[package]] +name = "cranelift-bforest" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899dc8d22f7771e7f887fb8bafa0c0d3ac1dea0c7f2c0ded6e20a855a7a1e890" +dependencies = [ + "cranelift-entity", +] + +[[package]] +name = "cranelift-codegen" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8dbdc03f695cf67e7bc45da57155528274f47390b85060af8107eb304ef167c4" +dependencies = [ + "cranelift-bforest", + "cranelift-codegen-meta", + "cranelift-codegen-shared", + "cranelift-entity", + "cranelift-isle", + "gimli", + "log", + "regalloc2", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-codegen-meta" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ea66cbba3eb7fcb3ec9f42839a6d381bd40cf97780397e7167daf9725d4ffa0" +dependencies = [ + "cranelift-codegen-shared", +] + +[[package]] +name = "cranelift-codegen-shared" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712fbebd119a476f59122b4ba51fdce893a66309b5c92bd5506bfb11a0587496" + +[[package]] +name = "cranelift-entity" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cb8b95859c4e14c9e860db78d596a904fdbe9261990233b62bd526346cb56cb" +dependencies = [ + "serde", +] + +[[package]] +name = "cranelift-frontend" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7b91b19a7d1221a73f190c0e865c12be77a84f661cac89abfd4ab5820142886" +dependencies = [ + "cranelift-codegen", + "log", + "smallvec", + "target-lexicon", +] + +[[package]] +name = "cranelift-isle" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "86d4f53bc86fb458e59c695c6a95ce8346e6a8377ee7ffc058e3ac08b5f94cb1" + +[[package]] +name = "cranelift-native" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "592f035d0ed41214dfeeb37abd536233536a27be6b4c2d39f380cd402f0cff4f" +dependencies = [ + "cranelift-codegen", + "libc", + "target-lexicon", +] + +[[package]] +name = "cranelift-wasm" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "295add6bf0b527a8bc50d02e31ff878585d2d2db53cb7e8754d6d82b84480086" +dependencies = [ + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "itertools", + "log", + "smallvec", + "wasmparser 0.85.0", + "wasmtime-types", +] + +[[package]] +name = "crc32fast" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "crossbeam-channel" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c02a4d71819009c192cf4872265391563fd6a84c81ff2c0f2a7026ca4c1d85c" +dependencies = [ + "cfg-if", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6455c0ca19f0d2fbf751b908d5c55c1f5cbc65e03c4225427254b46890bdde1e" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "07db9d94cbd326813772c968ccd25999e5f8ae22f4f8d1b11effa37ef6ce281d" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "once_cell", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d82ee10ce34d7bc12c2122495e7593a9c41347ecdd64185af4ecf72cb1a7f83" +dependencies = [ + "cfg-if", + "once_cell", +] + +[[package]] +name = "ctor" +version = "0.1.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f877be4f7c9f246b183111634f75baa039715e3f46ce860677d3b19a69fb229c" +dependencies = [ + "quote", + "syn", +] + +[[package]] +name = "diff" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e25ea47919b1560c4e3b7fe0aaab9becf5b84a10325ddf7db0f0ba5e1026499" + +[[package]] +name = "digest" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3dd60d1080a57a05ab032377049e0591415d2b31afd7028356dbf3cc6dcb066" +dependencies = [ + "generic-array", +] + +[[package]] +name = "directories-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "339ee130d97a610ea5a5872d2bbb130fdf68884ff09d3028b81bec8a1ac23bbc" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-next" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" +dependencies = [ + "cfg-if", + "dirs-sys-next", +] + +[[package]] +name = "dirs-sys-next" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" +dependencies = [ + "libc", + "redox_users", + "winapi", +] + +[[package]] +name = "either" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" + +[[package]] +name = "env_logger" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b2cf0344971ee6c64c31be0d530793fba457d322dfec2810c453d0ef228f9c3" +dependencies = [ + "atty", + "humantime", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "errno" +version = "0.2.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" +dependencies = [ + "errno-dragonfly", + "libc", + "winapi", +] + +[[package]] +name = "errno-dragonfly" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" +dependencies = [ + "cc", + "libc", +] + +[[package]] +name = "fallible-iterator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4443176a9f2c162692bd3d352d745ef9413eec5782a80d8fd6f8a1ac692a07f7" + +[[package]] +name = "file-per-thread-logger" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21e16290574b39ee41c71aeb90ae960c504ebaf1e2a1c87bd52aa56ed6e1a02f" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "filetime" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0408e2626025178a6a7f7ffc05a25bc47103229f19c113755de7bf63816290c" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "winapi", +] + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "fs-set-times" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7df62ee66ee2d532ea8d567b5a3f0d03ecd64636b98bad5be1e93dcc918b92aa" +dependencies = [ + "io-lifetimes", + "rustix", + "winapi", +] + +[[package]] +name = "futures-core" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c09fd04b7e4073ac7156a9539b57a484a8ea920f79c7c675d05d289ab6110d3" + +[[package]] +name = "futures-macro" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33c1e13800337f4d4d7a316bf45a567dbcb6ffe087f16424852d97e97a91f512" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "futures-task" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c66a976bf5909d801bbef33416c41372779507e7a6b3a5e25e4749c58f776a" + +[[package]] +name = "futures-util" +version = "0.3.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b7abd5d659d9b90c8cba917f6ec750a74e2dc23902ef9cd4cc8c8b22e6036a" +dependencies = [ + "futures-core", + "futures-macro", + "futures-task", + "pin-project-lite", + "pin-utils", + "slab", +] + +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + +[[package]] +name = "generic-array" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd48d33ec7f05fbfa152300fdad764757cbded343c1aa1cff2fbaf4134851803" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "getrandom" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6" +dependencies = [ + "cfg-if", + "libc", + "wasi", +] + +[[package]] +name = "gimli" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78cc372d058dcf6d5ecd98510e7fbc9e5aec4d21de70f65fea8fecebcd881bd4" +dependencies = [ + "fallible-iterator", + "indexmap", + "stable_deref_trait", +] + +[[package]] +name = "glob" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b919933a397b79c37e33b77bb2aa3dc8eb6e165ad809e58ff75bc7db2e34574" + +[[package]] +name = "globset" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a1e17342619edbc21a964c2afbeb6c820c6a2560032872f397bb97ea127bd0a" +dependencies = [ + "aho-corasick", + "bstr", + "fnv", + "log", + "regex", +] + +[[package]] +name = "hashbrown" +version = "0.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab5ef0d4909ef3724cc8cce6ccc8572c5c817592e9285f5464f8e86f8bd3726e" +dependencies = [ + "ahash", +] + +[[package]] +name = "hashbrown" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0d4cf898abf0081f964436dc980e96670a0f36863e4b83aaacdb65c9d7ccc3" + +[[package]] +name = "heck" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" +dependencies = [ + "unicode-segmentation", +] + +[[package]] +name = "heck" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" + +[[package]] +name = "hermit-abi" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" +dependencies = [ + "libc", +] + +[[package]] +name = "hermit-abi" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c71a9c6ee0d06d82b89ae2674096d2ba1b832a5ee0b1c9a5a6b013deeab5b11f" +dependencies = [ + "libc", +] + +[[package]] +name = "humantime" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" + +[[package]] +name = "id-arena" +version = "2.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25a2bc672d1148e28034f176e01fffebb08b35768468cc954630da77a1449005" + +[[package]] +name = "ignore" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "713f1b139373f96a2e0ce3ac931cd01ee973c3c5dd7c40c0c2efe96ad2b6751d" +dependencies = [ + "crossbeam-utils", + "globset", + "lazy_static", + "log", + "memchr", + "regex", + "same-file", + "thread_local", + "walkdir", + "winapi-util", +] + +[[package]] +name = "indexmap" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10a35a97730320ffe8e2d410b5d3b69279b98d2c14bdb8b70ea89ecf7888d41e" +dependencies = [ + "autocfg", + "hashbrown 0.12.1", + "serde", +] + +[[package]] +name = "io-extras" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0c937cc9891c12eaa8c63ad347e4a288364b1328b924886970b47a14ab8f8f8" +dependencies = [ + "io-lifetimes", + "winapi", +] + +[[package]] +name = "io-lifetimes" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec58677acfea8a15352d42fc87d11d63596ade9239e0a7c9352914417515dbe6" +dependencies = [ + "libc", + "winapi", +] + +[[package]] +name = "ipnet" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b" + +[[package]] +name = "is-terminal" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7c89a757e762896bdbdfadf2860d0f8b0cea5e363d8cf3e7bdfeb63d1d976352" +dependencies = [ + "hermit-abi 0.2.1", + "io-lifetimes", + "rustix", + "winapi", +] + +[[package]] +name = "itertools" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a9a9d19fa1e79b6215ff29b9d6880b706147f16e9b1dbb1e4e5947b5b02bc5e3" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "112c678d4050afce233f4f2852bb2eb519230b3cf12f33585275537d7e41578d" + +[[package]] +name = "ittapi-rs" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f712648a1ad72fbfb7adc2772c331e8d90f022f8cf30cbabefba2878dd3172b0" +dependencies = [ + "cc", +] + +[[package]] +name = "jobserver" +version = "0.1.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af25a77299a7f711a01975c35a6a424eb6862092cc2d6c72c4ed6cbc56dfc1fa" +dependencies = [ + "libc", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "leb128" +version = "0.2.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67" + +[[package]] +name = "libc" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836" + +[[package]] +name = "linux-raw-sys" +version = "0.0.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5284f00d480e1c39af34e72f8ad60b94f47007e3481cd3b731c1d67190ddc7b7" + +[[package]] +name = "log" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "mach" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b823e83b2affd8f40a9ee8c29dbc56404c1e34cd2710921f2801e2cf29527afa" +dependencies = [ + "libc", +] + +[[package]] +name = "maybe-owned" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4facc753ae494aeb6e3c22f839b158aebd4f9270f55cd3c79906c45476c47ab4" + +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + +[[package]] +name = "memfd" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6627dc657574b49d6ad27105ed671822be56e0d2547d413bfbf3e8d8fa92e7a" +dependencies = [ + "libc", +] + +[[package]] +name = "memoffset" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" +dependencies = [ + "autocfg", +] + +[[package]] +name = "miniz_oxide" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f5c75688da582b8ffc1f1799e9db273f32133c49e048f614d22ec3256773ccc" +dependencies = [ + "adler", +] + +[[package]] +name = "more-asserts" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7843ec2de400bcbc6a6328c958dc38e5359da6e93e72e37bc5246bf1ae776389" + +[[package]] +name = "num_cpus" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1" +dependencies = [ + "hermit-abi 0.1.19", + "libc", +] + +[[package]] +name = "object" +version = "0.28.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e42c982f2d955fac81dd7e1d0e1426a7d702acd9c98d19ab01083a6a0328c424" +dependencies = [ + "crc32fast", + "hashbrown 0.11.2", + "indexmap", + "memchr", +] + +[[package]] +name = "once_cell" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709cef83f0c1f58f666e746a08b21e0085f7440fa6a29cc194d68aac97a4225" + +[[package]] +name = "opaque-debug" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" + +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + +[[package]] +name = "output_vt100" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628223faebab4e3e40667ee0b2336d34a5b960ff60ea743ddfdbcf7770bcfb66" +dependencies = [ + "winapi", +] + +[[package]] +name = "paste" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c520e05135d6e763148b6426a837e239041653ba7becd2e538c076c738025fc" + +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + +[[package]] +name = "ppv-lite86" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872" + +[[package]] +name = "pretty_assertions" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89f989ac94207d048d92db058e4f6ec7342b0971fc58d1271ca148b799b3563" +dependencies = [ + "ansi_term", + "ctor", + "diff", + "output_vt100", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + +[[package]] +name = "proc-macro2" +version = "1.0.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd96a1e8ed2596c337f8eae5f24924ec83f5ad5ab21ea8e455d3566c69fbcaf7" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "psm" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "871372391786ccec00d3c5d3d6608905b3d4db263639cfe075d3b60a736d115a" +dependencies = [ + "cc", +] + +[[package]] +name = "pulldown-cmark" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffade02495f22453cd593159ea2f59827aae7f53fa8323f756799b670881dcf8" +dependencies = [ + "bitflags", + "memchr", + "unicase", +] + +[[package]] +name = "quote" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3bcdf212e9776fbcb2d23ab029360416bb1706b1aea2d1a5ba002727cbcab804" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "rand" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" +dependencies = [ + "libc", + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7" +dependencies = [ + "getrandom", +] + +[[package]] +name = "rayon" +version = "1.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd99e5772ead8baa5215278c9b15bf92087709e9c1b2d1f97cdb5a183c933a7d" +dependencies = [ + "autocfg", + "crossbeam-deque", + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "258bcdb5ac6dad48491bb2992db6b7cf74878b0384908af124823d118c99683f" +dependencies = [ + "crossbeam-channel", + "crossbeam-deque", + "crossbeam-utils", + "num_cpus", +] + +[[package]] +name = "redox_syscall" +version = "0.2.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "62f25bc4c7e55e0b0b7a1d43fb893f4fa1361d0abe38b9ce4f323c2adfe6ef42" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_users" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" +dependencies = [ + "getrandom", + "redox_syscall", + "thiserror", +] + +[[package]] +name = "regalloc2" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0d37148700dbb38f994cd99a1431613057f37ed934d7e4d799b7ab758c482461" +dependencies = [ + "fxhash", + "log", + "slice-group-by", + "smallvec", +] + +[[package]] +name = "regex" +version = "1.5.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d83f127d94bdbcda4c8cc2e50f6f84f4b611f69c902699ca385a39c3a75f9ff1" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.6.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49b3de9ec5dc0a3417da371aab17d729997c15010e7fd24ff707773a33bddb64" + +[[package]] +name = "region" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877e54ea2adcd70d80e9179344c97f93ef0dffd6b03e1f4529e6e83ab2fa9ae0" +dependencies = [ + "bitflags", + "libc", + "mach", + "winapi", +] + +[[package]] +name = "rustc-demangle" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342" + +[[package]] +name = "rustix" +version = "0.33.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "938a344304321a9da4973b9ff4f9f8db9caf4597dfd9dda6a60b523340a0fff0" +dependencies = [ + "bitflags", + "errno", + "io-lifetimes", + "itoa", + "libc", + "linux-raw-sys", + "once_cell", + "winapi", +] + +[[package]] +name = "ryu" +version = "1.0.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3f6f92acf49d1b98f7a81226834412ada05458b7364277387724a237f062695" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scopeguard" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" + +[[package]] +name = "serde" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61ea8d54c77f8315140a05f4c7237403bf38b72704d031543aa1d16abbf517d1" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.137" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f26faba0c3959972377d3b2d306ee9f71faee9714294e41bb777f83f88578be" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.81" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b7ce2b32a1aed03c558dc61a5cd328f15aff2dbc17daad8fb8af04d2100e15c" +dependencies = [ + "itoa", + "ryu", + "serde", +] + +[[package]] +name = "sha2" +version = "0.9.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4d58a1e1bf39749807d89cf2d98ac2dfa0ff1cb3faa38fbb64dd88ac8013d800" +dependencies = [ + "block-buffer", + "cfg-if", + "cpufeatures", + "digest", + "opaque-debug", +] + +[[package]] +name = "shellexpand" +version = "2.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83bdb7831b2d85ddf4a7b148aa19d0587eddbe8671a436b7bd1182eaad0f2829" +dependencies = [ + "dirs-next", +] + +[[package]] +name = "slab" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb703cfe953bccee95685111adeedb76fabe4e97549a58d16f03ea7b9367bb32" + +[[package]] +name = "slice-group-by" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "03b634d87b960ab1a38c4fe143b508576f075e7c978bfad18217645ebfdfa2ec" + +[[package]] +name = "smallvec" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2dd574626839106c320a323308629dcb1acfc96e32a8cba364ddc61ac23ee83" + +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + +[[package]] +name = "strsim" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" + +[[package]] +name = "strsim" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" + +[[package]] +name = "structopt" +version = "0.3.26" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" +dependencies = [ + "clap 2.34.0", + "lazy_static", + "structopt-derive", +] + +[[package]] +name = "structopt-derive" +version = "0.4.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +dependencies = [ + "heck 0.3.3", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "syn" +version = "1.0.98" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c50aef8a904de4c23c788f104b7dddc7d6f79c647c7c8ce4cc8f73eb0ca773dd" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "system-interface" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e09bb3fb4e02ec4b87e182ea9718fadbc0fa3e50085b40a9af9690572b67f9e" +dependencies = [ + "atty", + "bitflags", + "cap-fs-ext", + "cap-std", + "io-lifetimes", + "rustix", + "winapi", + "winx", +] + +[[package]] +name = "target-lexicon" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c02424087780c9b71cc96799eaeddff35af2bc513278cda5c99fc1f5d026d3c1" + +[[package]] +name = "termcolor" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "test-helpers" +version = "0.1.0" +dependencies = [ + "backtrace", + "filetime", + "heck 0.3.3", + "ignore", + "proc-macro2", + "quote", + "wit-bindgen-gen-c", + "wit-bindgen-gen-core", + "wit-bindgen-gen-js", + "wit-bindgen-gen-rust-wasm", + "wit-bindgen-gen-spidermonkey", + "wit-bindgen-gen-wasmtime", + "wit-bindgen-gen-wasmtime-py", + "wit-parser", +] + +[[package]] +name = "test-rust-wasm" +version = "0.1.0" +dependencies = [ + "futures-util", + "wit-bindgen-rust", +] + +[[package]] +name = "textwrap" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" + +[[package]] +name = "thiserror" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd829fe32373d27f76265620b5309d0340cb8550f523c1dda251d6298069069a" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0396bc89e626244658bef819e22d0cc459e795a5ebe878e6ec336d1674a8d79a" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "thread_local" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5516c27b78311c50bf42c071425c560ac799b11c30b31f87e3081965fe5e0180" +dependencies = [ + "once_cell", +] + +[[package]] +name = "tinyvec" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87cc5ceb3875bb20c2890005a4e226a4651264a5c75edb2421b52861a0a0cb50" +dependencies = [ + "tinyvec_macros", +] + +[[package]] +name = "tinyvec_macros" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c" + +[[package]] +name = "toml" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d82e1a7758622a465f8cee077614c73484dac5b836c02ff6a40d5d1010324d7" +dependencies = [ + "serde", +] + +[[package]] +name = "tracing" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a400e31aa60b9d44a52a8ee0343b5b18566b03a8321e0d321f695cf56e940160" +dependencies = [ + "cfg-if", + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc6b8ad3567499f98a1db7a752b07a7c8c7c7c34c332ec00effb2b0027974b7c" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tracing-core" +version = "0.1.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7709595b8878a4965ce5e87ebf880a7d39c9afc6837721b21a5a816a8117d921" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987" + +[[package]] +name = "unicase" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" +dependencies = [ + "version_check", +] + +[[package]] +name = "unicode-ident" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5bd2fe26506023ed7b5e1e315add59d6f584c621d037f9368fea9cfb988f368c" + +[[package]] +name = "unicode-normalization" +version = "0.1.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d54590932941a9e9266f0832deed84ebe1bf2e4c9e4a3554d393d18f5e854bf9" +dependencies = [ + "tinyvec", +] + +[[package]] +name = "unicode-segmentation" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" + +[[package]] +name = "unicode-width" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ed742d4ea2bd1176e236172c8429aaf54486e7ac098db29ffe6529e0ce50973" + +[[package]] +name = "unicode-xid" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "957e51f3646910546462e67d5f7599b9e4fb8acdd304b087a6494730f9eebf04" + +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + +[[package]] +name = "version_check" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" + +[[package]] +name = "walkdir" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "808cf2735cd4b6866113f648b791c6adc5714537bc222d9347bb203386ffda56" +dependencies = [ + "same-file", + "winapi", + "winapi-util", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wasi-cap-std-sync" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2d982597fafde637a1d771d5dcc4524f337466f963dc95dd84eaa76fd74dcbf" +dependencies = [ + "anyhow", + "async-trait", + "cap-fs-ext", + "cap-rand", + "cap-std", + "cap-time-ext", + "fs-set-times", + "io-extras", + "io-lifetimes", + "is-terminal", + "lazy_static", + "rustix", + "system-interface", + "tracing", + "wasi-common", + "winapi", +] + +[[package]] +name = "wasi-common" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "593416d860f31caf7e59e8a5671827c392258fc5242fc0987d047beadd02bfc0" +dependencies = [ + "anyhow", + "bitflags", + "cap-rand", + "cap-std", + "io-extras", + "rustix", + "thiserror", + "tracing", + "wiggle", + "winapi", +] + +[[package]] +name = "wasm-encoder" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db0c351632e46cc06a58a696a6c11e4cf90cad4b9f8f07a0b59128d616c29bb0" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasm-encoder" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31f0c17267a5ffd6ae3d897589460e21db1673c84fb7016b909c9691369a75ea" +dependencies = [ + "leb128", +] + +[[package]] +name = "wasmparser" +version = "0.80.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "449167e2832691a1bff24cde28d2804e90e09586a448c8e76984792c44334a6b" + +[[package]] +name = "wasmparser" +version = "0.85.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "570460c58b21e9150d2df0eaaedbb7816c34bcec009ae0dcc976e40ba81463e7" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmparser" +version = "0.86.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4bcbfe95447da2aa7ff171857fc8427513eb57c75a729bb190e974dc695e8f5c" +dependencies = [ + "indexmap", +] + +[[package]] +name = "wasmprinter" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa4cca415278da771add7c9ab7f3391f04b8d98719d2cf28a185d38d5206697e" +dependencies = [ + "anyhow", + "wasmparser 0.86.0", +] + +[[package]] +name = "wasmtime" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c842f9c8e190fe01300fc8d715e9368c775670fb9856247c67abffdb5236d6db" +dependencies = [ + "anyhow", + "async-trait", + "backtrace", + "bincode", + "cfg-if", + "indexmap", + "lazy_static", + "libc", + "log", + "object", + "once_cell", + "paste", + "psm", + "rayon", + "region", + "serde", + "target-lexicon", + "wasmparser 0.85.0", + "wasmtime-cache", + "wasmtime-cranelift", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit", + "wasmtime-runtime", + "wat", + "winapi", +] + +[[package]] +name = "wasmtime-cache" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cce2aa752e864a33eef2a6629edc59554e75f0bc1719431dac5e49eed516af69" +dependencies = [ + "anyhow", + "base64", + "bincode", + "directories-next", + "file-per-thread-logger", + "log", + "rustix", + "serde", + "sha2", + "toml", + "winapi", + "zstd", +] + +[[package]] +name = "wasmtime-cranelift" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "922361eb8c03cea8909bc922471202f6c6bc2f0c682fac2fe473740441c86b3b" +dependencies = [ + "anyhow", + "cranelift-codegen", + "cranelift-entity", + "cranelift-frontend", + "cranelift-native", + "cranelift-wasm", + "gimli", + "log", + "more-asserts", + "object", + "target-lexicon", + "thiserror", + "wasmparser 0.85.0", + "wasmtime-environ", +] + +[[package]] +name = "wasmtime-environ" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e602f1120fc40a3f016f1f69d08c86cfeff7b867bed1462901953e6871f85167" +dependencies = [ + "anyhow", + "cranelift-entity", + "gimli", + "indexmap", + "log", + "more-asserts", + "object", + "serde", + "target-lexicon", + "thiserror", + "wasmparser 0.85.0", + "wasmtime-types", +] + +[[package]] +name = "wasmtime-fiber" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e078bd1a01b8bacd982280eca4dfc22093ae2b19607ae6a38a97278f45aa5fa" +dependencies = [ + "cc", + "rustix", + "winapi", +] + +[[package]] +name = "wasmtime-jit" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49af1445759a8e797a92f27dd0983c155615648263052e0b80d69e7d223896b7" +dependencies = [ + "addr2line", + "anyhow", + "bincode", + "cfg-if", + "cpp_demangle", + "gimli", + "ittapi-rs", + "log", + "object", + "region", + "rustc-demangle", + "rustix", + "serde", + "target-lexicon", + "thiserror", + "wasmtime-environ", + "wasmtime-jit-debug", + "wasmtime-runtime", + "winapi", +] + +[[package]] +name = "wasmtime-jit-debug" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5dd480cc6dc0a401653e45b79796a3317f8228990d84bc2271bdaf0810071" +dependencies = [ + "lazy_static", + "object", + "rustix", +] + +[[package]] +name = "wasmtime-runtime" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e875bcd02d1ecfc7d099dd58354d55d73467652eb2b103ff470fe3aecb7d0381" +dependencies = [ + "anyhow", + "backtrace", + "cc", + "cfg-if", + "indexmap", + "libc", + "log", + "mach", + "memfd", + "memoffset", + "more-asserts", + "rand", + "region", + "rustix", + "thiserror", + "wasmtime-environ", + "wasmtime-fiber", + "wasmtime-jit-debug", + "winapi", +] + +[[package]] +name = "wasmtime-types" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd63a19ba61ac7448add4dc1fecb8d78304812af2a52dad04b89f887791b156" +dependencies = [ + "cranelift-entity", + "serde", + "thiserror", + "wasmparser 0.85.0", +] + +[[package]] +name = "wasmtime-wasi" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3129ba8ced91c5d8e4f8747c385f117eb24ac51ff387e3a4bf3b351a764eb2d" +dependencies = [ + "anyhow", + "wasi-cap-std-sync", + "wasi-common", + "wasmtime", + "wiggle", +] + +[[package]] +name = "wast" +version = "33.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d04fe175c7f78214971293e7d8875673804e736092206a3a4544dbc12811c1b" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "35.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ef140f1b49946586078353a453a1d28ba90adfc54dde75710bc1931de204d68" +dependencies = [ + "leb128", +] + +[[package]] +name = "wast" +version = "42.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "badcb03f976f983ff0daf294da9697be659442f61e6b0942bb37a2b6cbfe9dd4" +dependencies = [ + "leb128", + "memchr", + "unicode-width", + "wasm-encoder 0.13.0", +] + +[[package]] +name = "wat" +version = "1.0.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b92f20b742ac527066c8414bc0637352661b68cab07ef42586cefaba71c965cf" +dependencies = [ + "wast 42.0.0", +] + +[[package]] +name = "wiggle" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e04785b48d9b0a6ac7a59cd1469dc38e9ce3238c9bea46f12250a1cc4b4c9fd9" +dependencies = [ + "anyhow", + "async-trait", + "bitflags", + "thiserror", + "tracing", + "wasmtime", + "wiggle-macro", +] + +[[package]] +name = "wiggle-generate" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49d559374b93f495a244efaa35bb1f2e2ee6a92ffdc8988333e48808c79b6732" +dependencies = [ + "anyhow", + "heck 0.4.0", + "proc-macro2", + "quote", + "shellexpand", + "syn", + "witx", +] + +[[package]] +name = "wiggle-macro" +version = "0.38.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78983b50f8f23b1ce4b2e6ba34ffda88e36f131eb54d4136d9e6e8e0139794db" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wiggle-generate", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178" +dependencies = [ + "winapi", +] + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "winx" +version = "0.31.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d5973cb8cd94a77d03ad7e23bbe14889cb29805da1cec0e4aff75e21aebded" +dependencies = [ + "bitflags", + "io-lifetimes", + "winapi", +] + +[[package]] +name = "wit-bindgen-cli" +version = "0.1.0" +dependencies = [ + "anyhow", + "structopt", + "wit-bindgen-gen-c", + "wit-bindgen-gen-core", + "wit-bindgen-gen-js", + "wit-bindgen-gen-markdown", + "wit-bindgen-gen-rust-wasm", + "wit-bindgen-gen-spidermonkey", + "wit-bindgen-gen-wasmtime", + "wit-bindgen-gen-wasmtime-py", +] + +[[package]] +name = "wit-bindgen-demo" +version = "0.1.0" +dependencies = [ + "wasmprinter", + "wit-bindgen-gen-c", + "wit-bindgen-gen-core", + "wit-bindgen-gen-js", + "wit-bindgen-gen-markdown", + "wit-bindgen-gen-rust-wasm", + "wit-bindgen-gen-spidermonkey", + "wit-bindgen-gen-wasmtime", + "wit-bindgen-gen-wasmtime-py", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-bindgen-gen-c" +version = "0.1.0" +dependencies = [ + "heck 0.3.3", + "structopt", + "test-helpers", + "wit-bindgen-gen-core", +] + +[[package]] +name = "wit-bindgen-gen-core" +version = "0.1.0" +dependencies = [ + "anyhow", + "wit-parser", +] + +[[package]] +name = "wit-bindgen-gen-js" +version = "0.1.0" +dependencies = [ + "heck 0.3.3", + "structopt", + "test-helpers", + "wit-bindgen-gen-core", +] + +[[package]] +name = "wit-bindgen-gen-markdown" +version = "0.1.0" +dependencies = [ + "heck 0.3.3", + "pulldown-cmark", + "structopt", + "wit-bindgen-gen-core", +] + +[[package]] +name = "wit-bindgen-gen-rust" +version = "0.1.0" +dependencies = [ + "heck 0.3.3", + "wit-bindgen-gen-core", +] + +[[package]] +name = "wit-bindgen-gen-rust-wasm" +version = "0.1.0" +dependencies = [ + "heck 0.3.3", + "structopt", + "test-helpers", + "wit-bindgen-gen-core", + "wit-bindgen-gen-rust", + "wit-bindgen-rust", +] + +[[package]] +name = "wit-bindgen-gen-spidermonkey" +version = "0.1.0" +dependencies = [ + "heck 0.3.3", + "lazy_static", + "structopt", + "test-helpers", + "wasm-encoder 0.8.0", + "wasmparser 0.80.2", + "wit-bindgen-gen-core", +] + +[[package]] +name = "wit-bindgen-gen-wasmtime" +version = "0.1.0" +dependencies = [ + "anyhow", + "heck 0.3.3", + "structopt", + "test-helpers", + "wasmtime", + "wasmtime-wasi", + "wit-bindgen-gen-core", + "wit-bindgen-gen-rust", + "wit-bindgen-wasmtime", +] + +[[package]] +name = "wit-bindgen-gen-wasmtime-py" +version = "0.1.0" +dependencies = [ + "heck 0.3.3", + "structopt", + "test-helpers", + "wit-bindgen-gen-core", +] + +[[package]] +name = "wit-bindgen-rust" +version = "0.1.0" +dependencies = [ + "async-trait", + "bitflags", + "wit-bindgen-rust-impl", +] + +[[package]] +name = "wit-bindgen-rust-impl" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "syn", + "wit-bindgen-gen-core", + "wit-bindgen-gen-rust-wasm", +] + +[[package]] +name = "wit-bindgen-wasmtime" +version = "0.1.0" +dependencies = [ + "anyhow", + "async-trait", + "bitflags", + "thiserror", + "tracing", + "wasmtime", + "wit-bindgen-wasmtime-impl", +] + +[[package]] +name = "wit-bindgen-wasmtime-impl" +version = "0.1.0" +dependencies = [ + "proc-macro2", + "syn", + "wit-bindgen-gen-core", + "wit-bindgen-gen-wasmtime", +] + +[[package]] +name = "wit-component" +version = "0.1.0" +dependencies = [ + "anyhow", + "clap 3.2.6", + "env_logger", + "glob", + "indexmap", + "log", + "pretty_assertions", + "wasm-encoder 0.13.0", + "wasmparser 0.86.0", + "wasmprinter", + "wat", + "wit-parser", +] + +[[package]] +name = "wit-parser" +version = "0.1.0" +dependencies = [ + "anyhow", + "id-arena", + "pulldown-cmark", + "rayon", + "serde", + "serde_json", + "unicode-normalization", + "unicode-xid", + "wast 33.0.0", +] + +[[package]] +name = "witx" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e366f27a5cabcddb2706a78296a40b8fcc451e1a6aba2fc1d94b4a01bdaaef4b" +dependencies = [ + "anyhow", + "log", + "thiserror", + "wast 35.0.2", +] + +[[package]] +name = "zstd" +version = "0.11.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20cc960326ece64f010d2d2107537f26dc589a6573a316bd5b1dba685fa5fde4" +dependencies = [ + "zstd-safe", +] + +[[package]] +name = "zstd-safe" +version = "5.0.2+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d2a5585e04f9eea4b2a3d1eca508c4dee9592a89ef6f450c11719da0726f4db" +dependencies = [ + "libc", + "zstd-sys", +] + +[[package]] +name = "zstd-sys" +version = "2.0.1+zstd.1.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fd07cbbc53846d9145dbffdf6dd09a7a0aa52be46741825f5c97bdd4f73f12b" +dependencies = [ + "cc", + "libc", +] diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/Cargo.toml new file mode 100644 index 0000000..12b7e97 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "wit-bindgen-cli" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[workspace] +members = [ + "crates/test-rust-wasm", + "crates/wit-bindgen-demo", + "crates/wit-component", +] +resolver = "2" + +[[bin]] +name = "wit-bindgen" +test = false + +[dependencies] +anyhow = "1.0" +structopt = { version = "0.3", default-features = false } +wit-bindgen-gen-core = { path = 'crates/gen-core' } +wit-bindgen-gen-rust-wasm = { path = 'crates/gen-rust-wasm', features = ['structopt'] } +wit-bindgen-gen-wasmtime = { path = 'crates/gen-wasmtime', features = ['structopt'] } +wit-bindgen-gen-wasmtime-py = { path = 'crates/gen-wasmtime-py', features = ['structopt'] } +wit-bindgen-gen-js = { path = 'crates/gen-js', features = ['structopt'] } +wit-bindgen-gen-c = { path = 'crates/gen-c', features = ['structopt'] } +wit-bindgen-gen-markdown = { path = 'crates/gen-markdown', features = ['structopt'] } +wit-bindgen-gen-spidermonkey = { path = 'crates/gen-spidermonkey', features = ['structopt'] } + +# Compiling `spidermonkey.wasm` takes way too long without this. +[profile.dev.package.cranelift-codegen] +debug-assertions = false +opt-level = 2 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/LICENSE b/__wasm/wit-bindgen-sample/wit-bindgen/LICENSE new file mode 100644 index 0000000..f9d8195 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/LICENSE @@ -0,0 +1,220 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + + +--- LLVM Exceptions to the Apache 2.0 License ---- + +As an exception, if, as a result of your compiling your source code, portions +of this Software are embedded into an Object form of such source code, you +may redistribute such embedded portions in such Object form without complying +with the conditions of Sections 4(a), 4(b) and 4(d) of the License. + +In addition, if you combine or link compiled forms of this Software with +software that is licensed under the GPLv2 ("Combined Software") and if a +court of competent jurisdiction determines that the patent provision (Section +3), the indemnity provision (Section 9) or other Section of the License +conflicts with the conditions of the GPLv2, you may retroactively and +prospectively choose to deem waived or otherwise exclude such Section(s) of +the License, but only in their entirety and only with respect to the Combined +Software. + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/README.md b/__wasm/wit-bindgen-sample/wit-bindgen/README.md new file mode 100644 index 0000000..ae28961 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/README.md @@ -0,0 +1,177 @@ +
+

wit-bindgen

+ +

+ A language bindings generator for wit +

+ + A Bytecode Alliance project + +

+ build status + supported rustc stable +

+
+ +## About + +> **Note**: this project is still relatively young and active development with +> large changes is still happening. If you're considering depending on this at +> this time it's recommended to reach out to the authors on [zulip] and get more +> information first. + +[zulip]: https://bytecodealliance.zulipchat.com/ + +This project is a bindings generator framework for WebAssembly programs and +embeddings of WebAssembly. This works with `*.wit` files which describe the +interface of a module, either imported or exported. For example this project can +be used in cases such as: + +* Your language (say, Rust) is compiled to WebAssembly and you'd like to import + WASI. This project will generate Rust bindings to import WASI APIs that are + described with `*.wit`. + +* Your runtime (say, Wasmtime) wants to then provide WASI functionality to guest + programs. This project will generate a Rust `trait` for you to implement for + the WASI interface. + +* You're consuming a WebAssembly module (say, in a browser) and you don't want + to deal with funky ABI details. You'd use this project to generate JS bindings + which give you a TypeScript interface dealing with native JS types for the + WebAssembly module described by `*.wit`. + +This project is based on the [interface types +proposal](https://github.com/webassembly/interface-types) and the [canonical +ABI](https://github.com/WebAssembly/interface-types/pull/132), both of which are +at the time of this writing a work in progress. This repository will be +following upstream changes. The purpose of `wit-bindgen` is to provide a +forwards-compatible toolchain and story for interface types and a canonical ABI. +Generated language bindings all use the canonical ABI for communication, +enabling WebAssembly modules to be written in any language with support and for +WebAssembly modules to be consumed in any environment with language support. + +## Demo + +[View generated bindings +online!](https://bytecodealliance.github.io/wit-bindgen/) + +If you're curious to poke around and see what generated bindings look like for a +given input `*.wit`, you can explore the generated code online to get an idea +of what's being generated and what the glue code looks like. + +## Installation + +At this time a CLI tool is provided mostly for debugging and exploratory +purposes. It can be installed with: + +``` +cargo install --git https://github.com/bytecodealliance/wit-bindgen wit-bindgen-cli +``` + +This tool is not necessarily intended to be integrated into toolchains. For +example usage in Rust would more likely be done through procedural macros and +Cargo dependencies. Usage in a Web application would probably use a version of +`wit-bindgen` compiled to WebAssembly and published to NPM. + +For now, though, you can explore what bindings look like in each language +through the CLI. Again if you'd like to depend on this if you wouldn't mind +please reach out on [zulip] so we can figure out a better story than relying on +the CLI tool for your use case. + +## Supported Languages + +First here's a list of supported languages for generating a WebAssembly binary +which uses interface types. This means that these languages support +`*.wit`-defined imports and exports. + +* `rust-wasm` - this is for Rust compiled to WebAssembly, typically using either + the `wasm32-wasi` or `wasm32-unknown-unknown` targets depending on your use + case. In this mode you'd probably depend on the `wit-bindgen-rust` crate + (located at `crates/rust-wasm`) and use the `import!` and `export!` macros to + generate code. + +* `c` - this is for C compiled to WebAssembly, using either of the targets above + for Rust as well. With C the `wit-bindgen` CLI tool will emit a `*.h` and a + `*.c` file to be compiled into the wasm module. + +This repository also supports a number of host languages/runtimes which can be +used to consume WebAssembly modules that use interface types. These modules need +to follow the canonical ABI for their exports/imports: + +* `wasmtime` - this is for Rust users using the `wasmtime` crate. This generator + is used through the `wit-bindgen-wasmtime` crate (located at + `crates/wasmtime`) and, like the compiled-to-wasm Rust support, has an + `import!` and an `export!` macro for generating code. + +* `js` - this is for JavaScript users executing WebAssembly modules. This could + be in a browsers, Node.js, or Deno. In theory this covers browser use cases + like web workers and such as well. In this mode the `wit-bindgen` CLI tool + will emit a `*.js` and a `*.d.ts` file describing the interface and providing + necessary runtime support in JS to implement the canonical ABI. Note that the + intended long-term integration of this language is to compile `wit-bindgen` + itself to WebAssembly and publish NPM packages for popular JS build systems to + integrate `wit-bindgen` into JS build processes. + +* `wasmtime-py` - this is for Python users using the `wasmtime` PyPI package. + This uses Wasmtime under the hood but you get to write Python in providing + imports to WebAssembly modules or consume modules using interface types. This + generates a `*.py` file which is annotated with types for usage in `mypy` or + other type-checkers. + +All generators support the `--import` and `--export` flags in the `wit-bindgen` +CLI tool: + +``` +$ wit-bindgen js --import browser.wit +$ wit-bindgen rust-wasm --export my-interface.wit +$ wit-bindgen wasmtime --import host-functions.wit +``` + +Here "import" means "I want to import and call the functions in this interface" +and "export" means "I want to define the functions in this interface for others +to call". + +Finally in a sort of "miscellaneous" category the `wit-bindgen` CLI also +supports: + +* `markdown` - generates a `*.md` and a `*.html` file with readable + documentation rendered from the comments in the source `*.wit` file. + +Note that the list of supported languages here is a snapshot in time and is not +final. The purpose of the interface-types proposal is to be language agnostic +both in how WebAssembly modules are written as well as how they are consumed. If +you have a runtime that isn't listed here or you're compiling to WebAssembly and +your language isn't listed here, it doesn't mean that it will never be +supported! A language binding generator is intended to be not the hardest thing +in the world (but unfortunately also not the easiest) to write, and the crates +and support in this repository mostly exist to make writing generators as easy +as possible. + +Some other languages and runtimes, for example, that don't have support in +`wit-bindgen` today but are possible in the future (and may get written here +too) are: + +* `wasmtime-go` - same as for `wasmtime-py` but for Go. Basically for Go users + using the [`wasmtime-go` + package](https://github.com/bytecodealliance/wasmtime-go) who want to work + with interface types rather than raw pointers/memories/etc. + +* `wasmtime-cpp` - again the same as for `wasmtime-py`, but for users of the + [`wasmtime-cpp` header file](https://github.com/alexcrichton/wasmtime-cpp) to + use interface types from C++. + +* JS - while host runtime support is provided for JS today it should also be + supported for + [JS-compiled-to-WebAssembly](https://bytecodealliance.org/articles/making-javascript-run-fast-on-webassembly). + For example a `*.d.ts` file could be generated for what JS projects could + import and then corresponding glue code for the engine-compiled-to-wasm would + also be generated. This means that you could use both JS-in-wasm but also JS + as a host (or more realistically another runtime like Wasmtime since if you're + running in a JS environment you're probably best off running the JS there + instead). + +Note that this is not an exclusive list, only intended to give you an idea of +what other bindings could look like. There's a plethora of runtimes and +languages that compile to WebAssembly, and interface types should be able to +work with all of them and it's theoretically just some work-hours away from +having support in `wit-bindgen`. diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/TODO.md b/__wasm/wit-bindgen-sample/wit-bindgen/TODO.md new file mode 100644 index 0000000..cdde2d5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/TODO.md @@ -0,0 +1,60 @@ +# All generators + +* handle support in exports + +* push/pull-buffer support in exports + +# wasmtime + +* buffer-in-buffer doesn't work. Doesn't work because we can't get a re-access + of the transaction to add more buffers into it after-the-fact. + +* Needs more testing on big-endian. + +* Features from wiggle: + * use `GuestError::InFunc` more liberally + - stores/loads + - `try_from` conversions + * generate just the trait (??? what to do about `wasmtime` dep ???) + +# JS + +* Is there a better representation for general `variant` types? Currently it's + `{ tag: string, val: T }` but that seems like it's probably sub-par. There's + specializations for `option` and `enum` variants, but that's it. + +* Is there a better representation for flags than simply an integer? + +* Should functions returning `expected` get translated in JS to functions + that return `T` and throw `E`? + +* Adding imports to an import object is clunky because you need to also pass in + a closure which extracts values from the raw instance. Unsure how to make this + less clunky though. + +* Needs more testing on big-endian. Specifically slice copies are probably not + correct. + +* Style with names needs to be respected, currently things are using + `to_snake_case` but I think JS prefers camelCase? + +* The `bigint` type is strict in that it does not accept plain `number` types to + work with it. Should generated bindings be more flexible though and work with + `number` in addition to `bigint`? + +* Host-handle types are always ascribed as `any` but ideally we'd do better than + that and assign them types. Maybe the type should be imported from somewhere + else? + +* Lifting/lowering of variants can almost surely use a more compressed technique + which generates less code. + +* Enums are handled in lowering as either strings or numbers, but should only + numbers be handled here? Does anyone pass around strings as enum values? + +* Exported handle types in JS aren't nominal. As of this writing they all only + have a `drop` and a `clone` method so they're interchangeable from `tsc`'s + perspective. Ideally these would be nominal separate types. + +* Imported handle types show up as `any` in TS, unsure how to plumb through + actual types to get that actually typed. diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/WIT.md b/__wasm/wit-bindgen-sample/wit-bindgen/WIT.md new file mode 100644 index 0000000..f3a4e4e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/WIT.md @@ -0,0 +1,550 @@ +# The `*.wit` format + +This is intended to document the `*.wit` format as it exists today. The goal is +to provide an overview to understand what features `wit` files give you and how +they're structured. This isn't intended to be a formal grammar, although it's +expected that one day we'll have a formal grammar for `*.wit` files. + +If you're curious to give things a spin try out the [online +demo](https://bytecodealliance.github.io/wit-bindgen/) of `wit-bindgen` where +you can input `*.wit` on the left and see output of generated bindings for +languages on the right. If you're looking to start you can try out the +"markdown" output mode which generates documentation for the input document on +the left. + +## Lexical structure + +The `wit` format is a curly-braced-based format where whitespace is optional (but +recommended). It is intended to be easily human readable and supports features +like comments, multi-line comments, and custom identifiers. A `wit` document +is parsed as a unicode string, and when stored in a file is expected to be +encoded as UTF-8. + +Additionally, wit files must not contain any bidirectional override scalar values, +control codes other than newline, carriage return, and horizontal tab, or +codepoints that Unicode officially deprecates or strongly discourages. + +The current structure of tokens are: + +```wit +token ::= whitespace + | comment + | operator + | keyword + | identifier +``` + +Whitespace and comments are ignored when parsing structures defined elsewhere +here. + +### Whitespace + +A `whitespace` token in `*.wit` is a space, a newline, a carriage return, or a +tab character: + +```wit +whitespace ::= ' ' | '\n' | '\r' | '\t' +``` + +### Comments + +A `comment` token in `*.wit` is either a line comment preceded with `//` which +ends at the next newline (`\n`) character or it's a block comment which starts +with `/*` and ends with `*/`. Note that block comments are allowed to be nested +and their delimiters must be balanced + +```wit +comment ::= '//' character-that-isnt-a-newline* + | '/*' any-unicode-character* '*/' +``` + +There is a special type of comment called `documentation comment`. A +`doc-comment` is either a line comment preceded with `///` whichends at the next +newline (`\n`) character or it's a block comment which starts with `/**` and ends +with `*/`. Note that block comments are allowed to be nested and their delimiters +must be balanced + +```wit +doc-comment ::= '///' character-that-isnt-a-newline* + | '/**' any-unicode-character* '*/' +``` + +### Operators + +There are some common operators in the lexical structure of `wit` used for +various constructs. Note that delimiters such as `{` and `(` must all be +balanced. + +```wit +operator ::= '=' | ',' | ':' | ';' | '(' | ')' | '{' | '}' | '<' | '>' | '*' | '->' +``` + +### Keywords + +Certain identifiers are reserved for use in `wit` documents and cannot be used +bare as an identifier. These are used to help parse the format, and the list of +keywords is still in flux at this time but the current set is: + +```wit +keyword ::= 'use' + | 'type' + | 'resource' + | 'func' + | 'u8' | 'u16' | 'u32' | 'u64' + | 's8' | 's16' | 's32' | 's64' + | 'float32' | 'float64' + | 'char' + | 'handle' + | 'record' + | 'enum' + | 'flags' + | 'variant' + | 'union' + | 'bool' + | 'string' + | 'option' + | 'list' + | 'expected' + | 'unit' + | 'as' + | 'from' + | 'static' + | 'interface' + | 'tuple' + | 'async' + | 'future' + | 'stream' +``` + +## Top-level items + +A `wit` document is a sequence of items specified at the top level. These items +come one after another and it's recommended to separate them with newlines for +readability but this isn't required. + +## Item: `use` + +A `use` statement enables importing type or resource definitions from other +wit documents. The structure of a use statement is: + +```wit +use * from other-file +use { a, list, of, names } from another-file +use { name as other-name } from yet-another-file +``` + +Specifically the structure of this is: + +```wit +use-item ::= 'use' use-names 'from' id + +use-names ::= '*' + | '{' use-names-list '}' + +use-names-list ::= use-names-item + | use-names-item ',' use-names-list? + +use-names-item ::= id + | id 'as' id +``` + +Note: Here `use-names-list?` means at least one `use-name-list` term. + +## Items: type + +There are a number of methods of defining types in a `wit` document, and all of +the types that can be defined in `wit` are intended to map directly to types in +the [interface types specification](https://github.com/WebAssembly/interface-types). + +### Item: `type` (alias) + +A `type` statement declares a new named type in the `wit` document. This name can +be later referred to when defining items using this type. This construct is +similar to a type alias in other languages + +```wit +type my-awesome-u32 = u32 +type my-complicated-tuple = tuple +``` + +Specifically the structure of this is: + +```wit +type-item ::= 'type' id '=' ty +``` + +### Item: `record` (bag of named fields) + +A `record` statement declares a new named structure with named fields. Records +are similar to a `struct` in many languages. Instances of a `record` always have +their fields defined. + +```wit +record pair { + x: u32, + y: u32, +} + +record person { + name: string, + age: u32, + has-lego-action-figure: bool, +} +``` + +Specifically the structure of this is: + +```wit +record-item ::= 'record' id '{' record-fields '}' + +record-fields ::= record-field + | record-field ',' record-fields? + +record-field ::= id ':' ty +``` + +### Item: `flags` (bag-of-bools) + +A `flags` statement defines a new `record`-like structure where all the fields +are booleans. The `flags` type is distinct from `record` in that it typically is +represented as a bit flags representation in the canonical ABI. For the purposes +of type-checking, however, it's simply syntactic sugar for a record-of-booleans. + +```wit +flags properties { + lego, + marvel-superhero, + supervillan, +} + +// type-wise equivalent to: +// +// record properties { +// lego: bool, +// marvel-superhero: bool, +// supervillan: bool, +// } +``` + +Specifically the structure of this is: + +```wit +flags-items ::= 'flags' id '{' flags-fields '}' + +flags-fields ::= id, + | id ',' flags-fields? +``` + +### Item: `variant` (one of a set of types) + +A `variant` statement defines a new type where instances of the type match +exactly one of the variants listed for the type. This is similar to a "sum" type +in algebraic datatypes (or an `enum` in Rust if you're familiar with it). +Variants can be thought of as tagged unions as well. + +Each case of a variant can have an optional type associated with it which is +present when values have that particular case's tag. + +All `variant` type must have at least one case specified. + +```wit +variant filter { + all, + none, + some(list), +} +``` + +Specifically the structure of this is: + +```wit +variant-items ::= 'variant' id '{' variant-cases '}' + +variant-cases ::= variant-case, + | variant-case ',' variant-cases? + +variant-case ::= id + | id '(' ty ')' +``` + +### Item: `enum` (variant but with no payload) + +An `enum` statement defines a new type which is semantically equivalent to a +`variant` where none of the cases have a payload type. This is special-cased, +however, to possibly have a different representation in the language ABIs or +have different bindings generated in for languages. + +```wit +enum color { + red, + green, + blue, + yellow, + other, +} + +// type-wise equivalent to: +// +// variant color { +// red, +// green, +// blue, +// yellow, +// other, +// } +``` + +Specifically the structure of this is: + +```wit +enum-items ::= 'enum' id '{' enum-cases '}' + +enum-cases ::= id, + | id ',' enum-cases? +``` + +### Item: `union` (variant but with no case names) + +A `union` statement defines a new type which is semantically equivalent to a +`variant` where all of the cases have a payload type and the case names are +numerical. This is special-cased, however, to possibly have a different +representation in the language ABIs or have different bindings generated in for +languages. + +```wit +union configuration { + string, + list, +} + +// type-wise equivalent to: +// +// variant configuration { +// 0(string), +// 1(list), +// } +``` + +Specifically the structure of this is: + +```wit +union-items ::= 'union' id '{' union-cases '}' + +union-cases ::= ty, + | ty ',' union-cases? +``` + +## Item: `func` + +Functions can also be defined in a `*.wit` document. Functions have a name, +parameters, and results. Functions can optionally also be declared as `async` +functions. + +```wit +thunk: func() +fibonacci: func(n: u32) -> u32 +sleep: async func(ms: u64) +``` + +Specifically functions have the structure: + +```wit +func-item ::= id ':' 'async'? 'func' '(' func-args ')' func-ret + +func-args ::= func-arg + | func-arg ',' func-args? + +func-arg ::= id ':' ty + +func-ret ::= nil + | '->' ty +``` + +## Item: `resource` + +Resources represent a value that has a hidden representation not known to the +outside world. This means that the resource is operated on through a "handle" (a +pointer of sorts). Resources also have ownership associated with them and +languages will have to manage the lifetime of resources manually (they're +similar to file descriptors). + +Resources can also optionally have functions defined within them which adds an +implicit "self" argument as the first argument to each function of the same type +of the including resource, unless the function is flagged as `static`. + +```wit +resource file-descriptor + +resource request { + static new: func() -> request + + body: async func() -> list + headers: func() -> list +} +``` + +Specifically resources have the structure: + +```wit +resource-item ::= 'resource' id resource-contents + +resource-contents ::= nil + | '{' resource-defs '}' + +resource-defs ::= resource-def resource-defs? + +resource-def ::= 'static'? func-item +``` + +## Types + +As mentioned previously the intention of `wit` is to allow defining types +corresponding to the interface types specification. Many of the top-level items +above are introducing new named types but "anonymous" types are also supported, +such as built-ins. For example: + +```wit +type number = u32 +type fallible-function-result = expected +type headers = list +``` + +Specifically the following types are available: + +```wit +ty ::= 'u8' | 'u16' | 'u32' | 'u64' + | 's8' | 's16' | 's32' | 's64' + | 'float32' | 'float64' + | 'char' + | 'bool' + | 'string' + | 'unit' + | tuple + | list + | option + | expected + | future + | stream + | id + +tuple ::= 'tuple' '<' tuple-list '>' +tuple-list ::= ty + | ty ',' tuple-list? + +list ::= 'list' '<' ty '>' + +option ::= 'option' '<' ty '>' + +expected ::= 'expected' '<' ty ',' ty '>' + +future ::= 'future' '<' ty '>' + +stream ::= 'stream' '<' ty ',' ty '>' +``` + +The `tuple` type is semantically equivalent to a `record` with numerical fields, +but it frequently can have language-specific meaning so it's provided as a +first-class type. + +Similarly the `option` and `expected` types are semantically equivalent to the +variants: + +```wit +variant option { + none, + some(ty), +} + +variant expected { + ok(ok-ty) + err(err-ty), +} +``` + +These types are so frequently used and frequently have language-specific +meanings though so they're also provided as first-class types. + +Finally the last case of a `ty` is simply an `id` which is intended to refer to +another type or resource defined in the document. Note that definitions can come +through a `use` statement or they can be defined locally. + +## Identifiers + +Identifiers in `wit` can be defined with two different forms. The first is a +lower-case [stream-safe] [NFC] [kebab-case] identifier where each part delimited +by '-'s starts with a `XID_Start` scalar value with a zero Canonical Combining +Class: + +```wit +foo: func(bar: u32) + +red-green-blue: func(r: u32, g: u32, b: u32) +``` + +This form can't name identifiers which have the same name as wit keywords, so +the second form is the same syntax with the same restrictions as the first, but +prefixed with '%': + +```wit +%foo: func(%bar: u32) + +%red-green-blue: func(%r: u32, %g: u32, %b: u32) + +// This form also supports identifiers that would otherwise be keywords. +%variant: func(%enum: s32) +``` + +[kebab-case]: https://en.wikipedia.org/wiki/Letter_case#Kebab_case +[Unicode identifier]: http://www.unicode.org/reports/tr31/ +[stream-safe]: https://unicode.org/reports/tr15/#Stream_Safe_Text_Format +[NFC]: https://unicode.org/reports/tr15/#Norm_Forms + +## Name resolution + +A `wit` document is resolved after parsing to ensure that all names resolve +correctly. For example this is not a valid `wit` document: + +```wit +type foo = bar // ERROR: name `bar` not defined +``` + +Type references primarily happen through the `id` production of `ty`. + +Additionally names in a `wit` document can only be defined once: + +```wit +type foo = u32 +type foo = u64 // ERROR: name `foo` already defined +``` + +Names do not need to be defined before they're used (unlike in C or C++), +it's ok to define a type after it's used: + +```wit +type foo = bar + +record bar { + age: u32, +} +``` + +Types, however, cannot be recursive: + +```wit +type foo = foo // ERROR: cannot refer to itself + +record bar1 { + a: bar2, +} + +record bar2 { + a: bar1, // ERROR: record cannot refer to itself +} +``` + +The intention of `wit` is that it maps down to interface types, so the goal of +name resolution is to effectively create the type section of a wasm module using +interface types. The restrictions about self-referential types and such come +from how types can be defined in the interface types section. Additionally +definitions of named types such as `record foo { ... }` are intended to map +roughly to declarations in the type section of new types. diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/Cargo.toml new file mode 100644 index 0000000..a50dd8f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wit-bindgen-gen-c" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +doctest = false +test = false + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' } +heck = "0.3" +structopt = { version = "0.3", default-features = false, optional = true } + +[dev-dependencies] +test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-c'] } diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/build.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/build.rs new file mode 100644 index 0000000..d7c7fc2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + // this build script is currently only here so OUT_DIR is set for testing. +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/src/lib.rs new file mode 100644 index 0000000..0d54d81 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/src/lib.rs @@ -0,0 +1,2260 @@ +use heck::*; +use std::collections::{BTreeSet, HashMap, HashSet}; +use std::fmt::Write; +use std::mem; +use wit_bindgen_gen_core::wit_parser::abi::{ + AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType, +}; +use wit_bindgen_gen_core::{uwrite, uwriteln, wit_parser::*, Direction, Files, Generator, Ns}; + +#[derive(Default)] +pub struct C { + src: Source, + in_import: bool, + opts: Opts, + funcs: HashMap>, + return_pointer_area_size: usize, + return_pointer_area_align: usize, + sizes: SizeAlign, + names: Ns, + + // The set of types that are considered public (aka need to be in the + // header file) which are anonymous and we're effectively monomorphizing. + // This is discovered lazily when printing type names. + public_anonymous_types: BTreeSet, + + // This is similar to `public_anonymous_types` where it's discovered + // lazily, but the set here are for private types only used in the + // implementation of functions. These types go in the implementation file, + // not the header file. + private_anonymous_types: BTreeSet, + + // Type definitions for the given `TypeId`. This is printed topologically + // at the end. + types: HashMap, + + needs_string: bool, +} + +struct Func { + src: Source, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + // ... +} + +impl Opts { + pub fn build(&self) -> C { + let mut r = C::new(); + r.opts = self.clone(); + r + } +} + +#[derive(Debug)] +struct Return { + splat_tuple: bool, + scalar: Option, + retptrs: Vec, +} + +struct CSig { + name: String, + sig: String, + params: Vec<(bool, String)>, + ret: Return, + retptrs: Vec, +} + +#[derive(Debug)] +enum Scalar { + Void, + OptionBool(Type), + ExpectedEnum { err: TypeId, max_err: usize }, + Type(Type), +} + +impl C { + pub fn new() -> C { + C::default() + } + + fn abi_variant(dir: Direction) -> AbiVariant { + // This generator uses the obvious direction to ABI variant mapping. + match dir { + Direction::Export => AbiVariant::GuestExport, + Direction::Import => AbiVariant::GuestImport, + } + } + + fn classify_ret(&mut self, iface: &Interface, func: &Function) -> Return { + let mut ret = Return { + splat_tuple: false, + scalar: None, + retptrs: Vec::new(), + }; + ret.return_single(iface, &func.result, &func.result); + return ret; + } + + fn print_sig(&mut self, iface: &Interface, func: &Function) -> CSig { + let name = format!( + "{}_{}", + iface.name.to_snake_case(), + func.name.to_snake_case() + ); + self.names.insert(&name).expect("duplicate symbols"); + let start = self.src.h.len(); + + let ret = self.classify_ret(iface, func); + match &ret.scalar { + None | Some(Scalar::Void) => self.src.h("void"), + Some(Scalar::OptionBool(_id)) => self.src.h("bool"), + Some(Scalar::ExpectedEnum { err, .. }) => self.print_ty(iface, &Type::Id(*err)), + Some(Scalar::Type(ty)) => self.print_ty(iface, ty), + } + self.src.h(" "); + self.src.h(&name); + self.src.h("("); + let mut params = Vec::new(); + for (i, (name, ty)) in func.params.iter().enumerate() { + if i > 0 { + self.src.h(", "); + } + self.print_ty(iface, ty); + self.src.h(" "); + let pointer = self.is_arg_by_pointer(iface, ty); + if pointer { + self.src.h("*"); + } + let name = name.to_snake_case(); + self.src.h(&name); + params.push((pointer, name)); + } + let mut retptrs = Vec::new(); + for (i, ty) in ret.retptrs.iter().enumerate() { + if i > 0 || func.params.len() > 0 { + self.src.h(", "); + } + self.print_ty(iface, ty); + self.src.h(" *"); + let name = format!("ret{}", i); + self.src.h(&name); + retptrs.push(name); + } + if func.params.len() == 0 && ret.retptrs.len() == 0 { + self.src.h("void"); + } + self.src.h(")"); + + let sig = self.src.h[start..].to_string(); + self.src.h(";\n"); + + CSig { + sig, + name, + params, + ret, + retptrs, + } + } + + fn is_arg_by_pointer(&self, iface: &Interface, ty: &Type) -> bool { + match ty { + Type::Id(id) => match &iface.types[*id].kind { + TypeDefKind::Type(t) => self.is_arg_by_pointer(iface, t), + TypeDefKind::Variant(_) => true, + TypeDefKind::Union(_) => true, + TypeDefKind::Option(_) => true, + TypeDefKind::Expected(_) => true, + TypeDefKind::Enum(_) => false, + TypeDefKind::Flags(_) => false, + TypeDefKind::Tuple(_) | TypeDefKind::Record(_) | TypeDefKind::List(_) => true, + TypeDefKind::Future(_) => todo!("is_arg_by_pointer for future"), + TypeDefKind::Stream(_) => todo!("is_arg_by_pointer for stream"), + }, + Type::String => true, + _ => false, + } + } + + fn type_string(&mut self, iface: &Interface, ty: &Type) -> String { + // Getting a type string happens during codegen, and by default means + // that this is a private type that's being generated. This means we + // want to keep track of new anonymous types that are *only* mentioned + // in methods like this, so we can place those types in the C file + // instead of the header interface file. + let prev = mem::take(&mut self.src.h); + let prev_public = mem::take(&mut self.public_anonymous_types); + let prev_private = mem::take(&mut self.private_anonymous_types); + + // Print the type, which will collect into the fields that we replaced + // above. + self.print_ty(iface, ty); + + // Reset our public/private sets back to what they were beforehand. + // Note that `print_ty` always adds to the public set, so we're + // inverting the meaning here by interpreting those as new private + // types. + let new_private = mem::replace(&mut self.public_anonymous_types, prev_public); + assert!(self.private_anonymous_types.is_empty()); + self.private_anonymous_types = prev_private; + + // For all new private types found while we printed this type, if the + // type isn't already public then it's a new private type. + for id in new_private { + if !self.public_anonymous_types.contains(&id) { + self.private_anonymous_types.insert(id); + } + } + + mem::replace(&mut self.src.h, prev).into() + } + + fn print_ty(&mut self, iface: &Interface, ty: &Type) { + match ty { + Type::Unit => self.src.h("void"), + Type::Bool => self.src.h("bool"), + Type::Char => self.src.h("uint32_t"), // TODO: better type? + Type::U8 => self.src.h("uint8_t"), + Type::S8 => self.src.h("int8_t"), + Type::U16 => self.src.h("uint16_t"), + Type::S16 => self.src.h("int16_t"), + Type::U32 => self.src.h("uint32_t"), + Type::S32 => self.src.h("int32_t"), + Type::U64 => self.src.h("uint64_t"), + Type::S64 => self.src.h("int64_t"), + Type::Float32 => self.src.h("float"), + Type::Float64 => self.src.h("double"), + Type::Handle(id) => { + self.print_namespace(iface); + self.src.h(&iface.resources[*id].name.to_snake_case()); + self.src.h("_t"); + } + Type::String => { + self.print_namespace(iface); + self.src.h("string_t"); + self.needs_string = true; + } + Type::Id(id) => { + let ty = &iface.types[*id]; + match &ty.name { + Some(name) => { + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t"); + } + None => match &ty.kind { + TypeDefKind::Type(t) => self.print_ty(iface, t), + _ => { + self.public_anonymous_types.insert(*id); + self.private_anonymous_types.remove(id); + self.print_namespace(iface); + self.print_ty_name(iface, &Type::Id(*id)); + self.src.h("_t"); + } + }, + } + } + } + } + + fn print_ty_name(&mut self, iface: &Interface, ty: &Type) { + match ty { + Type::Unit => self.src.h("unit"), + Type::Bool => self.src.h("bool"), + Type::Char => self.src.h("char32"), + Type::U8 => self.src.h("u8"), + Type::S8 => self.src.h("s8"), + Type::U16 => self.src.h("u16"), + Type::S16 => self.src.h("s16"), + Type::U32 => self.src.h("u32"), + Type::S32 => self.src.h("s32"), + Type::U64 => self.src.h("u64"), + Type::S64 => self.src.h("s64"), + Type::Float32 => self.src.h("float32"), + Type::Float64 => self.src.h("float64"), + Type::Handle(id) => self.src.h(&iface.resources[*id].name.to_snake_case()), + Type::String => self.src.h("string"), + Type::Id(id) => { + let ty = &iface.types[*id]; + if let Some(name) = &ty.name { + return self.src.h(&name.to_snake_case()); + } + match &ty.kind { + TypeDefKind::Type(t) => self.print_ty_name(iface, t), + TypeDefKind::Record(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Union(_) => { + unimplemented!() + } + TypeDefKind::Tuple(t) => { + self.src.h("tuple"); + self.src.h(&t.types.len().to_string()); + for ty in t.types.iter() { + self.src.h("_"); + self.print_ty_name(iface, ty); + } + } + TypeDefKind::Option(ty) => { + self.src.h("option_"); + self.print_ty_name(iface, ty); + } + TypeDefKind::Expected(e) => { + self.src.h("expected_"); + self.print_ty_name(iface, &e.ok); + self.src.h("_"); + self.print_ty_name(iface, &e.err); + } + TypeDefKind::List(t) => { + self.src.h("list_"); + self.print_ty_name(iface, t); + } + TypeDefKind::Future(t) => { + self.src.h("future_"); + self.print_ty_name(iface, t); + } + TypeDefKind::Stream(s) => { + self.src.h("stream_"); + self.print_ty_name(iface, &s.element); + self.src.h("_"); + self.print_ty_name(iface, &s.end); + } + } + } + } + } + + fn print_anonymous_type(&mut self, iface: &Interface, ty: TypeId) { + let prev = mem::take(&mut self.src.h); + self.src.h("typedef "); + let kind = &iface.types[ty].kind; + match kind { + TypeDefKind::Type(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Record(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Union(_) => { + unreachable!() + } + TypeDefKind::Tuple(t) => { + self.src.h("struct {\n"); + for (i, t) in t.types.iter().enumerate() { + self.print_ty(iface, t); + uwriteln!(self.src.h, " f{i};"); + } + self.src.h("}"); + } + TypeDefKind::Option(t) => { + self.src.h("struct {\n"); + self.src.h("bool is_some;\n"); + if !self.is_empty_type(iface, t) { + self.print_ty(iface, t); + self.src.h(" val;\n"); + } + self.src.h("}"); + } + TypeDefKind::Expected(e) => { + self.src.h("struct { + bool is_err; + union { + "); + if !self.is_empty_type(iface, &e.ok) { + self.print_ty(iface, &e.ok); + self.src.h(" ok;\n"); + } + if !self.is_empty_type(iface, &e.err) { + self.print_ty(iface, &e.err); + self.src.h(" err;\n"); + } + self.src.h("} val;\n"); + self.src.h("}"); + } + TypeDefKind::List(t) => { + self.src.h("struct {\n"); + self.print_ty(iface, t); + self.src.h(" *ptr;\n"); + self.src.h("size_t len;\n"); + self.src.h("}"); + } + TypeDefKind::Future(_) => todo!("print_anonymous_type for future"), + TypeDefKind::Stream(_) => todo!("print_anonymous_type for stream"), + } + self.src.h(" "); + self.print_namespace(iface); + self.print_ty_name(iface, &Type::Id(ty)); + self.src.h("_t;\n"); + self.types.insert(ty, mem::replace(&mut self.src.h, prev)); + } + + fn is_empty_type(&self, iface: &Interface, ty: &Type) -> bool { + let id = match ty { + Type::Id(id) => *id, + Type::Unit => return true, + _ => return false, + }; + match &iface.types[id].kind { + TypeDefKind::Type(t) => self.is_empty_type(iface, t), + TypeDefKind::Record(r) => r.fields.is_empty(), + TypeDefKind::Tuple(t) => t.types.is_empty(), + _ => false, + } + } + + fn print_intrinsics(&mut self) { + // Note that these intrinsics are declared as `weak` so they can be + // overridden from some other symbol. + self.src.c(" + __attribute__((weak, export_name(\"canonical_abi_realloc\"))) + void *canonical_abi_realloc( + void *ptr, + size_t orig_size, + size_t org_align, + size_t new_size + ) { + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + return ret; + } + + __attribute__((weak, export_name(\"canonical_abi_free\"))) + void canonical_abi_free( + void *ptr, + size_t size, + size_t align + ) { + free(ptr); + } + "); + } + + fn print_namespace(&mut self, iface: &Interface) { + self.src.h(&iface.name.to_snake_case()); + self.src.h("_"); + } + + fn print_dtor(&mut self, iface: &Interface, id: TypeId) { + let ty = Type::Id(id); + if !self.owns_anything(iface, &ty) { + return; + } + let pos = self.src.h.len(); + self.src.h("void "); + self.print_namespace(iface); + self.print_ty_name(iface, &ty); + self.src.h("_free("); + self.print_namespace(iface); + self.print_ty_name(iface, &ty); + self.src.h("_t *ptr)"); + + self.src.c(&self.src.h[pos..].to_string()); + self.src.h(";\n"); + self.src.c(" {\n"); + match &iface.types[id].kind { + TypeDefKind::Type(t) => self.free(iface, t, "ptr"), + + TypeDefKind::Flags(_) => {} + TypeDefKind::Enum(_) => {} + + TypeDefKind::Record(r) => { + for field in r.fields.iter() { + if !self.owns_anything(iface, &field.ty) { + continue; + } + self.free( + iface, + &field.ty, + &format!("&ptr->{}", field.name.to_snake_case()), + ); + } + } + + TypeDefKind::Tuple(t) => { + for (i, ty) in t.types.iter().enumerate() { + if !self.owns_anything(iface, ty) { + continue; + } + self.free(iface, ty, &format!("&ptr->f{i}")); + } + } + + TypeDefKind::List(t) => { + if self.owns_anything(iface, t) { + self.src.c("for (size_t i = 0; i < ptr->len; i++) {\n"); + self.free(iface, t, "&ptr->ptr[i]"); + self.src.c("}\n"); + } + uwriteln!( + self.src.c, + "canonical_abi_free(ptr->ptr, ptr->len * {}, {});", + self.sizes.size(t), + self.sizes.align(t), + ); + } + + TypeDefKind::Variant(v) => { + self.src.c("switch ((int32_t) ptr->tag) {\n"); + for (i, case) in v.cases.iter().enumerate() { + if !self.owns_anything(iface, &case.ty) { + continue; + } + uwriteln!(self.src.c, "case {}: {{", i); + let expr = format!("&ptr->val.{}", case.name.to_snake_case()); + self.free(iface, &case.ty, &expr); + self.src.c("break;\n"); + self.src.c("}\n"); + } + self.src.c("}\n"); + } + + TypeDefKind::Union(u) => { + self.src.c("switch ((int32_t) ptr->tag) {\n"); + for (i, case) in u.cases.iter().enumerate() { + if !self.owns_anything(iface, &case.ty) { + continue; + } + uwriteln!(self.src.c, "case {i}: {{"); + let expr = format!("&ptr->val.f{i}"); + self.free(iface, &case.ty, &expr); + self.src.c("break;\n"); + self.src.c("}\n"); + } + self.src.c("}\n"); + } + + TypeDefKind::Option(t) => { + self.src.c("if (ptr->is_some) {\n"); + self.free(iface, t, "&ptr->val"); + self.src.c("}\n"); + } + + TypeDefKind::Expected(e) => { + self.src.c("if (!ptr->is_err) {\n"); + if self.owns_anything(iface, &e.ok) { + self.free(iface, &e.ok, "&ptr->val.ok"); + } + if self.owns_anything(iface, &e.err) { + self.src.c("} else {\n"); + self.free(iface, &e.err, "&ptr->val.err"); + } + self.src.c("}\n"); + } + TypeDefKind::Future(_) => todo!("print_dtor for future"), + TypeDefKind::Stream(_) => todo!("print_dtor for stream"), + } + self.src.c("}\n"); + } + + fn owns_anything(&self, iface: &Interface, ty: &Type) -> bool { + let id = match ty { + Type::Id(id) => *id, + Type::String => return true, + Type::Handle(_) => return true, + _ => return false, + }; + match &iface.types[id].kind { + TypeDefKind::Type(t) => self.owns_anything(iface, t), + TypeDefKind::Record(r) => r.fields.iter().any(|t| self.owns_anything(iface, &t.ty)), + TypeDefKind::Tuple(t) => t.types.iter().any(|t| self.owns_anything(iface, t)), + TypeDefKind::Flags(_) => false, + TypeDefKind::Enum(_) => false, + TypeDefKind::List(_) => true, + TypeDefKind::Variant(v) => v.cases.iter().any(|c| self.owns_anything(iface, &c.ty)), + TypeDefKind::Union(v) => v + .cases + .iter() + .any(|case| self.owns_anything(iface, &case.ty)), + TypeDefKind::Option(t) => self.owns_anything(iface, t), + TypeDefKind::Expected(e) => { + self.owns_anything(iface, &e.ok) || self.owns_anything(iface, &e.err) + } + TypeDefKind::Future(_) => todo!("owns_anything for future"), + TypeDefKind::Stream(_) => todo!("owns_anything for stream"), + } + } + + fn free(&mut self, iface: &Interface, ty: &Type, expr: &str) { + let prev = mem::take(&mut self.src.h); + self.print_namespace(iface); + self.print_ty_name(iface, ty); + let name = mem::replace(&mut self.src.h, prev); + + self.src.c(&name); + self.src.c("_free("); + self.src.c(expr); + self.src.c(");\n"); + } + + fn docs(&mut self, docs: &Docs) { + let docs = match &docs.contents { + Some(docs) => docs, + None => return, + }; + for line in docs.trim().lines() { + self.src.h("// "); + self.src.h(line); + self.src.h("\n"); + } + } +} + +impl Return { + fn return_single(&mut self, iface: &Interface, ty: &Type, orig_ty: &Type) { + let id = match ty { + Type::Id(id) => *id, + Type::String => { + self.retptrs.push(*orig_ty); + return; + } + Type::Unit => { + self.scalar = Some(Scalar::Void); + return; + } + _ => { + self.scalar = Some(Scalar::Type(*orig_ty)); + return; + } + }; + match &iface.types[id].kind { + TypeDefKind::Type(t) => self.return_single(iface, t, orig_ty), + + // Flags are returned as their bare values + TypeDefKind::Flags(_) => { + self.scalar = Some(Scalar::Type(*orig_ty)); + } + + // Tuples get splatted to multiple return pointers + TypeDefKind::Tuple(_) => self.splat_tuples(iface, ty, orig_ty), + + // Record returns are always through a pointer + TypeDefKind::Record(_) => { + self.retptrs.push(*orig_ty); + } + + // other records/lists/buffers always go to return pointers + TypeDefKind::List(_) => self.retptrs.push(*orig_ty), + + // Enums are scalars + TypeDefKind::Enum(_) => { + self.scalar = Some(Scalar::Type(*orig_ty)); + } + + // Unpack optional returns where a boolean discriminant is + // returned and then the actual type returned is returned + // through a return pointer. + TypeDefKind::Option(ty) => { + self.scalar = Some(Scalar::OptionBool(*ty)); + self.retptrs.push(*ty); + } + + // Unpack `expected` returns where `E` looks like an enum + // so we can return that in the scalar return and have `T` get + // returned through the normal returns. + TypeDefKind::Expected(e) => { + if let Type::Id(err) = e.err { + if let TypeDefKind::Enum(enum_) = &iface.types[err].kind { + self.scalar = Some(Scalar::ExpectedEnum { + err, + max_err: enum_.cases.len(), + }); + self.splat_tuples(iface, &e.ok, &e.ok); + return; + } + } + + // otherwise just return the variant via a normal + // return pointer + self.retptrs.push(*orig_ty); + } + + TypeDefKind::Variant(_) | TypeDefKind::Union(_) => { + self.retptrs.push(*orig_ty); + } + TypeDefKind::Future(_) => todo!("return_single for future"), + TypeDefKind::Stream(_) => todo!("return_single for stream"), + } + } + + fn splat_tuples(&mut self, iface: &Interface, ty: &Type, orig_ty: &Type) { + let id = match ty { + Type::Id(id) => *id, + Type::Unit => return, + _ => { + self.retptrs.push(*orig_ty); + return; + } + }; + match &iface.types[id].kind { + TypeDefKind::Tuple(t) => { + self.splat_tuple = true; + self.retptrs.extend(t.types.iter()); + } + _ => self.retptrs.push(*orig_ty), + } + } +} + +impl Generator for C { + fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + let variant = Self::abi_variant(dir); + self.sizes.fill(iface); + self.in_import = variant == AbiVariant::GuestImport; + } + + fn type_record( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.names.insert(&name.to_snake_case()).unwrap(); + self.src.h("typedef struct {\n"); + for field in record.fields.iter() { + self.print_ty(iface, &field.ty); + self.src.h(" "); + self.src.h(&field.name.to_snake_case()); + self.src.h(";\n"); + } + self.src.h("} "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_tuple( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + tuple: &Tuple, + docs: &Docs, + ) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.names.insert(&name.to_snake_case()).unwrap(); + self.src.h("typedef struct {\n"); + for (i, ty) in tuple.types.iter().enumerate() { + self.print_ty(iface, ty); + uwriteln!(self.src.h, " f{i};"); + } + self.src.h("} "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_flags( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + flags: &Flags, + docs: &Docs, + ) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.names.insert(&name.to_snake_case()).unwrap(); + self.src.h("typedef "); + let repr = flags_repr(flags); + self.src.h(int_repr(repr)); + self.src.h(" "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + + for (i, flag) in flags.flags.iter().enumerate() { + uwriteln!( + self.src.h, + "#define {}_{}_{} (1 << {})", + iface.name.to_shouty_snake_case(), + name.to_shouty_snake_case(), + flag.name.to_shouty_snake_case(), + i, + ); + } + + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_variant( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + variant: &Variant, + docs: &Docs, + ) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.names.insert(&name.to_snake_case()).unwrap(); + self.src.h("typedef struct {\n"); + self.src.h(int_repr(variant.tag())); + self.src.h(" tag;\n"); + self.src.h("union {\n"); + for case in variant.cases.iter() { + if self.is_empty_type(iface, &case.ty) { + continue; + } + self.print_ty(iface, &case.ty); + self.src.h(" "); + self.src.h(&case.name.to_snake_case()); + self.src.h(";\n"); + } + self.src.h("} val;\n"); + self.src.h("} "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + for (i, case) in variant.cases.iter().enumerate() { + uwriteln!( + self.src.h, + "#define {}_{}_{} {}", + iface.name.to_shouty_snake_case(), + name.to_shouty_snake_case(), + case.name.to_shouty_snake_case(), + i, + ); + } + + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_union( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + union: &Union, + docs: &Docs, + ) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.names.insert(&name.to_snake_case()).unwrap(); + self.src.h("typedef struct {\n"); + self.src.h(int_repr(union.tag())); + self.src.h(" tag;\n"); + self.src.h("union {\n"); + for (i, case) in union.cases.iter().enumerate() { + self.print_ty(iface, &case.ty); + uwriteln!(self.src.h, " f{i};"); + } + self.src.h("} val;\n"); + self.src.h("} "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_option( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + payload: &Type, + docs: &Docs, + ) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.names.insert(&name.to_snake_case()).unwrap(); + self.src.h("typedef struct {\n"); + self.src.h("bool is_some;\n"); + if !self.is_empty_type(iface, payload) { + self.print_ty(iface, payload); + self.src.h(" val;\n"); + } + self.src.h("} "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_expected( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + expected: &Expected, + docs: &Docs, + ) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.names.insert(&name.to_snake_case()).unwrap(); + self.src.h("typedef struct {\n"); + self.src.h("bool is_err;\n"); + self.src.h("union {\n"); + if !self.is_empty_type(iface, &expected.ok) { + self.print_ty(iface, &expected.ok); + self.src.h(" ok;\n"); + } + if !self.is_empty_type(iface, &expected.err) { + self.print_ty(iface, &expected.err); + self.src.h(" err;\n"); + } + self.src.h("} val;\n"); + self.src.h("} "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_enum(&mut self, iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.names.insert(&name.to_snake_case()).unwrap(); + self.src.h("typedef "); + self.src.h(int_repr(enum_.tag())); + self.src.h(" "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + for (i, case) in enum_.cases.iter().enumerate() { + uwriteln!( + self.src.h, + "#define {}_{}_{} {}", + iface.name.to_shouty_snake_case(), + name.to_shouty_snake_case(), + case.name.to_shouty_snake_case(), + i, + ); + } + + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_resource(&mut self, iface: &Interface, ty: ResourceId) { + drop((iface, ty)); + } + + fn type_alias(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.src.h("typedef "); + self.print_ty(iface, ty); + self.src.h(" "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_list(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let prev = mem::take(&mut self.src.h); + self.docs(docs); + self.src.h("typedef struct {\n"); + self.print_ty(iface, ty); + self.src.h(" *ptr;\n"); + self.src.h("size_t len;\n"); + self.src.h("} "); + self.print_namespace(iface); + self.src.h(&name.to_snake_case()); + self.src.h("_t;\n"); + self.types.insert(id, mem::replace(&mut self.src.h, prev)); + } + + fn type_builtin(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + drop((iface, _id, name, ty, docs)); + } + + fn import(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let prev = mem::take(&mut self.src); + let sig = iface.wasm_signature(AbiVariant::GuestImport, func); + + // In the private C file, print a function declaration which is the + // actual wasm import that we'll be calling, and this has the raw wasm + // signature. + uwriteln!( + self.src.c, + "__attribute__((import_module(\"{}\"), import_name(\"{}\")))", + iface.name, + func.name + ); + let import_name = self.names.tmp(&format!( + "__wasm_import_{}_{}", + iface.name.to_snake_case(), + func.name.to_snake_case() + )); + match sig.results.len() { + 0 => self.src.c("void"), + 1 => self.src.c(wasm_type(sig.results[0])), + _ => unimplemented!("multi-value return not supported"), + } + self.src.c(" "); + self.src.c(&import_name); + self.src.c("("); + for (i, param) in sig.params.iter().enumerate() { + if i > 0 { + self.src.c(", "); + } + self.src.c(wasm_type(*param)); + } + if sig.params.len() == 0 { + self.src.c("void"); + } + self.src.c(");\n"); + + // Print the public facing signature into the header, and since that's + // what we are defining also print it into the C file. + let c_sig = self.print_sig(iface, func); + self.src.c(&c_sig.sig); + self.src.c(" {\n"); + + let mut f = FunctionBindgen::new(self, c_sig, &import_name); + for (pointer, param) in f.sig.params.iter() { + f.locals.insert(param).unwrap(); + + if *pointer { + f.params.push(format!("*{}", param)); + } else { + f.params.push(param.clone()); + } + } + for ptr in f.sig.retptrs.iter() { + f.locals.insert(ptr).unwrap(); + } + iface.call( + AbiVariant::GuestImport, + LiftLower::LowerArgsLiftResults, + func, + &mut f, + ); + + let FunctionBindgen { src, .. } = f; + + self.src.c(&String::from(src)); + self.src.c("}\n"); + + let src = mem::replace(&mut self.src, prev); + self.funcs + .entry(iface.name.to_string()) + .or_insert(Vec::new()) + .push(Func { src }); + } + + fn export(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let prev = mem::take(&mut self.src); + let sig = iface.wasm_signature(AbiVariant::GuestExport, func); + + // Print the actual header for this function into the header file, and + // it's what we'll be calling. + let c_sig = self.print_sig(iface, func); + + // Generate, in the C source file, the raw wasm signature that has the + // canonical ABI. + uwriteln!( + self.src.c, + "__attribute__((export_name(\"{}\")))", + func.name + ); + let import_name = self.names.tmp(&format!( + "__wasm_export_{}_{}", + iface.name.to_snake_case(), + func.name.to_snake_case() + )); + + let mut f = FunctionBindgen::new(self, c_sig, &import_name); + match sig.results.len() { + 0 => f.gen.src.c("void"), + 1 => f.gen.src.c(wasm_type(sig.results[0])), + _ => unimplemented!("multi-value return not supported"), + } + f.gen.src.c(" "); + f.gen.src.c(&import_name); + f.gen.src.c("("); + for (i, param) in sig.params.iter().enumerate() { + if i > 0 { + f.gen.src.c(", "); + } + let name = f.locals.tmp("arg"); + uwrite!(f.gen.src.c, "{} {}", wasm_type(*param), name); + f.params.push(name); + } + if sig.params.len() == 0 { + f.gen.src.c("void"); + } + f.gen.src.c(") {\n"); + + // Perform all lifting/lowering and append it to our src. + iface.call( + AbiVariant::GuestExport, + LiftLower::LiftArgsLowerResults, + func, + &mut f, + ); + let FunctionBindgen { src, .. } = f; + self.src.c(&src); + self.src.c("}\n"); + + let src = mem::replace(&mut self.src, prev); + self.funcs + .entry(iface.name.to_string()) + .or_insert(Vec::new()) + .push(Func { src }); + } + + fn finish_one(&mut self, iface: &Interface, files: &mut Files) { + uwrite!( + self.src.h, + "\ + #ifndef __BINDINGS_{0}_H + #define __BINDINGS_{0}_H + #ifdef __cplusplus + extern \"C\" + {{ + #endif + + #include + #include + ", + iface.name.to_shouty_snake_case(), + ); + uwrite!( + self.src.c, + "\ + #include + #include <{}.h> + ", + iface.name.to_kebab_case(), + ); + + self.print_intrinsics(); + + for (_, resource) in iface.resources.iter() { + let ns = iface.name.to_snake_case(); + let name = resource.name.to_snake_case(); + uwrite!( + self.src.h, + " + typedef struct {{ + uint32_t idx; + }} {ns}_{name}_t; + void {ns}_{name}_free({ns}_{name}_t *ptr); + {ns}_{name}_t {ns}_{name}_clone({ns}_{name}_t *ptr); + ", + ns = ns, + name = name, + ); + uwrite!( + self.src.c, + " + __attribute__((import_module(\"canonical_abi\"), import_name(\"resource_drop_{name_orig}\"))) + void __resource_{name}_drop(uint32_t idx); + + void {ns}_{name}_free({ns}_{name}_t *ptr) {{ + __resource_{name}_drop(ptr->idx); + }} + + __attribute__((import_module(\"canonical_abi\"), import_name(\"resource_clone_{name_orig}\"))) + uint32_t __resource_{name}_clone(uint32_t idx); + + {ns}_{name}_t {ns}_{name}_clone({ns}_{name}_t *ptr) {{ + return ({ns}_{name}_t){{__resource_{name}_clone(ptr->idx)}}; + }} + ", + ns = ns, + name = name, + name_orig = resource.name, + ); + + // Exported resources have more capabilities, they can create new + // resources and get the private value that it was created with. + // Furthermore we also define the destructor which delegates to the + // actual user-defined destructor, if any. + if !self.in_import { + uwrite!( + self.src.h, + "\ + {ns}_{name}_t {ns}_{name}_new(void *data); + void* {ns}_{name}_get({ns}_{name}_t *ptr); + + __attribute__((weak)) + void {ns}_{name}_dtor(void *data); + ", + ns = ns, + name = name, + ); + uwrite!( + self.src.c, + " + __attribute__((import_module(\"canonical_abi\"), import_name(\"resource_new_{name_orig}\"))) + uint32_t __resource_{name}_new(uint32_t val); + + {ns}_{name}_t {ns}_{name}_new(void *data) {{ + return ({ns}_{name}_t){{__resource_{name}_new((uint32_t) data)}}; + }} + + __attribute__((import_module(\"canonical_abi\"), import_name(\"resource_get_{name_orig}\"))) + uint32_t __resource_{name}_get(uint32_t idx); + + void* {ns}_{name}_get({ns}_{name}_t *ptr) {{ + return (void*) __resource_{name}_get(ptr->idx); + }} + + __attribute__((export_name(\"canonical_abi_drop_{name_orig}\"))) + void __resource_{name}_dtor(uint32_t val) {{ + if ({ns}_{name}_dtor) + {ns}_{name}_dtor((void*) val); + }} + ", + ns = ns, + name = name, + name_orig = resource.name, + ); + } + } + + // Continuously generate anonymous types while we continue to find more + // + // First we take care of the public set of anonymous types. This will + // iteratively print them and also remove any references from the + // private set if we happen to also reference them. + while !self.public_anonymous_types.is_empty() { + for ty in mem::take(&mut self.public_anonymous_types) { + self.print_anonymous_type(iface, ty); + } + } + + // Next we take care of private types. To do this we have basically the + // same loop as above, after we switch the sets. We record, however, + // all private types in a local set here to later determine if the type + // needs to be in the C file or the H file. + // + // Note though that we don't re-print a type (and consider it private) + // if we already printed it above as part of the public set. + let mut private_types = HashSet::new(); + self.public_anonymous_types = mem::take(&mut self.private_anonymous_types); + while !self.public_anonymous_types.is_empty() { + for ty in mem::take(&mut self.public_anonymous_types) { + if self.types.contains_key(&ty) { + continue; + } + private_types.insert(ty); + self.print_anonymous_type(iface, ty); + } + } + + if self.needs_string { + uwrite!( + self.src.h, + " + typedef struct {{ + char *ptr; + size_t len; + }} {0}_string_t; + + void {0}_string_set({0}_string_t *ret, const char *s); + void {0}_string_dup({0}_string_t *ret, const char *s); + void {0}_string_free({0}_string_t *ret); + ", + iface.name.to_snake_case(), + ); + self.src.c("#include \n"); + uwrite!( + self.src.c, + " + void {0}_string_set({0}_string_t *ret, const char *s) {{ + ret->ptr = (char*) s; + ret->len = strlen(s); + }} + + void {0}_string_dup({0}_string_t *ret, const char *s) {{ + ret->len = strlen(s); + ret->ptr = canonical_abi_realloc(NULL, 0, 1, ret->len); + memcpy(ret->ptr, s, ret->len); + }} + + void {0}_string_free({0}_string_t *ret) {{ + canonical_abi_free(ret->ptr, ret->len, 1); + ret->ptr = NULL; + ret->len = 0; + }} + ", + iface.name.to_snake_case(), + ); + } + + // Afterwards print all types. Note that this print must be in a + // topological order, so we + for id in iface.topological_types() { + if let Some(ty) = self.types.get(&id) { + if private_types.contains(&id) { + self.src.c(ty); + } else { + self.src.h(ty); + self.print_dtor(iface, id); + } + } + } + + if self.return_pointer_area_size > 0 { + uwrite!( + self.src.c, + " + __attribute__((aligned({}))) + static uint8_t RET_AREA[{}]; + ", + self.return_pointer_area_align, + self.return_pointer_area_size, + ); + } + + for (_module, funcs) in mem::take(&mut self.funcs) { + for func in funcs { + self.src.h(&func.src.h); + self.src.c(&func.src.c); + } + } + + self.src.h("\ + #ifdef __cplusplus + } + #endif + "); + self.src.h("#endif\n"); + + files.push( + &format!("{}.c", iface.name.to_kebab_case()), + self.src.c.as_bytes(), + ); + files.push( + &format!("{}.h", iface.name.to_kebab_case()), + self.src.h.as_bytes(), + ); + } +} + +struct FunctionBindgen<'a> { + gen: &'a mut C, + locals: Ns, + // tmp: usize, + src: wit_bindgen_gen_core::Source, + sig: CSig, + func_to_call: &'a str, + block_storage: Vec, + blocks: Vec<(String, Vec)>, + payloads: Vec, + params: Vec, + wasm_return: Option, +} + +impl<'a> FunctionBindgen<'a> { + fn new(gen: &'a mut C, sig: CSig, func_to_call: &'a str) -> FunctionBindgen<'a> { + FunctionBindgen { + gen, + sig, + locals: Default::default(), + src: Default::default(), + func_to_call, + block_storage: Vec::new(), + blocks: Vec::new(), + payloads: Vec::new(), + params: Vec::new(), + wasm_return: None, + } + } + + fn store_op(&mut self, op: &str, loc: &str) { + self.src.push_str(loc); + self.src.push_str(" = "); + self.src.push_str(op); + self.src.push_str(";\n"); + } + + fn load(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec) { + results.push(format!("*(({}*) ({} + {}))", ty, operands[0], offset)); + } + + fn load_ext(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec) { + self.load(ty, offset, operands, results); + let result = results.pop().unwrap(); + results.push(format!("(int32_t) ({})", result)); + } + + fn store(&mut self, ty: &str, offset: i32, operands: &[String]) { + uwriteln!( + self.src, + "*(({}*)({} + {})) = {};", + ty, + operands[1], + offset, + operands[0] + ); + } + + fn store_in_retptrs(&mut self, operands: &[String]) { + if self.sig.ret.splat_tuple { + assert_eq!(operands.len(), 1); + let op = &operands[0]; + for (i, ptr) in self.sig.retptrs.clone().into_iter().enumerate() { + self.store_op(&format!("{}.f{}", op, i), &format!("*{}", ptr)); + } + // ... + } else { + assert_eq!(operands.len(), self.sig.retptrs.len()); + for (op, ptr) in operands.iter().zip(self.sig.retptrs.clone()) { + self.store_op(op, &format!("*{}", ptr)); + } + } + } +} + +impl Bindgen for FunctionBindgen<'_> { + type Operand = String; + + fn sizes(&self) -> &SizeAlign { + &self.gen.sizes + } + + fn push_block(&mut self) { + let prev = mem::take(&mut self.src); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().unwrap(); + let src = mem::replace(&mut self.src, to_restore); + self.blocks.push((src.into(), mem::take(operands))); + } + + fn return_pointer(&mut self, _iface: &Interface, size: usize, align: usize) -> String { + self.gen.return_pointer_area_size = self.gen.return_pointer_area_size.max(size); + self.gen.return_pointer_area_align = self.gen.return_pointer_area_align.max(align); + let ptr = self.locals.tmp("ptr"); + uwriteln!(self.src, "int32_t {} = (int32_t) &RET_AREA;", ptr); + ptr + } + + fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { + iface.all_bits_valid(ty) + } + + fn emit( + &mut self, + iface: &Interface, + 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 _ in tys.iter() { + results.push("0".to_string()); + } + } + + // TODO: checked? + Instruction::U8FromI32 => results.push(format!("(uint8_t) ({})", operands[0])), + Instruction::S8FromI32 => results.push(format!("(int8_t) ({})", operands[0])), + Instruction::U16FromI32 => results.push(format!("(uint16_t) ({})", operands[0])), + Instruction::S16FromI32 => results.push(format!("(int16_t) ({})", operands[0])), + Instruction::U32FromI32 => results.push(format!("(uint32_t) ({})", operands[0])), + Instruction::S32FromI32 | Instruction::S64FromI64 => results.push(operands[0].clone()), + Instruction::U64FromI64 => results.push(format!("(uint64_t) ({})", operands[0])), + + Instruction::I32FromU8 + | Instruction::I32FromS8 + | Instruction::I32FromU16 + | Instruction::I32FromS16 + | Instruction::I32FromU32 => { + results.push(format!("(int32_t) ({})", operands[0])); + } + Instruction::I32FromS32 | Instruction::I64FromS64 => results.push(operands[0].clone()), + Instruction::I64FromU64 => { + results.push(format!("(int64_t) ({})", operands[0])); + } + + // f32/f64 have the same representation in the import type and in C, + // so no conversions necessary. + Instruction::F32FromFloat32 + | Instruction::F64FromFloat64 + | Instruction::Float32FromF32 + | Instruction::Float64FromF64 => { + results.push(operands[0].clone()); + } + + // TODO: checked + Instruction::CharFromI32 => { + results.push(format!("(uint32_t) ({})", operands[0])); + } + Instruction::I32FromChar => { + results.push(format!("(int32_t) ({})", operands[0])); + } + + Instruction::Bitcasts { casts } => { + for (cast, op) in casts.iter().zip(operands) { + let op = op; + match cast { + Bitcast::I32ToF32 | Bitcast::I64ToF32 => { + results + .push(format!("((union {{ int32_t a; float b; }}){{ {} }}).b", op)); + } + Bitcast::F32ToI32 | Bitcast::F32ToI64 => { + results + .push(format!("((union {{ float a; int32_t b; }}){{ {} }}).b", op)); + } + Bitcast::I64ToF64 => { + results.push(format!( + "((union {{ int64_t a; double b; }}){{ {} }}).b", + op + )); + } + Bitcast::F64ToI64 => { + results.push(format!( + "((union {{ double a; int64_t b; }}){{ {} }}).b", + op + )); + } + Bitcast::I32ToI64 => { + results.push(format!("(int64_t) {}", op)); + } + Bitcast::I64ToI32 => { + results.push(format!("(int32_t) {}", op)); + } + Bitcast::None => results.push(op.to_string()), + } + } + } + + Instruction::UnitLower => {} + Instruction::UnitLift => { + results.push("INVALID".to_string()); + } + Instruction::BoolFromI32 | Instruction::I32FromBool => { + results.push(operands[0].clone()); + } + + Instruction::I32FromOwnedHandle { .. } | Instruction::I32FromBorrowedHandle { .. } => { + results.push(format!("({}).idx", operands[0])); + } + + Instruction::HandleBorrowedFromI32 { ty, .. } + | Instruction::HandleOwnedFromI32 { ty, .. } => { + results.push(format!( + "({}_{}_t){{ {} }}", + iface.name.to_snake_case(), + iface.resources[*ty].name.to_snake_case(), + operands[0], + )); + } + + Instruction::RecordLower { record, .. } => { + let op = &operands[0]; + for f in record.fields.iter() { + results.push(format!("({}).{}", op, f.name.to_snake_case())); + } + } + Instruction::RecordLift { ty, .. } => { + let name = self.gen.type_string(iface, &Type::Id(*ty)); + let mut result = format!("({}) {{\n", name); + for op in operands { + uwriteln!(result, "{},", op); + } + result.push_str("}"); + results.push(result); + } + + Instruction::TupleLower { tuple, .. } => { + let op = &operands[0]; + for i in 0..tuple.types.len() { + results.push(format!("({}).f{}", op, i)); + } + } + Instruction::TupleLift { ty, .. } => { + let name = self.gen.type_string(iface, &Type::Id(*ty)); + let mut result = format!("({}) {{\n", name); + for op in operands { + uwriteln!(result, "{},", op); + } + result.push_str("}"); + results.push(result); + } + + // TODO: checked + Instruction::FlagsLower { flags, ty, .. } => match flags_repr(flags) { + Int::U8 | Int::U16 | Int::U32 => { + results.push(operands.pop().unwrap()); + } + Int::U64 => { + let name = self.gen.type_string(iface, &Type::Id(*ty)); + let tmp = self.locals.tmp("flags"); + uwriteln!(self.src, "{name} {tmp} = {};", operands[0]); + results.push(format!("{tmp} & 0xffffffff")); + results.push(format!("({tmp} >> 32) & 0xffffffff")); + } + }, + + Instruction::FlagsLift { flags, ty, .. } => match flags_repr(flags) { + Int::U8 | Int::U16 | Int::U32 => { + results.push(operands.pop().unwrap()); + } + Int::U64 => { + let name = self.gen.type_string(iface, &Type::Id(*ty)); + let op0 = &operands[0]; + let op1 = &operands[1]; + results.push(format!("(({name}) ({op0})) | ((({name}) ({op1})) << 32)")); + } + }, + + Instruction::VariantPayloadName => { + let name = self.locals.tmp("payload"); + results.push(format!("*{}", name)); + self.payloads.push(name); + } + + Instruction::VariantLower { + variant, + results: result_types, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + let payloads = self + .payloads + .drain(self.payloads.len() - variant.cases.len()..) + .collect::>(); + + let mut variant_results = Vec::with_capacity(result_types.len()); + for ty in result_types.iter() { + let name = self.locals.tmp("variant"); + results.push(name.clone()); + self.src.push_str(wasm_type(*ty)); + self.src.push_str(" "); + self.src.push_str(&name); + self.src.push_str(";\n"); + variant_results.push(name); + } + + let expr_to_match = format!("({}).tag", operands[0]); + + uwriteln!(self.src, "switch ((int32_t) {}) {{", expr_to_match); + for (i, ((case, (block, block_results)), payload)) in + variant.cases.iter().zip(blocks).zip(payloads).enumerate() + { + uwriteln!(self.src, "case {}: {{", i); + if !self.gen.is_empty_type(iface, &case.ty) { + let ty = self.gen.type_string(iface, &case.ty); + uwrite!( + self.src, + "const {} *{} = &({}).val", + ty, + payload, + operands[0], + ); + self.src.push_str("."); + self.src.push_str(&case.name.to_snake_case()); + self.src.push_str(";\n"); + } + self.src.push_str(&block); + + for (name, result) in variant_results.iter().zip(&block_results) { + uwriteln!(self.src, "{} = {};", name, result); + } + self.src.push_str("break;\n}\n"); + } + self.src.push_str("}\n"); + } + + Instruction::VariantLift { variant, ty, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + + let ty = self.gen.type_string(iface, &Type::Id(*ty)); + let result = self.locals.tmp("variant"); + uwriteln!(self.src, "{} {};", ty, result); + uwriteln!(self.src, "{}.tag = {};", result, operands[0]); + uwriteln!(self.src, "switch ((int32_t) {}.tag) {{", result); + for (i, (case, (block, block_results))) in + variant.cases.iter().zip(blocks).enumerate() + { + uwriteln!(self.src, "case {}: {{", i); + self.src.push_str(&block); + assert!(block_results.len() == 1); + + if !self.gen.is_empty_type(iface, &case.ty) { + let mut dst = format!("{}.val", result); + dst.push_str("."); + dst.push_str(&case.name.to_snake_case()); + self.store_op(&block_results[0], &dst); + } + self.src.push_str("break;\n}\n"); + } + self.src.push_str("}\n"); + results.push(result); + } + + Instruction::UnionLower { + union, + results: result_types, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + let payloads = self + .payloads + .drain(self.payloads.len() - union.cases.len()..) + .collect::>(); + + let mut union_results = Vec::with_capacity(result_types.len()); + for ty in result_types.iter() { + let name = self.locals.tmp("unionres"); + results.push(name.clone()); + let ty = wasm_type(*ty); + uwriteln!(self.src, "{ty} {name};"); + union_results.push(name); + } + + let op0 = &operands[0]; + uwriteln!(self.src, "switch (({op0}).tag) {{"); + for (i, ((case, (block, block_results)), payload)) in + union.cases.iter().zip(blocks).zip(payloads).enumerate() + { + uwriteln!(self.src, "case {i}: {{"); + if !self.gen.is_empty_type(iface, &case.ty) { + let ty = self.gen.type_string(iface, &case.ty); + uwriteln!(self.src, "const {ty} *{payload} = &({op0}).val.f{i};"); + } + self.src.push_str(&block); + + for (name, result) in union_results.iter().zip(&block_results) { + uwriteln!(self.src, "{name} = {result};"); + } + self.src.push_str("break;\n}\n"); + } + self.src.push_str("}\n"); + } + + Instruction::UnionLift { union, ty, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + + let ty = self.gen.type_string(iface, &Type::Id(*ty)); + let result = self.locals.tmp("unionres"); + uwriteln!(self.src, "{} {};", ty, result); + uwriteln!(self.src, "{}.tag = {};", result, operands[0]); + uwriteln!(self.src, "switch ((int32_t) {}.tag) {{", result); + for (i, (_case, (block, block_results))) in + union.cases.iter().zip(blocks).enumerate() + { + uwriteln!(self.src, "case {i}: {{"); + self.src.push_str(&block); + + assert!(block_results.len() == 1); + let dst = format!("{result}.val.f{i}"); + self.store_op(&block_results[0], &dst); + self.src.push_str("break;\n}\n"); + } + self.src.push_str("}\n"); + results.push(result); + } + + Instruction::OptionLower { + results: result_types, + payload, + .. + } => { + let (mut some, some_results) = self.blocks.pop().unwrap(); + let (mut none, none_results) = self.blocks.pop().unwrap(); + let some_payload = self.payloads.pop().unwrap(); + let _none_payload = self.payloads.pop().unwrap(); + + for (i, ty) in result_types.iter().enumerate() { + let name = self.locals.tmp("option"); + results.push(name.clone()); + self.src.push_str(wasm_type(*ty)); + self.src.push_str(" "); + self.src.push_str(&name); + self.src.push_str(";\n"); + let some_result = &some_results[i]; + uwriteln!(some, "{name} = {some_result};"); + let none_result = &none_results[i]; + uwriteln!(none, "{name} = {none_result};"); + } + + let op0 = &operands[0]; + let ty = self.gen.type_string(iface, payload); + let bind_some = if self.gen.is_empty_type(iface, payload) { + String::new() + } else { + format!("const {ty} *{some_payload} = &({op0}).val;") + }; + uwrite!( + self.src, + " + if (({op0}).is_some) {{ + {bind_some} + {some} + }} else {{ + {none} + }} + " + ); + } + + Instruction::OptionLift { payload, ty, .. } => { + let (some, some_results) = self.blocks.pop().unwrap(); + let (none, none_results) = self.blocks.pop().unwrap(); + assert!(none_results.len() == 1); + assert!(some_results.len() == 1); + let some_result = &some_results[0]; + assert_eq!(none_results[0], "INVALID"); + + let ty = self.gen.type_string(iface, &Type::Id(*ty)); + let result = self.locals.tmp("option"); + uwriteln!(self.src, "{ty} {result};"); + let op0 = &operands[0]; + let set_some = if self.gen.is_empty_type(iface, payload) { + String::new() + } else { + format!("{result}.val = {some_result};") + }; + uwrite!( + self.src, + "switch ({op0}) {{ + case 0: {{ + {result}.is_some = false; + {none} + break; + }} + case 1: {{ + {result}.is_some = true; + {some} + {set_some} + break; + }} + }}" + ); + results.push(result); + } + + Instruction::ExpectedLower { + results: result_types, + expected, + .. + } => { + let (mut err, err_results) = self.blocks.pop().unwrap(); + let (mut ok, ok_results) = self.blocks.pop().unwrap(); + let err_payload = self.payloads.pop().unwrap(); + let ok_payload = self.payloads.pop().unwrap(); + + for (i, ty) in result_types.iter().enumerate() { + let name = self.locals.tmp("expected"); + results.push(name.clone()); + self.src.push_str(wasm_type(*ty)); + self.src.push_str(" "); + self.src.push_str(&name); + self.src.push_str(";\n"); + let ok_result = &ok_results[i]; + uwriteln!(ok, "{name} = {ok_result};"); + let err_result = &err_results[i]; + uwriteln!(err, "{name} = {err_result};"); + } + + let op0 = &operands[0]; + let ok_ty = self.gen.type_string(iface, &expected.ok); + let err_ty = self.gen.type_string(iface, &expected.err); + let bind_ok = if self.gen.is_empty_type(iface, &expected.ok) { + String::new() + } else { + format!("const {ok_ty} *{ok_payload} = &({op0}).val.ok;") + }; + let bind_err = if self.gen.is_empty_type(iface, &expected.err) { + String::new() + } else { + format!("const {err_ty} *{err_payload} = &({op0}).val.err;") + }; + uwrite!( + self.src, + " + if (({op0}).is_err) {{ + {bind_err} + {err} + }} else {{ + {bind_ok} + {ok} + }} + " + ); + } + + Instruction::ExpectedLift { expected, ty, .. } => { + let (err, err_results) = self.blocks.pop().unwrap(); + assert!(err_results.len() == 1); + let err_result = &err_results[0]; + let (ok, ok_results) = self.blocks.pop().unwrap(); + assert!(ok_results.len() == 1); + let ok_result = &ok_results[0]; + + let result = self.locals.tmp("expected"); + let set_ok = if self.gen.is_empty_type(iface, &expected.ok) { + String::new() + } else { + format!("{result}.val.ok = {ok_result};") + }; + let set_err = if self.gen.is_empty_type(iface, &expected.err) { + String::new() + } else { + format!("{result}.val.err = {err_result};") + }; + + let ty = self.gen.type_string(iface, &Type::Id(*ty)); + uwriteln!(self.src, "{ty} {result};"); + let op0 = &operands[0]; + uwrite!( + self.src, + "switch ({op0}) {{ + case 0: {{ + {result}.is_err = false; + {ok} + {set_ok} + break; + }} + case 1: {{ + {result}.is_err = true; + {err} + {set_err} + break; + }} + }}" + ); + results.push(result); + } + + Instruction::EnumLower { .. } => results.push(format!("(int32_t) {}", operands[0])), + Instruction::EnumLift { .. } => results.push(operands.pop().unwrap()), + + Instruction::ListCanonLower { .. } | Instruction::StringLower { .. } => { + results.push(format!("(int32_t) ({}).ptr", operands[0])); + results.push(format!("(int32_t) ({}).len", operands[0])); + } + Instruction::ListCanonLift { element, ty, .. } => { + let list_name = self.gen.type_string(iface, &Type::Id(*ty)); + let elem_name = self.gen.type_string(iface, element); + results.push(format!( + "({}) {{ ({}*)({}), (size_t)({}) }}", + list_name, elem_name, operands[0], operands[1] + )); + } + Instruction::StringLift { .. } => { + let list_name = self.gen.type_string(iface, &Type::String); + results.push(format!( + "({}) {{ (char*)({}), (size_t)({}) }}", + list_name, operands[0], operands[1] + )); + } + + Instruction::ListLower { .. } => { + let _body = self.blocks.pop().unwrap(); + results.push(format!("(int32_t) ({}).ptr", operands[0])); + results.push(format!("(int32_t) ({}).len", operands[0])); + } + + Instruction::ListLift { element, ty, .. } => { + let _body = self.blocks.pop().unwrap(); + let list_name = self.gen.type_string(iface, &Type::Id(*ty)); + let elem_name = self.gen.type_string(iface, element); + results.push(format!( + "({}) {{ ({}*)({}), (size_t)({}) }}", + list_name, elem_name, operands[0], operands[1] + )); + } + Instruction::IterElem { .. } => results.push("e".to_string()), + Instruction::IterBasePointer => results.push("base".to_string()), + + Instruction::CallWasm { sig, .. } => { + match sig.results.len() { + 0 => {} + 1 => { + self.src.push_str(wasm_type(sig.results[0])); + let ret = self.locals.tmp("ret"); + self.wasm_return = Some(ret.clone()); + uwrite!(self.src, " {} = ", ret); + results.push(ret); + } + _ => unimplemented!(), + } + self.src.push_str(self.func_to_call); + self.src.push_str("("); + for (i, op) in operands.iter().enumerate() { + if i > 0 { + self.src.push_str(", "); + } + self.src.push_str(op); + } + self.src.push_str(");\n"); + } + + Instruction::CallInterface { module: _, func } => { + let mut args = String::new(); + for (i, (op, (byref, _))) in operands.iter().zip(&self.sig.params).enumerate() { + if i > 0 { + args.push_str(", "); + } + if *byref { + let name = self.locals.tmp("arg"); + let ty = self.gen.type_string(iface, &func.params[i].1); + uwriteln!(self.src, "{} {} = {};", ty, name, op); + args.push_str("&"); + args.push_str(&name); + } else { + args.push_str(op); + } + } + match &self.sig.ret.scalar { + None => { + let mut retptrs = Vec::new(); + for ty in self.sig.ret.retptrs.iter() { + let name = self.locals.tmp("ret"); + let ty = self.gen.type_string(iface, ty); + uwriteln!(self.src, "{} {};", ty, name); + if args.len() > 0 { + args.push_str(", "); + } + args.push_str("&"); + args.push_str(&name); + retptrs.push(name); + } + uwriteln!(self.src, "{}({});", self.sig.name, args); + if self.sig.ret.splat_tuple { + let ty = self.gen.type_string(iface, &func.result); + results.push(format!("({}){{ {} }}", ty, retptrs.join(", "))); + } else if self.sig.retptrs.len() > 0 { + results.extend(retptrs); + } + } + Some(Scalar::Void) => { + uwriteln!(self.src, "{}({});", self.sig.name, args); + results.push("INVALID".to_string()); + } + Some(Scalar::Type(_)) => { + let ret = self.locals.tmp("ret"); + let ty = self.gen.type_string(iface, &func.result); + uwriteln!(self.src, "{} {} = {}({});", ty, ret, self.sig.name, args); + results.push(ret); + } + Some(Scalar::OptionBool(ty)) => { + let ret = self.locals.tmp("ret"); + let val = self.locals.tmp("val"); + if args.len() > 0 { + args.push_str(", "); + } + args.push_str("&"); + args.push_str(&val); + let payload_ty = self.gen.type_string(iface, ty); + uwriteln!(self.src, "{} {};", payload_ty, val); + uwriteln!(self.src, "bool {} = {}({});", ret, self.sig.name, args); + let option_ty = self.gen.type_string(iface, &func.result); + let option_ret = self.locals.tmp("ret"); + uwrite!( + self.src, + " + {ty} {ret}; + {ret}.is_some = {tag}; + {ret}.val = {val}; + ", + ty = option_ty, + ret = option_ret, + tag = ret, + val = val, + ); + results.push(option_ret); + } + Some(Scalar::ExpectedEnum { err, max_err }) => { + let ret = self.locals.tmp("ret"); + let mut ok_names = Vec::new(); + for ty in self.sig.ret.retptrs.iter() { + let val = self.locals.tmp("ok"); + if args.len() > 0 { + args.push_str(", "); + } + args.push_str("&"); + args.push_str(&val); + let ty = self.gen.type_string(iface, ty); + uwriteln!(self.src, "{} {};", ty, val); + ok_names.push(val); + } + let err_ty = self.gen.type_string(iface, &Type::Id(*err)); + uwriteln!( + self.src, + "{} {} = {}({});", + err_ty, + ret, + self.sig.name, + args, + ); + let expected_ty = self.gen.type_string(iface, &func.result); + let expected_ret = self.locals.tmp("ret"); + uwrite!( + self.src, + " + {ty} {ret}; + if ({tag} <= {max}) {{ + {ret}.is_err = true; + {ret}.val.err = {tag}; + }} else {{ + {ret}.is_err = false; + {set_ok} + }} + ", + ty = expected_ty, + ret = expected_ret, + tag = ret, + max = max_err, + set_ok = if self.sig.ret.retptrs.len() == 0 { + String::new() + } else if self.sig.ret.splat_tuple { + let mut s = String::new(); + for (i, name) in ok_names.iter().enumerate() { + uwriteln!(s, "{}.val.ok.f{} = {};", expected_ret, i, name,); + } + s + } else { + let name = ok_names.pop().unwrap(); + format!("{}.val.ok = {};", expected_ret, name) + }, + ); + results.push(expected_ret); + } + } + } + Instruction::Return { .. } if self.gen.in_import => match self.sig.ret.scalar { + None => self.store_in_retptrs(operands), + Some(Scalar::Void) => { + assert_eq!(operands, &["INVALID"]); + } + Some(Scalar::Type(_)) => { + assert_eq!(operands.len(), 1); + self.src.push_str("return "); + self.src.push_str(&operands[0]); + self.src.push_str(";\n"); + } + Some(Scalar::OptionBool(_)) => { + assert_eq!(operands.len(), 1); + let variant = &operands[0]; + self.store_in_retptrs(&[format!("{}.val", variant)]); + self.src.push_str("return "); + self.src.push_str(&variant); + self.src.push_str(".is_some;\n"); + } + Some(Scalar::ExpectedEnum { .. }) => { + assert_eq!(operands.len(), 1); + let variant = &operands[0]; + if self.sig.retptrs.len() > 0 { + self.store_in_retptrs(&[format!("{}.val.ok", variant)]); + } + uwriteln!(self.src, "return {}.is_err ? {0}.val.err : -1;", variant); + } + }, + Instruction::Return { amt, .. } => { + assert!(*amt <= 1); + if *amt == 1 { + uwriteln!(self.src, "return {};", operands[0]); + } + } + + Instruction::I32Load { offset } => self.load("int32_t", *offset, operands, results), + Instruction::I64Load { offset } => self.load("int64_t", *offset, operands, results), + Instruction::F32Load { offset } => self.load("float", *offset, operands, results), + Instruction::F64Load { offset } => self.load("double", *offset, operands, results), + Instruction::I32Store { offset } => self.store("int32_t", *offset, operands), + Instruction::I64Store { offset } => self.store("int64_t", *offset, operands), + Instruction::F32Store { offset } => self.store("float", *offset, operands), + Instruction::F64Store { offset } => self.store("double", *offset, operands), + Instruction::I32Store8 { offset } => self.store("int8_t", *offset, operands), + Instruction::I32Store16 { offset } => self.store("int16_t", *offset, operands), + + Instruction::I32Load8U { offset } => { + self.load_ext("uint8_t", *offset, operands, results) + } + Instruction::I32Load8S { offset } => { + self.load_ext("int8_t", *offset, operands, results) + } + Instruction::I32Load16U { offset } => { + self.load_ext("uint16_t", *offset, operands, results) + } + Instruction::I32Load16S { offset } => { + self.load_ext("int16_t", *offset, operands, results) + } + + Instruction::Free { .. } => { + uwriteln!(self.src, "free((void*) ({}));", operands[0]); + } + + i => unimplemented!("{:?}", i), + } + } +} + +#[derive(Default)] +struct Source { + h: wit_bindgen_gen_core::Source, + c: wit_bindgen_gen_core::Source, +} + +impl Source { + fn c(&mut self, s: &str) { + self.c.push_str(s); + } + fn h(&mut self, s: &str) { + self.h.push_str(s); + } +} + +fn wasm_type(ty: WasmType) -> &'static str { + match ty { + WasmType::I32 => "int32_t", + WasmType::I64 => "int64_t", + WasmType::F32 => "float", + WasmType::F64 => "double", + } +} + +fn int_repr(ty: Int) -> &'static str { + match ty { + Int::U8 => "uint8_t", + Int::U16 => "uint16_t", + Int::U32 => "uint32_t", + Int::U64 => "uint64_t", + } +} + +fn flags_repr(f: &Flags) -> Int { + match f.repr() { + FlagsRepr::U8 => Int::U8, + FlagsRepr::U16 => Int::U16, + FlagsRepr::U32(1) => Int::U32, + FlagsRepr::U32(2) => Int::U64, + repr => panic!("unimplemented flags {:?}", repr), + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/tests/codegen.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/tests/codegen.rs new file mode 100644 index 0000000..3b3d893 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-c/tests/codegen.rs @@ -0,0 +1,58 @@ +use std::env; +use std::path::{Path, PathBuf}; +use std::process::Command; + +mod imports { + test_helpers::codegen_c_import!( + // ... + "*.wit" + + // TODO: implement async support + "!async-functions.wit" + ); +} + +mod exports { + test_helpers::codegen_c_export!( + "*.wit" + + // TODO: implement async support + "!async-functions.wit" + + // TODO: these use push/pull buffer in exports which isn't implemented + // yet + "!wasi-next.wit" + "!host.wit" + ); +} + +fn verify(dir: &str, name: &str) { + let dir = Path::new(dir); + let path = PathBuf::from(env::var_os("WASI_SDK_PATH").unwrap()); + let mut cmd = Command::new(path.join("bin/clang")); + cmd.arg("--sysroot").arg(path.join("share/wasi-sysroot")); + cmd.arg(dir.join(format!("{}.c", name))); + cmd.arg("-I").arg(dir); + cmd.arg("-Wall") + .arg("-Wextra") + .arg("-Werror") + .arg("-Wno-unused-parameter"); + cmd.arg("-c"); + cmd.arg("-o").arg(dir.join("obj.o")); + + println!("{:?}", cmd); + let output = match cmd.output() { + Ok(output) => output, + Err(e) => panic!("failed to spawn compiler: {}", e), + }; + + if output.status.success() { + return; + } + println!("status: {}", output.status); + println!("stdout: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stderr)); + panic!("failed to compile"); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/Cargo.toml new file mode 100644 index 0000000..b3bc9c9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/Cargo.toml @@ -0,0 +1,12 @@ +[package] +name = "wit-bindgen-gen-core" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +doctest = false + +[dependencies] +wit-parser = { path = '../parser' } +anyhow = "1" diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/src/lib.rs new file mode 100644 index 0000000..81f4640 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/src/lib.rs @@ -0,0 +1,505 @@ +use anyhow::Result; +use std::collections::{btree_map::Entry, BTreeMap, HashMap}; +use std::fmt::{self, Write}; +use std::ops::Deref; +use std::path::Path; +use wit_parser::*; + +pub use wit_parser; +mod ns; + +pub use ns::Ns; + +/// This is the direction from the user's perspective. Are we importing +/// functions to call, or defining functions and exporting them to be called? +/// +/// This is only used outside of `Generator` implementations. Inside of +/// `Generator` implementations, the `Direction` is translated to an +/// `AbiVariant` instead. The ABI variant is usually the same as the +/// `Direction`, but it's different in the case of the Wasmtime host bindings: +/// +/// In a wasm-calling-wasm use case, one wasm module would use the `Import` +/// ABI, the other would use the `Export` ABI, and there would be an adapter +/// layer between the two that translates from one ABI to the other. +/// +/// But with wasm-calling-host, we don't go through a separate adapter layer; +/// the binding code we generate on the host side just does everything itself. +/// So when the host is conceptually "exporting" a function to wasm, it uses +/// the `Import` ABI so that wasm can also use the `Import` ABI and import it +/// directly from the host. +/// +/// These are all implementation details; from the user perspective, and +/// from the perspective of everything outside of `Generator` implementations, +/// `export` means I'm exporting functions to be called, and `import` means I'm +/// importing functions that I'm going to call, in both wasm modules and host +/// code. The enum here represents this user perspective. +#[derive(Copy, Clone, Eq, PartialEq)] +pub enum Direction { + Import, + Export, +} + +pub trait Generator { + fn preprocess_all(&mut self, imports: &[Interface], exports: &[Interface]) { + drop((imports, exports)); + } + + fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + drop((iface, dir)); + } + + fn type_record( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ); + fn type_flags(&mut self, iface: &Interface, id: TypeId, name: &str, flags: &Flags, docs: &Docs); + fn type_tuple(&mut self, iface: &Interface, id: TypeId, name: &str, flags: &Tuple, docs: &Docs); + fn type_variant( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + variant: &Variant, + docs: &Docs, + ); + fn type_option( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + payload: &Type, + docs: &Docs, + ); + fn type_expected( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + expected: &Expected, + docs: &Docs, + ); + fn type_union(&mut self, iface: &Interface, id: TypeId, name: &str, union: &Union, docs: &Docs); + fn type_enum(&mut self, iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs); + fn type_resource(&mut self, iface: &Interface, ty: ResourceId); + fn type_alias(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs); + fn type_list(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs); + fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs); + + fn preprocess_functions(&mut self, iface: &Interface, dir: Direction) { + drop((iface, dir)); + } + fn import(&mut self, iface: &Interface, func: &Function); + fn export(&mut self, iface: &Interface, func: &Function); + fn finish_functions(&mut self, iface: &Interface, dir: Direction) { + drop((iface, dir)); + } + + fn finish_one(&mut self, iface: &Interface, files: &mut Files); + + fn finish_all(&mut self, files: &mut Files) { + drop(files); + } + + fn generate_one(&mut self, iface: &Interface, dir: Direction, files: &mut Files) { + self.preprocess_one(iface, dir); + + for (id, ty) in iface.types.iter() { + // assert!(ty.foreign_module.is_none()); // TODO + let name = match &ty.name { + Some(name) => name, + None => continue, + }; + match &ty.kind { + TypeDefKind::Record(record) => self.type_record(iface, id, name, record, &ty.docs), + TypeDefKind::Flags(flags) => self.type_flags(iface, id, name, flags, &ty.docs), + TypeDefKind::Tuple(tuple) => self.type_tuple(iface, id, name, tuple, &ty.docs), + TypeDefKind::Enum(enum_) => self.type_enum(iface, id, name, enum_, &ty.docs), + TypeDefKind::Variant(variant) => { + self.type_variant(iface, id, name, variant, &ty.docs) + } + TypeDefKind::Option(t) => self.type_option(iface, id, name, t, &ty.docs), + TypeDefKind::Expected(e) => self.type_expected(iface, id, name, e, &ty.docs), + TypeDefKind::Union(u) => self.type_union(iface, id, name, u, &ty.docs), + TypeDefKind::List(t) => self.type_list(iface, id, name, t, &ty.docs), + TypeDefKind::Type(t) => self.type_alias(iface, id, name, t, &ty.docs), + TypeDefKind::Future(_) => todo!("generate for future"), + TypeDefKind::Stream(_) => todo!("generate for stream"), + } + } + + for (id, _resource) in iface.resources.iter() { + self.type_resource(iface, id); + } + + self.preprocess_functions(iface, dir); + + for f in iface.functions.iter() { + match dir { + Direction::Import => self.import(iface, &f), + Direction::Export => self.export(iface, &f), + } + } + + self.finish_functions(iface, dir); + + self.finish_one(iface, files) + } + + fn generate_all(&mut self, imports: &[Interface], exports: &[Interface], files: &mut Files) { + self.preprocess_all(imports, exports); + + for imp in imports { + self.generate_one(imp, Direction::Import, files); + } + + for exp in exports { + self.generate_one(exp, Direction::Export, files); + } + + self.finish_all(files); + } +} + +#[derive(Default)] +pub struct Types { + type_info: HashMap, +} + +#[derive(Default, Clone, Copy)] +pub struct TypeInfo { + /// Whether or not this type is ever used (transitively) within the + /// parameter of a function. + pub param: bool, + + /// Whether or not this type is ever used (transitively) within the + /// result of a function. + pub result: bool, + + /// Whether or not this type (transitively) has a list. + pub has_list: bool, + + /// Whether or not this type (transitively) has a handle. + pub has_handle: bool, +} + +impl std::ops::BitOrAssign for TypeInfo { + fn bitor_assign(&mut self, rhs: Self) { + self.param |= rhs.param; + self.result |= rhs.result; + self.has_list |= rhs.has_list; + self.has_handle |= rhs.has_handle; + } +} + +impl Types { + pub fn analyze(&mut self, iface: &Interface) { + for (t, _) in iface.types.iter() { + self.type_id_info(iface, t); + } + for f in iface.functions.iter() { + for (_, ty) in f.params.iter() { + self.set_param_result_ty(iface, ty, true, false); + } + self.set_param_result_ty(iface, &f.result, false, true); + } + } + + pub fn get(&self, id: TypeId) -> TypeInfo { + self.type_info[&id] + } + + pub fn type_id_info(&mut self, iface: &Interface, ty: TypeId) -> TypeInfo { + if let Some(info) = self.type_info.get(&ty) { + return *info; + } + let mut info = TypeInfo::default(); + match &iface.types[ty].kind { + TypeDefKind::Record(r) => { + for field in r.fields.iter() { + info |= self.type_info(iface, &field.ty); + } + } + TypeDefKind::Tuple(t) => { + for ty in t.types.iter() { + info |= self.type_info(iface, ty); + } + } + TypeDefKind::Flags(_) => {} + TypeDefKind::Enum(_) => {} + TypeDefKind::Variant(v) => { + for case in v.cases.iter() { + info |= self.type_info(iface, &case.ty); + } + } + TypeDefKind::List(ty) => { + info = self.type_info(iface, ty); + info.has_list = true; + } + TypeDefKind::Type(ty) => { + info = self.type_info(iface, ty); + } + TypeDefKind::Option(ty) => { + info = self.type_info(iface, ty); + } + TypeDefKind::Expected(e) => { + info = self.type_info(iface, &e.ok); + info |= self.type_info(iface, &e.err); + } + TypeDefKind::Union(u) => { + for case in u.cases.iter() { + info |= self.type_info(iface, &case.ty); + } + } + TypeDefKind::Future(_) => todo!("type_id_info for future"), + TypeDefKind::Stream(_) => todo!("type_id_info for stream"), + } + self.type_info.insert(ty, info); + return info; + } + + pub fn type_info(&mut self, iface: &Interface, ty: &Type) -> TypeInfo { + let mut info = TypeInfo::default(); + match ty { + Type::Handle(_) => info.has_handle = true, + Type::String => info.has_list = true, + Type::Id(id) => return self.type_id_info(iface, *id), + _ => {} + } + info + } + + fn set_param_result_id(&mut self, iface: &Interface, ty: TypeId, param: bool, result: bool) { + match &iface.types[ty].kind { + TypeDefKind::Record(r) => { + for field in r.fields.iter() { + self.set_param_result_ty(iface, &field.ty, param, result) + } + } + TypeDefKind::Tuple(t) => { + for ty in t.types.iter() { + self.set_param_result_ty(iface, ty, param, result) + } + } + TypeDefKind::Flags(_) => {} + TypeDefKind::Enum(_) => {} + TypeDefKind::Variant(v) => { + for case in v.cases.iter() { + self.set_param_result_ty(iface, &case.ty, param, result) + } + } + TypeDefKind::List(ty) | TypeDefKind::Type(ty) | TypeDefKind::Option(ty) => { + self.set_param_result_ty(iface, ty, param, result) + } + TypeDefKind::Expected(e) => { + self.set_param_result_ty(iface, &e.ok, param, result); + self.set_param_result_ty(iface, &e.err, param, result); + } + TypeDefKind::Union(u) => { + for case in u.cases.iter() { + self.set_param_result_ty(iface, &case.ty, param, result) + } + } + TypeDefKind::Future(_) => todo!("set_param_result_id for future"), + TypeDefKind::Stream(_) => todo!("set_param_result_id for stream"), + } + } + + fn set_param_result_ty(&mut self, iface: &Interface, ty: &Type, param: bool, result: bool) { + match ty { + Type::Id(id) => { + self.type_id_info(iface, *id); + let info = self.type_info.get_mut(id).unwrap(); + if (param && !info.param) || (result && !info.result) { + info.param = info.param || param; + info.result = info.result || result; + self.set_param_result_id(iface, *id, param, result); + } + } + _ => {} + } + } +} + +#[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())) + } +} + +pub fn load(path: impl AsRef) -> Result { + Interface::parse_file(path) +} + +#[derive(Default)] +pub struct Source { + s: String, + indent: usize, +} + +impl Source { + pub fn push_str(&mut self, src: &str) { + let lines = src.lines().collect::>(); + for (i, line) in lines.iter().enumerate() { + let trimmed = line.trim(); + if trimmed.starts_with("}") && self.s.ends_with(" ") { + self.s.pop(); + self.s.pop(); + } + self.s.push_str(if lines.len() == 1 { + line + } else { + line.trim_start() + }); + if trimmed.ends_with('{') { + self.indent += 1; + } + if trimmed.starts_with('}') { + self.indent -= 1; + } + if i != lines.len() - 1 || src.ends_with("\n") { + self.newline(); + } + } + } + + pub fn indent(&mut self, amt: usize) { + self.indent += amt; + } + + pub fn deindent(&mut self, amt: usize) { + self.indent -= amt; + } + + fn newline(&mut self) { + self.s.push_str("\n"); + for _ in 0..self.indent { + self.s.push_str(" "); + } + } + + pub fn as_mut_string(&mut self) -> &mut String { + &mut self.s + } +} + +impl Write for Source { + fn write_str(&mut self, s: &str) -> fmt::Result { + self.push_str(s); + Ok(()) + } +} + +impl Deref for Source { + type Target = str; + fn deref(&self) -> &str { + &self.s + } +} + +impl From for String { + fn from(s: Source) -> String { + s.s + } +} + +/// 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() + }; +} + +#[cfg(test)] +mod tests { + use super::{Generator, Source}; + + #[test] + fn simple_append() { + let mut s = Source::default(); + s.push_str("x"); + assert_eq!(s.s, "x"); + s.push_str("y"); + assert_eq!(s.s, "xy"); + s.push_str("z "); + assert_eq!(s.s, "xyz "); + s.push_str(" a "); + assert_eq!(s.s, "xyz a "); + s.push_str("\na"); + assert_eq!(s.s, "xyz a \na"); + } + + #[test] + fn newline_remap() { + let mut s = Source::default(); + s.push_str("function() {\n"); + s.push_str("y\n"); + s.push_str("}\n"); + assert_eq!(s.s, "function() {\n y\n}\n"); + } + + #[test] + fn if_else() { + let mut s = Source::default(); + s.push_str("if() {\n"); + s.push_str("y\n"); + s.push_str("} else if () {\n"); + s.push_str("z\n"); + s.push_str("}\n"); + assert_eq!(s.s, "if() {\n y\n} else if () {\n z\n}\n"); + } + + #[test] + fn trim_ws() { + let mut s = Source::default(); + s.push_str( + "function() { + x + }", + ); + assert_eq!(s.s, "function() {\n x\n}"); + } + + #[test] + fn generator_is_object_safe() { + fn _assert(_: &dyn Generator) {} + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/src/ns.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/src/ns.rs new file mode 100644 index 0000000..4fb0551 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-core/src/ns.rs @@ -0,0 +1,27 @@ +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/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/.eslintrc.js b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/.eslintrc.js new file mode 100644 index 0000000..b9b7fa1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/.eslintrc.js @@ -0,0 +1,18 @@ +module.exports = { + "env": { + "browser": true, + "es2021": true, + "node": true + }, + "extends": "eslint:recommended", + "parserOptions": { + "ecmaVersion": 12, + "sourceType": "module" + }, + "rules": { + // allow this since we generate `const {} = e;` for empty structs + "no-empty-pattern": 0, + // TODO: we generate some unused functions by accident, let's fix that later + "no-unused-vars": 0, + } +}; diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/Cargo.toml new file mode 100644 index 0000000..30f38e0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "wit-bindgen-gen-js" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +doctest = false +test = false + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' } +heck = "0.3" +structopt = { version = "0.3", default-features = false, optional = true } + +[dev-dependencies] +test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-js'] } diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/build.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/build.rs new file mode 100644 index 0000000..d7c7fc2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + // this build script is currently only here so OUT_DIR is set for testing. +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/package.json b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/package.json new file mode 100644 index 0000000..64894f2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/package.json @@ -0,0 +1,9 @@ +{ + "devDependencies": { + "@types/node": "^15.12.2", + "@typescript-eslint/eslint-plugin": "^4.27.0", + "@typescript-eslint/parser": "^4.27.0", + "eslint": "^7.28.0", + "typescript": "^4.3.2" + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/src/lib.rs new file mode 100644 index 0000000..5489cac --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/src/lib.rs @@ -0,0 +1,2649 @@ +use heck::*; +use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::fmt::Write; +use std::mem; +use wit_bindgen_gen_core::wit_parser::abi::{ + AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType, +}; +use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator}; + +#[derive(Default)] +pub struct Js { + src: Source, + in_import: bool, + opts: Opts, + guest_imports: HashMap, + guest_exports: HashMap, + sizes: SizeAlign, + intrinsics: BTreeMap, + all_intrinsics: BTreeSet, + needs_get_export: bool, + imported_resources: BTreeSet, + exported_resources: BTreeSet, + needs_ty_option: bool, + needs_ty_result: bool, +} + +#[derive(Default)] +struct Imports { + freestanding_funcs: Vec<(String, Source)>, + resource_funcs: BTreeMap>, +} + +#[derive(Default)] +struct Exports { + freestanding_funcs: Vec, + resource_funcs: BTreeMap>, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + #[cfg_attr(feature = "structopt", structopt(long = "no-typescript"))] + pub no_typescript: bool, +} + +impl Opts { + pub fn build(self) -> Js { + let mut r = Js::new(); + r.opts = self; + r + } +} + +#[derive(Copy, Clone, Ord, PartialOrd, Eq, PartialEq)] +enum Intrinsic { + ClampGuest, + ClampHost, + ClampHost64, + DataView, + ValidateGuestChar, + ValidateHostChar, + ValidateFlags, + ValidateFlags64, + /// Implementation of https://tc39.es/ecma262/#sec-tostring. + ToString, + I32ToF32, + F32ToI32, + I64ToF64, + F64ToI64, + Utf8Decoder, + Utf8Encode, + Utf8EncodedLen, + Slab, + Promises, + WithCurrentPromise, + ThrowInvalidBool, +} + +impl Intrinsic { + fn name(&self) -> &'static str { + match self { + Intrinsic::ClampGuest => "clamp_guest", + Intrinsic::ClampHost => "clamp_host", + Intrinsic::ClampHost64 => "clamp_host64", + Intrinsic::DataView => "data_view", + Intrinsic::ValidateGuestChar => "validate_guest_char", + Intrinsic::ValidateHostChar => "validate_host_char", + Intrinsic::ValidateFlags => "validate_flags", + Intrinsic::ValidateFlags64 => "validate_flags64", + Intrinsic::ToString => "to_string", + Intrinsic::F32ToI32 => "f32ToI32", + Intrinsic::I32ToF32 => "i32ToF32", + Intrinsic::F64ToI64 => "f64ToI64", + Intrinsic::I64ToF64 => "i64ToF64", + Intrinsic::Utf8Decoder => "UTF8_DECODER", + Intrinsic::Utf8Encode => "utf8_encode", + Intrinsic::Utf8EncodedLen => "UTF8_ENCODED_LEN", + Intrinsic::Slab => "Slab", + Intrinsic::Promises => "PROMISES", + Intrinsic::WithCurrentPromise => "with_current_promise", + Intrinsic::ThrowInvalidBool => "throw_invalid_bool", + } + } +} + +impl Js { + pub fn new() -> Js { + Js::default() + } + + fn abi_variant(dir: Direction) -> AbiVariant { + // This generator uses a reversed mapping! In the JS host-side + // bindings, we don't use any extra adapter layer between guest wasm + // modules and the host. When the guest imports functions using the + // `GuestImport` ABI, the host directly implements the `GuestImport` + // ABI, even though the host is *exporting* functions. Similarly, when + // the guest exports functions using the `GuestExport` ABI, the host + // directly imports them with the `GuestExport` ABI, even though the + // host is *importing* functions. + match dir { + Direction::Import => AbiVariant::GuestExport, + Direction::Export => AbiVariant::GuestImport, + } + } + + fn array_ty(&self, iface: &Interface, ty: &Type) -> Option<&'static str> { + match ty { + Type::Unit | Type::Bool => None, + Type::U8 => Some("Uint8Array"), + Type::S8 => Some("Int8Array"), + Type::U16 => Some("Uint16Array"), + Type::S16 => Some("Int16Array"), + Type::U32 => Some("Uint32Array"), + Type::S32 => Some("Int32Array"), + Type::U64 => Some("BigUint64Array"), + Type::S64 => Some("BigInt64Array"), + Type::Float32 => Some("Float32Array"), + Type::Float64 => Some("Float64Array"), + Type::Char => None, + Type::Handle(_) => None, + Type::String => None, + Type::Id(id) => match &iface.types[*id].kind { + TypeDefKind::Type(t) => self.array_ty(iface, t), + _ => None, + }, + } + } + + fn print_ty(&mut self, iface: &Interface, ty: &Type) { + match ty { + Type::Unit => self.src.ts("void"), + Type::Bool => self.src.ts("boolean"), + Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::Float32 + | Type::Float64 => self.src.ts("number"), + Type::U64 | Type::S64 => self.src.ts("bigint"), + Type::Char => self.src.ts("string"), + Type::Handle(id) => self.src.ts(&iface.resources[*id].name.to_camel_case()), + Type::String => self.src.ts("string"), + Type::Id(id) => { + let ty = &iface.types[*id]; + if let Some(name) = &ty.name { + return self.src.ts(&name.to_camel_case()); + } + match &ty.kind { + TypeDefKind::Type(t) => self.print_ty(iface, t), + TypeDefKind::Tuple(t) => self.print_tuple(iface, t), + TypeDefKind::Record(_) => panic!("anonymous record"), + TypeDefKind::Flags(_) => panic!("anonymous flags"), + TypeDefKind::Enum(_) => panic!("anonymous enum"), + TypeDefKind::Union(_) => panic!("anonymous union"), + TypeDefKind::Option(t) => { + if self.maybe_null(iface, t) { + self.needs_ty_option = true; + self.src.ts("Option<"); + self.print_ty(iface, t); + self.src.ts(">"); + } else { + self.print_ty(iface, t); + self.src.ts(" | null"); + } + } + TypeDefKind::Expected(e) => { + self.needs_ty_result = true; + self.src.ts("Result<"); + self.print_ty(iface, &e.ok); + self.src.ts(", "); + self.print_ty(iface, &e.err); + self.src.ts(">"); + } + TypeDefKind::Variant(_) => panic!("anonymous variant"), + TypeDefKind::List(v) => self.print_list(iface, v), + TypeDefKind::Future(_) => todo!("anonymous future"), + TypeDefKind::Stream(_) => todo!("anonymous stream"), + } + } + } + } + + fn print_list(&mut self, iface: &Interface, ty: &Type) { + match self.array_ty(iface, ty) { + Some(ty) => self.src.ts(ty), + None => { + self.print_ty(iface, ty); + self.src.ts("[]"); + } + } + } + + fn print_tuple(&mut self, iface: &Interface, tuple: &Tuple) { + self.src.ts("["); + for (i, ty) in tuple.types.iter().enumerate() { + if i > 0 { + self.src.ts(", "); + } + self.print_ty(iface, ty); + } + self.src.ts("]"); + } + + fn docs_raw(&mut self, docs: &str) { + self.src.ts("/**\n"); + for line in docs.lines() { + self.src.ts(&format!(" * {}\n", line)); + } + self.src.ts(" */\n"); + } + + fn docs(&mut self, docs: &Docs) { + match &docs.contents { + Some(docs) => self.docs_raw(docs), + None => return, + } + } + + fn ts_func(&mut self, iface: &Interface, func: &Function) { + self.docs(&func.docs); + + let mut name_printed = false; + if let FunctionKind::Static { .. } = &func.kind { + // static methods in imports are still wired up to an imported host + // object, but static methods on exports are actually static + // methods on the resource object. + if self.in_import { + name_printed = true; + self.src.ts(&func.name.to_mixed_case()); + } else { + self.src.ts("static "); + } + } + if !name_printed { + self.src.ts(&func.item_name().to_mixed_case()); + } + self.src.ts("("); + + let param_start = match &func.kind { + FunctionKind::Freestanding => 0, + FunctionKind::Static { .. } if self.in_import => 0, + FunctionKind::Static { .. } => { + // the 0th argument for exported static methods will be the + // instantiated interface + self.src.ts(&iface.name.to_mixed_case()); + self.src.ts(": "); + self.src.ts(&iface.name.to_camel_case()); + if func.params.len() > 0 { + self.src.ts(", "); + } + 0 + } + // skip the first parameter on methods which is `this` + FunctionKind::Method { .. } => 1, + }; + + for (i, (name, ty)) in func.params[param_start..].iter().enumerate() { + if i > 0 { + self.src.ts(", "); + } + self.src.ts(to_js_ident(&name.to_mixed_case())); + self.src.ts(": "); + self.print_ty(iface, ty); + } + self.src.ts("): "); + if func.is_async { + self.src.ts("Promise<"); + } + self.print_ty(iface, &func.result); + if func.is_async { + self.src.ts(">"); + } + self.src.ts(";\n"); + } + + fn intrinsic(&mut self, i: Intrinsic) -> String { + if let Some(name) = self.intrinsics.get(&i) { + return name.clone(); + } + // TODO: should select a name that automatically doesn't conflict with + // anything else being generated. + self.intrinsics.insert(i, i.name().to_string()); + return i.name().to_string(); + } + + /// Returns whether `null` is a valid value of type `ty` + fn maybe_null(&self, iface: &Interface, ty: &Type) -> bool { + self.as_nullable(iface, ty).is_some() + } + + /// Tests whether `ty` can be represented with `null`, and if it can then + /// the "other type" is returned. If `Some` is returned that means that `ty` + /// is `null | `. If `None` is returned that means that `null` can't + /// be used to represent `ty`. + fn as_nullable<'a>(&self, iface: &'a Interface, ty: &'a Type) -> Option<&'a Type> { + let id = match ty { + Type::Id(id) => *id, + _ => return None, + }; + match &iface.types[id].kind { + // If `ty` points to an `option`, then `ty` can be represented + // with `null` if `t` itself can't be represented with null. For + // example `option>` can't be represented with `null` + // since that's ambiguous if it's `none` or `some(none)`. + // + // Note, oddly enough, that `option>>` can be + // represented as `null` since: + // + // * `null` => `none` + // * `{ tag: "none" }` => `some(none)` + // * `{ tag: "some", val: null }` => `some(some(none))` + // * `{ tag: "some", val: 1 }` => `some(some(some(1)))` + // + // It's doubtful anyone would actually rely on that though due to + // how confusing it is. + TypeDefKind::Option(t) => { + if !self.maybe_null(iface, t) { + Some(t) + } else { + None + } + } + TypeDefKind::Type(t) => self.as_nullable(iface, t), + _ => None, + } + } +} + +impl Generator for Js { + fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + let variant = Self::abi_variant(dir); + self.sizes.fill(iface); + self.in_import = variant == AbiVariant::GuestImport; + } + + fn type_record( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ) { + self.docs(docs); + self.src + .ts(&format!("export interface {} {{\n", name.to_camel_case())); + for field in record.fields.iter() { + self.docs(&field.docs); + let (option_str, ty) = self + .as_nullable(iface, &field.ty) + .map_or(("", &field.ty), |ty| ("?", ty)); + self.src + .ts(&format!("{}{}: ", field.name.to_mixed_case(), option_str)); + self.print_ty(iface, ty); + self.src.ts(",\n"); + } + self.src.ts("}\n"); + } + + fn type_tuple( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + tuple: &Tuple, + docs: &Docs, + ) { + self.docs(docs); + self.src + .ts(&format!("export type {} = ", name.to_camel_case())); + self.print_tuple(iface, tuple); + self.src.ts(";\n"); + } + + fn type_flags( + &mut self, + _iface: &Interface, + _id: TypeId, + name: &str, + flags: &Flags, + docs: &Docs, + ) { + self.docs(docs); + let repr = js_flags_repr(flags); + let ty = repr.ty(); + let suffix = repr.suffix(); + self.src + .ts(&format!("export type {} = {ty};\n", name.to_camel_case())); + let name = name.to_shouty_snake_case(); + for (i, flag) in flags.flags.iter().enumerate() { + let flag = flag.name.to_shouty_snake_case(); + self.src.js(&format!( + "export const {name}_{flag} = {}{suffix};\n", + 1u128 << i, + )); + self.src.ts(&format!( + "export const {name}_{flag} = {}{suffix};\n", + 1u128 << i, + )); + } + } + + fn type_variant( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + variant: &Variant, + docs: &Docs, + ) { + self.docs(docs); + self.src + .ts(&format!("export type {} = ", name.to_camel_case())); + for (i, case) in variant.cases.iter().enumerate() { + if i > 0 { + self.src.ts(" | "); + } + self.src + .ts(&format!("{}_{}", name, case.name).to_camel_case()); + } + self.src.ts(";\n"); + for case in variant.cases.iter() { + self.docs(&case.docs); + self.src.ts(&format!( + "export interface {} {{\n", + format!("{}_{}", name, case.name).to_camel_case() + )); + self.src.ts("tag: \""); + self.src.ts(&case.name); + self.src.ts("\",\n"); + if case.ty != Type::Unit { + self.src.ts("val: "); + self.print_ty(iface, &case.ty); + self.src.ts(",\n"); + } + self.src.ts("}\n"); + } + } + + fn type_union( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + union: &Union, + docs: &Docs, + ) { + self.docs(docs); + let name = name.to_camel_case(); + self.src.ts(&format!("export type {name} = ")); + for i in 0..union.cases.len() { + if i > 0 { + self.src.ts(" | "); + } + self.src.ts(&format!("{name}{i}")); + } + self.src.ts(";\n"); + for (i, case) in union.cases.iter().enumerate() { + self.docs(&case.docs); + self.src.ts(&format!("export interface {name}{i} {{\n")); + self.src.ts(&format!("tag: {i},\n")); + self.src.ts("val: "); + self.print_ty(iface, &case.ty); + self.src.ts(",\n"); + self.src.ts("}\n"); + } + } + + fn type_option( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + payload: &Type, + docs: &Docs, + ) { + self.docs(docs); + let name = name.to_camel_case(); + self.src.ts(&format!("export type {name} = ")); + if self.maybe_null(iface, payload) { + self.needs_ty_option = true; + self.src.ts("Option<"); + self.print_ty(iface, payload); + self.src.ts(">"); + } else { + self.print_ty(iface, payload); + self.src.ts(" | null"); + } + self.src.ts(";\n"); + } + + fn type_expected( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + expected: &Expected, + docs: &Docs, + ) { + self.docs(docs); + let name = name.to_camel_case(); + self.needs_ty_result = true; + self.src.ts(&format!("export type {name} = Result<")); + self.print_ty(iface, &expected.ok); + self.src.ts(", "); + self.print_ty(iface, &expected.err); + self.src.ts(">;\n"); + } + + fn type_enum( + &mut self, + _iface: &Interface, + _id: TypeId, + name: &str, + enum_: &Enum, + docs: &Docs, + ) { + // The complete documentation for this enum, including documentation for variants. + let mut complete_docs = String::new(); + + if let Some(docs) = &docs.contents { + complete_docs.push_str(docs); + // Add a gap before the `# Variants` section. + complete_docs.push('\n'); + } + + writeln!(complete_docs, "# Variants").unwrap(); + + for case in enum_.cases.iter() { + writeln!(complete_docs).unwrap(); + writeln!(complete_docs, "## `\"{}\"`", case.name).unwrap(); + + if let Some(docs) = &case.docs.contents { + writeln!(complete_docs).unwrap(); + complete_docs.push_str(docs); + } + } + + self.docs_raw(&complete_docs); + + self.src + .ts(&format!("export type {} = ", name.to_camel_case())); + for (i, case) in enum_.cases.iter().enumerate() { + if i != 0 { + self.src.ts(" | "); + } + self.src.ts(&format!("\"{}\"", case.name)); + } + self.src.ts(";\n"); + } + + fn type_resource(&mut self, _iface: &Interface, ty: ResourceId) { + if !self.in_import { + self.exported_resources.insert(ty); + } + } + + fn type_alias(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.docs(docs); + self.src + .ts(&format!("export type {} = ", name.to_camel_case())); + self.print_ty(iface, ty); + self.src.ts(";\n"); + } + + fn type_list(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.docs(docs); + self.src + .ts(&format!("export type {} = ", name.to_camel_case())); + self.print_list(iface, ty); + self.src.ts(";\n"); + } + + fn type_builtin(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + drop((iface, _id, name, ty, docs)); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "export" uses the "guest import" ABI variant on the inside of + // this `Generator` implementation. + fn export(&mut self, iface: &Interface, func: &Function) { + let prev = mem::take(&mut self.src); + + let sig = iface.wasm_signature(AbiVariant::GuestImport, func); + let params = (0..sig.params.len()) + .map(|i| format!("arg{}", i)) + .collect::>(); + self.src + .js(&format!("function({}) {{\n", params.join(", "))); + self.ts_func(iface, func); + + let mut f = FunctionBindgen::new(self, false, params); + iface.call( + AbiVariant::GuestImport, + LiftLower::LiftArgsLowerResults, + func, + &mut f, + ); + + let FunctionBindgen { + src, + needs_memory, + needs_realloc, + needs_free, + .. + } = f; + + if needs_memory { + self.needs_get_export = true; + // TODO: hardcoding "memory" + self.src.js("const memory = get_export(\"memory\");\n"); + } + + if let Some(name) = needs_realloc { + self.needs_get_export = true; + self.src + .js(&format!("const realloc = get_export(\"{}\");\n", name)); + } + + if let Some(name) = needs_free { + self.needs_get_export = true; + self.src + .js(&format!("const free = get_export(\"{}\");\n", name)); + } + self.src.js(&src.js); + + if func.is_async { + // Note that `catch_closure` here is defined by the `CallInterface` + // instruction. + self.src.js("}, catch_closure);\n"); // `.then` block + self.src.js("});\n"); // `with_current_promise` block. + } + self.src.js("}"); + + let src = mem::replace(&mut self.src, prev); + let imports = self + .guest_imports + .entry(iface.name.to_string()) + .or_insert(Imports::default()); + let dst = match &func.kind { + FunctionKind::Freestanding | FunctionKind::Static { .. } => { + &mut imports.freestanding_funcs + } + FunctionKind::Method { resource, .. } => imports + .resource_funcs + .entry(*resource) + .or_insert(Vec::new()), + }; + dst.push((func.name.to_string(), src)); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "import" uses the "export" ABI variant on the inside of + // this `Generator` implementation. + fn import(&mut self, iface: &Interface, func: &Function) { + let prev = mem::take(&mut self.src); + + let mut params = func + .params + .iter() + .enumerate() + .map(|(i, _)| format!("arg{}", i)) + .collect::>(); + let mut sig_start = 0; + let mut first_is_operand = true; + let src_object = match &func.kind { + FunctionKind::Freestanding => "this".to_string(), + FunctionKind::Static { .. } => { + self.src.js("static "); + params.insert(0, iface.name.to_mixed_case()); + first_is_operand = false; + iface.name.to_mixed_case() + } + FunctionKind::Method { .. } => { + params[0] = "this".to_string(); + sig_start = 1; + "this._obj".to_string() + } + }; + if func.is_async { + self.src.js("async "); + } + self.src.js(&format!( + "{}({}) {{\n", + func.item_name().to_mixed_case(), + params[sig_start..].join(", ") + )); + self.ts_func(iface, func); + + if !first_is_operand { + params.remove(0); + } + let mut f = FunctionBindgen::new(self, false, params); + f.src_object = src_object; + iface.call( + AbiVariant::GuestExport, + LiftLower::LowerArgsLiftResults, + func, + &mut f, + ); + + let FunctionBindgen { + src, + needs_memory, + needs_realloc, + needs_free, + src_object, + .. + } = f; + if needs_memory { + // TODO: hardcoding "memory" + self.src + .js(&format!("const memory = {}._exports.memory;\n", src_object)); + } + + if let Some(name) = needs_realloc { + self.src.js(&format!( + "const realloc = {}._exports[\"{}\"];\n", + src_object, name + )); + } + + if let Some(name) = needs_free { + self.src.js(&format!( + "const free = {}._exports[\"{}\"];\n", + src_object, name + )); + } + self.src.js(&src.js); + self.src.js("}\n"); + + let exports = self + .guest_exports + .entry(iface.name.to_string()) + .or_insert_with(Exports::default); + + let func_body = mem::replace(&mut self.src, prev); + match &func.kind { + FunctionKind::Freestanding => { + exports.freestanding_funcs.push(func_body); + } + FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => { + exports + .resource_funcs + .entry(*resource) + .or_insert(Vec::new()) + .push(func_body); + } + } + } + + fn finish_one(&mut self, iface: &Interface, files: &mut Files) { + for (module, funcs) in mem::take(&mut self.guest_imports) { + // TODO: `module.exports` vs `export function` + self.src.js(&format!( + "export function add{}ToImports(imports, obj{}) {{\n", + module.to_camel_case(), + if self.needs_get_export { + ", get_export" + } else { + "" + }, + )); + self.src.ts(&format!( + "export function add{}ToImports(imports: any, obj: {0}{}): void;\n", + module.to_camel_case(), + if self.needs_get_export { + ", get_export: (name: string) => WebAssembly.ExportValue" + } else { + "" + }, + )); + self.src.js(&format!( + "if (!(\"{0}\" in imports)) imports[\"{0}\"] = {{}};\n", + module, + )); + + self.src + .ts(&format!("export interface {} {{\n", module.to_camel_case())); + + for (name, src) in funcs + .freestanding_funcs + .iter() + .chain(funcs.resource_funcs.values().flat_map(|v| v)) + { + self.src.js(&format!( + "imports[\"{}\"][\"{}\"] = {};\n", + module, + name, + src.js.trim(), + )); + } + + for (_, src) in funcs.freestanding_funcs.iter() { + self.src.ts(&src.ts); + } + + if self.imported_resources.len() > 0 { + self.src + .js("if (!(\"canonical_abi\" in imports)) imports[\"canonical_abi\"] = {};\n"); + } + for resource in self.imported_resources.clone() { + let slab = self.intrinsic(Intrinsic::Slab); + self.src.js(&format!( + " + const resources{idx} = new {slab}(); + imports.canonical_abi[\"resource_drop_{name}\"] = (i) => {{ + const val = resources{idx}.remove(i); + if (obj.drop{camel}) + obj.drop{camel}(val); + }}; + ", + name = iface.resources[resource].name, + camel = iface.resources[resource].name.to_camel_case(), + idx = resource.index(), + slab = slab, + )); + self.src.ts(&format!( + "drop{}?: (val: {0}) => void;\n", + iface.resources[resource].name.to_camel_case() + )); + } + self.src.js("}"); + self.src.ts("}\n"); + + for (resource, _) in iface.resources.iter() { + self.src.ts(&format!( + "export interface {} {{\n", + iface.resources[resource].name.to_camel_case() + )); + if let Some(funcs) = funcs.resource_funcs.get(&resource) { + for (_, src) in funcs { + self.src.ts(&src.ts); + } + } + self.src.ts("}\n"); + } + } + let imports = mem::take(&mut self.src); + + for (module, exports) in mem::take(&mut self.guest_exports) { + let module = module.to_camel_case(); + self.src.ts(&format!("export class {} {{\n", module)); + self.src.js(&format!("export class {} {{\n", module)); + + self.src.ts(" + /** + * The WebAssembly instance that this class is operating with. + * This is only available after the `instantiate` method has + * been called. + */ + instance: WebAssembly.Instance; + "); + + self.src.ts(" + /** + * Constructs a new instance with internal state necessary to + * manage a wasm instance. + * + * Note that this does not actually instantiate the WebAssembly + * instance or module, you'll need to call the `instantiate` + * method below to \"activate\" this class. + */ + constructor(); + "); + if self.exported_resources.len() > 0 { + self.src.js("constructor() {\n"); + let slab = self.intrinsic(Intrinsic::Slab); + for r in self.exported_resources.iter() { + self.src.js(&format!( + "this._resource{}_slab = new {}();\n", + r.index(), + slab + )); + } + self.src.js("}\n"); + } + + self.src.ts(" + /** + * This is a low-level method which can be used to add any + * intrinsics necessary for this instance to operate to an + * import object. + * + * The `import` object given here is expected to be used later + * to actually instantiate the module this class corresponds to. + * If the `instantiate` method below actually does the + * instantiation then there's no need to call this method, but + * if you're instantiating manually elsewhere then this can be + * used to prepare the import object for external instantiation. + */ + addToImports(imports: any): void; + "); + self.src.js("addToImports(imports) {\n"); + let any_async = iface.functions.iter().any(|f| f.is_async); + if self.exported_resources.len() > 0 || any_async { + self.src + .js("if (!(\"canonical_abi\" in imports)) imports[\"canonical_abi\"] = {};\n"); + } + for r in self.exported_resources.iter() { + self.src.js(&format!( + " + imports.canonical_abi['resource_drop_{name}'] = i => {{ + this._resource{idx}_slab.remove(i).drop(); + }}; + imports.canonical_abi['resource_clone_{name}'] = i => {{ + const obj = this._resource{idx}_slab.get(i); + return this._resource{idx}_slab.insert(obj.clone()) + }}; + imports.canonical_abi['resource_get_{name}'] = i => {{ + return this._resource{idx}_slab.get(i)._wasm_val; + }}; + imports.canonical_abi['resource_new_{name}'] = i => {{ + const registry = this._registry{idx}; + return this._resource{idx}_slab.insert(new {class}(i, this)); + }}; + ", + name = iface.resources[*r].name, + idx = r.index(), + class = iface.resources[*r].name.to_camel_case(), + )); + } + if any_async { + let promises = self.intrinsic(Intrinsic::Promises); + self.src.js(&format!( + " + imports.canonical_abi['async_export_done'] = (ctx, ptr) => {{ + {}.remove(ctx)(ptr >>> 0) + }}; + ", + promises + )); + } + self.src.js("}\n"); + + self.src.ts(&format!( + " + /** + * Initializes this object with the provided WebAssembly + * module/instance. + * + * This is intended to be a flexible method of instantiating + * and completion of the initialization of this class. This + * method must be called before interacting with the + * WebAssembly object. + * + * The first argument to this method is where to get the + * wasm from. This can be a whole bunch of different types, + * for example: + * + * * A precompiled `WebAssembly.Module` + * * A typed array buffer containing the wasm bytecode. + * * A `Promise` of a `Response` which is used with + * `instantiateStreaming` + * * A `Response` itself used with `instantiateStreaming`. + * * An already instantiated `WebAssembly.Instance` + * + * If necessary the module is compiled, and if necessary the + * module is instantiated. Whether or not it's necessary + * depends on the type of argument provided to + * instantiation. + * + * If instantiation is performed then the `imports` object + * passed here is the list of imports used to instantiate + * the instance. This method may add its own intrinsics to + * this `imports` object too. + */ + instantiate( + module: WebAssembly.Module | BufferSource | Promise | Response | WebAssembly.Instance, + imports?: any, + ): Promise; + ", + )); + self.src.js(" + async instantiate(module, imports) { + imports = imports || {}; + this.addToImports(imports); + "); + + // With intrinsics prep'd we can now instantiate the module. JS has + // a ... variety of methods of instantiation, so we basically just + // try to be flexible here. + self.src.js(" + if (module instanceof WebAssembly.Instance) { + this.instance = module; + } else if (module instanceof WebAssembly.Module) { + this.instance = await WebAssembly.instantiate(module, imports); + } else if (module instanceof ArrayBuffer || module instanceof Uint8Array) { + const { instance } = await WebAssembly.instantiate(module, imports); + this.instance = instance; + } else { + const { instance } = await WebAssembly.instantiateStreaming(module, imports); + this.instance = instance; + } + this._exports = this.instance.exports; + "); + + // Exported resources all get a finalization registry, and we + // created them after instantiation so we can pass the raw wasm + // export as the destructor callback. + for r in self.exported_resources.iter() { + self.src.js(&format!( + "this._registry{} = new FinalizationRegistry(this._exports['canonical_abi_drop_{}']);\n", + r.index(), + iface.resources[*r].name, + )); + } + self.src.js("}\n"); + + for func in exports.freestanding_funcs.iter() { + self.src.js(&func.js); + self.src.ts(&func.ts); + } + self.src.ts("}\n"); + self.src.js("}\n"); + + for &ty in self.exported_resources.iter() { + self.src.js(&format!( + " + export class {} {{ + constructor(wasm_val, obj) {{ + this._wasm_val = wasm_val; + this._obj = obj; + this._refcnt = 1; + obj._registry{idx}.register(this, wasm_val, this); + }} + + clone() {{ + this._refcnt += 1; + return this; + }} + + drop() {{ + this._refcnt -= 1; + if (this._refcnt !== 0) + return; + this._obj._registry{idx}.unregister(this); + const dtor = this._obj._exports['canonical_abi_drop_{}']; + const wasm_val = this._wasm_val; + delete this._obj; + delete this._refcnt; + delete this._wasm_val; + dtor(wasm_val); + }} + ", + iface.resources[ty].name.to_camel_case(), + iface.resources[ty].name, + idx = ty.index(), + )); + self.src.ts(&format!( + " + export class {} {{ + // Creates a new strong reference count as a new + // object. This is only required if you're also + // calling `drop` below and want to manually manage + // the reference count from JS. + // + // If you don't call `drop`, you don't need to call + // this and can simply use the object from JS. + clone(): {0}; + + // Explicitly indicate that this JS object will no + // longer be used. If the internal reference count + // reaches zero then this will deterministically + // destroy the underlying wasm object. + // + // This is not required to be called from JS. Wasm + // destructors will be automatically called for you + // if this is not called using the JS + // `FinalizationRegistry`. + // + // Calling this method does not guarantee that the + // underlying wasm object is deallocated. Something + // else (including wasm) may be holding onto a + // strong reference count. + drop(): void; + ", + iface.resources[ty].name.to_camel_case(), + )); + + if let Some(funcs) = exports.resource_funcs.get(&ty) { + for func in funcs { + self.src.js(&func.js); + self.src.ts(&func.ts); + } + } + + self.src.ts("}\n"); + self.src.js("}\n"); + } + } + + let exports = mem::take(&mut self.src); + + if mem::take(&mut self.needs_ty_option) { + self.src + .ts("export type Option = { tag: \"none\" } | { tag: \"some\", val; T };\n"); + } + if mem::take(&mut self.needs_ty_result) { + self.src.ts( + "export type Result = { tag: \"ok\", val: T } | { tag: \"err\", val: E };\n", + ); + } + + if self.intrinsics.len() > 0 { + self.src.js("import { "); + for (i, (intrinsic, name)) in mem::take(&mut self.intrinsics).into_iter().enumerate() { + if i > 0 { + self.src.js(", "); + } + self.src.js(intrinsic.name()); + if intrinsic.name() != name { + self.src.js(" as "); + self.src.js(&name); + } + self.all_intrinsics.insert(intrinsic); + } + self.src.js(" } from './intrinsics.js';\n"); + } + + self.src.js(&imports.js); + self.src.ts(&imports.ts); + self.src.js(&exports.js); + self.src.ts(&exports.ts); + + let src = mem::take(&mut self.src); + let name = iface.name.to_kebab_case(); + files.push(&format!("{}.js", name), src.js.as_bytes()); + if !self.opts.no_typescript { + files.push(&format!("{}.d.ts", name), src.ts.as_bytes()); + } + } + + fn finish_all(&mut self, files: &mut Files) { + assert!(self.src.ts.is_empty()); + assert!(self.src.js.is_empty()); + self.print_intrinsics(); + assert!(self.src.ts.is_empty()); + files.push("intrinsics.js", self.src.js.as_bytes()); + } +} + +struct FunctionBindgen<'a> { + gen: &'a mut Js, + tmp: usize, + src: Source, + block_storage: Vec, + blocks: Vec<(String, Vec)>, + in_import: bool, + needs_memory: bool, + needs_realloc: Option, + needs_free: Option, + params: Vec, + src_object: String, +} + +impl FunctionBindgen<'_> { + fn new(gen: &mut Js, in_import: bool, params: Vec) -> FunctionBindgen<'_> { + FunctionBindgen { + gen, + tmp: 0, + src: Source::default(), + block_storage: Vec::new(), + blocks: Vec::new(), + in_import, + needs_memory: false, + needs_realloc: None, + needs_free: None, + params, + src_object: "this".to_string(), + } + } + + fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + ret + } + + fn clamp_guest(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) + where + T: std::fmt::Display, + { + let clamp = self.gen.intrinsic(Intrinsic::ClampGuest); + results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max)); + } + + fn clamp_host(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) + where + T: std::fmt::Display, + { + let clamp = self.gen.intrinsic(Intrinsic::ClampHost); + results.push(format!("{}({}, {}, {})", clamp, operands[0], min, max)); + } + + fn clamp_host64(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) + where + T: std::fmt::Display, + { + let clamp = self.gen.intrinsic(Intrinsic::ClampHost64); + results.push(format!("{}({}, {}n, {}n)", clamp, operands[0], min, max)); + } + + fn load(&mut self, method: &str, offset: i32, operands: &[String], results: &mut Vec) { + self.needs_memory = true; + let view = self.gen.intrinsic(Intrinsic::DataView); + results.push(format!( + "{}(memory).{}({} + {}, true)", + view, method, operands[0], offset, + )); + } + + fn store(&mut self, method: &str, offset: i32, operands: &[String]) { + self.needs_memory = true; + let view = self.gen.intrinsic(Intrinsic::DataView); + self.src.js(&format!( + "{}(memory).{}({} + {}, {}, true);\n", + view, method, operands[1], offset, operands[0] + )); + } + + fn bind_results(&mut self, amt: usize, results: &mut Vec) { + match amt { + 0 => {} + 1 => { + self.src.js("const ret = "); + results.push("ret".to_string()); + } + n => { + self.src.js("const ["); + for i in 0..n { + if i > 0 { + self.src.js(", "); + } + self.src.js(&format!("ret{}", i)); + results.push(format!("ret{}", i)); + } + self.src.js("] = "); + } + } + } +} + +impl Bindgen for FunctionBindgen<'_> { + type Operand = String; + + fn sizes(&self) -> &SizeAlign { + &self.gen.sizes + } + + fn push_block(&mut self) { + let prev = mem::take(&mut self.src.js); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().unwrap(); + let src = mem::replace(&mut self.src.js, to_restore); + self.blocks.push((src.into(), mem::take(operands))); + } + + fn return_pointer(&mut self, _iface: &Interface, _size: usize, _align: usize) -> String { + unimplemented!() + } + + fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { + self.gen.array_ty(iface, ty).is_some() + } + + fn emit( + &mut self, + iface: &Interface, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + match inst { + Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), + Instruction::I32Const { val } => results.push(val.to_string()), + Instruction::ConstZero { tys } => { + for t in tys.iter() { + match t { + WasmType::I64 => results.push("0n".to_string()), + WasmType::I32 | WasmType::F32 | WasmType::F64 => { + results.push("0".to_string()); + } + } + } + } + + // The representation of i32 in JS is a number, so 8/16-bit values + // get further clamped to ensure that the upper bits aren't set when + // we pass the value, ensuring that only the right number of bits + // are transferred. + Instruction::U8FromI32 => self.clamp_guest(results, operands, u8::MIN, u8::MAX), + Instruction::S8FromI32 => self.clamp_guest(results, operands, i8::MIN, i8::MAX), + Instruction::U16FromI32 => self.clamp_guest(results, operands, u16::MIN, u16::MAX), + Instruction::S16FromI32 => self.clamp_guest(results, operands, i16::MIN, i16::MAX), + // Use `>>>0` to ensure the bits of the number are treated as + // unsigned. + Instruction::U32FromI32 => { + results.push(format!("{} >>> 0", operands[0])); + } + // All bigints coming from wasm are treated as signed, so convert + // it to ensure it's treated as unsigned. + Instruction::U64FromI64 => results.push(format!("BigInt.asUintN(64, {})", operands[0])), + // Nothing to do signed->signed where the representations are the + // same. + Instruction::S32FromI32 | Instruction::S64FromI64 => { + results.push(operands.pop().unwrap()) + } + + // All values coming from the host and going to wasm need to have + // their ranges validated, since the host could give us any value. + Instruction::I32FromU8 => self.clamp_host(results, operands, u8::MIN, u8::MAX), + Instruction::I32FromS8 => self.clamp_host(results, operands, i8::MIN, i8::MAX), + Instruction::I32FromU16 => self.clamp_host(results, operands, u16::MIN, u16::MAX), + Instruction::I32FromS16 => self.clamp_host(results, operands, i16::MIN, i16::MAX), + Instruction::I32FromU32 => { + self.clamp_host(results, operands, u32::MIN, u32::MAX); + } + Instruction::I32FromS32 => self.clamp_host(results, operands, i32::MIN, i32::MAX), + Instruction::I64FromU64 => self.clamp_host64(results, operands, u64::MIN, u64::MAX), + Instruction::I64FromS64 => self.clamp_host64(results, operands, i64::MIN, i64::MAX), + + // The native representation in JS of f32 and f64 is just a number, + // so there's nothing to do here. Everything wasm gives us is + // representable in JS. + Instruction::Float32FromF32 | Instruction::Float64FromF64 => { + results.push(operands.pop().unwrap()) + } + + Instruction::F32FromFloat32 | Instruction::F64FromFloat64 => { + // Use a unary `+` to cast to a float. + results.push(format!("+{}", operands[0])); + } + + // Validate that i32 values coming from wasm are indeed valid code + // points. + Instruction::CharFromI32 => { + let validate = self.gen.intrinsic(Intrinsic::ValidateGuestChar); + results.push(format!("{}({})", validate, operands[0])); + } + + // Validate that strings are indeed 1 character long and valid + // unicode. + Instruction::I32FromChar => { + let validate = self.gen.intrinsic(Intrinsic::ValidateHostChar); + results.push(format!("{}({})", validate, operands[0])); + } + + Instruction::Bitcasts { casts } => { + for (cast, op) in casts.iter().zip(operands) { + match cast { + Bitcast::I32ToF32 => { + let cvt = self.gen.intrinsic(Intrinsic::I32ToF32); + results.push(format!("{}({})", cvt, op)); + } + Bitcast::F32ToI32 => { + let cvt = self.gen.intrinsic(Intrinsic::F32ToI32); + results.push(format!("{}({})", cvt, op)); + } + Bitcast::I64ToF64 => { + let cvt = self.gen.intrinsic(Intrinsic::I64ToF64); + results.push(format!("{}({})", cvt, op)); + } + Bitcast::F64ToI64 => { + let cvt = self.gen.intrinsic(Intrinsic::F64ToI64); + results.push(format!("{}({})", cvt, op)); + } + Bitcast::I32ToI64 => results.push(format!("BigInt({})", op)), + Bitcast::I64ToI32 => results.push(format!("Number({})", op)), + Bitcast::I64ToF32 => { + let cvt = self.gen.intrinsic(Intrinsic::I32ToF32); + results.push(format!("{}(Number({}))", cvt, op)); + } + Bitcast::F32ToI64 => { + let cvt = self.gen.intrinsic(Intrinsic::F32ToI32); + results.push(format!("BigInt({}({}))", cvt, op)); + } + Bitcast::None => results.push(op.clone()), + } + } + } + + Instruction::UnitLower => {} + Instruction::UnitLift => { + results.push("undefined".to_string()); + } + + Instruction::BoolFromI32 => { + let tmp = self.tmp(); + self.src + .js(&format!("const bool{} = {};\n", tmp, operands[0])); + let throw = self.gen.intrinsic(Intrinsic::ThrowInvalidBool); + results.push(format!( + "bool{tmp} == 0 ? false : (bool{tmp} == 1 ? true : {throw}())" + )); + } + Instruction::I32FromBool => { + results.push(format!("{} ? 1 : 0", operands[0])); + } + + // These instructions are used with handles when we're implementing + // imports. This means we interact with the `resources` slabs to + // translate the wasm-provided index into a JS value. + Instruction::I32FromOwnedHandle { ty } => { + self.gen.imported_resources.insert(*ty); + results.push(format!("resources{}.insert({})", ty.index(), operands[0])); + } + Instruction::HandleBorrowedFromI32 { ty } => { + self.gen.imported_resources.insert(*ty); + results.push(format!("resources{}.get({})", ty.index(), operands[0])); + } + + // These instructions are used for handles to objects owned in wasm. + // This means that they're interacting with a wrapper class defined + // in JS. + Instruction::I32FromBorrowedHandle { ty } => { + let tmp = self.tmp(); + self.src + .js(&format!("const obj{} = {};\n", tmp, operands[0])); + + // If this is the `this` argument then it's implicitly already valid + if operands[0] != "this" { + self.src.js(&format!( + "if (!(obj{} instanceof {})) ", + tmp, + iface.resources[*ty].name.to_camel_case() + )); + self.src.js(&format!( + "throw new TypeError('expected instance of {}');\n", + iface.resources[*ty].name.to_camel_case() + )); + } + results.push(format!( + "{}._resource{}_slab.insert(obj{}.clone())", + self.src_object, + ty.index(), + tmp, + )); + } + Instruction::HandleOwnedFromI32 { ty } => { + results.push(format!( + "{}._resource{}_slab.remove({})", + self.src_object, + ty.index(), + operands[0], + )); + } + + Instruction::RecordLower { record, .. } => { + // use destructuring field access to get each + // field individually. + let tmp = self.tmp(); + let mut expr = "const {".to_string(); + for (i, field) in record.fields.iter().enumerate() { + if i > 0 { + expr.push_str(", "); + } + let name = format!("v{}_{}", tmp, i); + expr.push_str(&field.name.to_mixed_case()); + expr.push_str(": "); + expr.push_str(&name); + results.push(name); + } + self.src.js(&format!("{} }} = {};\n", expr, operands[0])); + } + + Instruction::RecordLift { record, .. } => { + // records are represented as plain objects, so we + // make a new object and set all the fields with an object + // literal. + let mut result = "{\n".to_string(); + for (field, op) in record.fields.iter().zip(operands) { + result.push_str(&format!("{}: {},\n", field.name.to_mixed_case(), op)); + } + result.push_str("}"); + results.push(result); + } + + Instruction::TupleLower { tuple, .. } => { + // Tuples are represented as an array, sowe can use + // destructuring assignment to lower the tuple into its + // components. + let tmp = self.tmp(); + let mut expr = "const [".to_string(); + for i in 0..tuple.types.len() { + if i > 0 { + expr.push_str(", "); + } + let name = format!("tuple{}_{}", tmp, i); + expr.push_str(&name); + results.push(name); + } + self.src.js(&format!("{}] = {};\n", expr, operands[0])); + } + + Instruction::TupleLift { .. } => { + // Tuples are represented as an array, so we just shove all + // the operands into an array. + results.push(format!("[{}]", operands.join(", "))); + } + + Instruction::FlagsLower { flags, .. } => { + let repr = js_flags_repr(flags); + let validate = match repr { + JsFlagsRepr::Number => self.gen.intrinsic(Intrinsic::ValidateFlags), + JsFlagsRepr::Bigint => self.gen.intrinsic(Intrinsic::ValidateFlags64), + }; + let op0 = &operands[0]; + let len = flags.flags.len(); + let n = repr.suffix(); + let tmp = self.tmp(); + let mask = (1u128 << len) - 1; + self.src.js(&format!( + "const flags{tmp} = {validate}({op0}, {mask}{n});\n" + )); + match repr { + JsFlagsRepr::Number => { + results.push(format!("flags{}", tmp)); + } + JsFlagsRepr::Bigint => { + for i in 0..flags.repr().count() { + let i = 32 * i; + results.push(format!("Number((flags{tmp} >> {i}n) & 0xffffffffn)",)); + } + } + } + } + + Instruction::FlagsLift { flags, .. } => { + let repr = js_flags_repr(flags); + let n = repr.suffix(); + let tmp = self.tmp(); + let operand = match repr { + JsFlagsRepr::Number => operands[0].clone(), + JsFlagsRepr::Bigint => { + self.src.js(&format!("let flags{tmp} = 0n;\n")); + for (i, op) in operands.iter().enumerate() { + let i = 32 * i; + self.src + .js(&format!("flags{tmp} |= BigInt({op}) << {i}n;\n",)); + } + format!("flags{tmp}") + } + }; + let validate = match repr { + JsFlagsRepr::Number => self.gen.intrinsic(Intrinsic::ValidateFlags), + JsFlagsRepr::Bigint => self.gen.intrinsic(Intrinsic::ValidateFlags64), + }; + let len = flags.flags.len(); + let mask = (1u128 << len) - 1; + results.push(format!("{validate}({operand}, {mask}{n})")); + } + + Instruction::VariantPayloadName => results.push("e".to_string()), + + Instruction::VariantLower { + variant, + results: result_types, + name, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + let tmp = self.tmp(); + self.src + .js(&format!("const variant{} = {};\n", tmp, operands[0])); + + for i in 0..result_types.len() { + self.src.js(&format!("let variant{}_{};\n", tmp, i)); + results.push(format!("variant{}_{}", tmp, i)); + } + + let expr_to_match = format!("variant{}.tag", tmp); + + self.src.js(&format!("switch ({}) {{\n", expr_to_match)); + for (case, (block, block_results)) in variant.cases.iter().zip(blocks) { + self.src + .js(&format!("case \"{}\": {{\n", case.name.as_str())); + if case.ty != Type::Unit { + self.src.js(&format!("const e = variant{}.val;\n", tmp)); + } + self.src.js(&block); + + for (i, result) in block_results.iter().enumerate() { + self.src + .js(&format!("variant{}_{} = {};\n", tmp, i, result)); + } + self.src.js("break;\n}\n"); + } + let variant_name = name.to_camel_case(); + self.src.js("default:\n"); + self.src.js(&format!( + "throw new RangeError(\"invalid variant specified for {}\");\n", + variant_name + )); + self.src.js("}\n"); + } + + Instruction::VariantLift { variant, name, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + + let tmp = self.tmp(); + + self.src.js(&format!("let variant{};\n", tmp)); + self.src.js(&format!("switch ({}) {{\n", operands[0])); + for (i, (case, (block, block_results))) in + variant.cases.iter().zip(blocks).enumerate() + { + self.src.js(&format!("case {}: {{\n", i)); + self.src.js(&block); + + self.src.js(&format!("variant{} = {{\n", tmp)); + self.src.js(&format!("tag: \"{}\",\n", case.name.as_str())); + assert!(block_results.len() == 1); + if case.ty != Type::Unit { + self.src.js(&format!("val: {},\n", block_results[0])); + } else { + assert_eq!(block_results[0], "undefined"); + } + self.src.js("};\n"); + self.src.js("break;\n}\n"); + } + let variant_name = name.to_camel_case(); + self.src.js("default:\n"); + self.src.js(&format!( + "throw new RangeError(\"invalid variant discriminant for {}\");\n", + variant_name + )); + self.src.js("}\n"); + results.push(format!("variant{}", tmp)); + } + + Instruction::UnionLower { + union, + results: result_types, + name, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + let tmp = self.tmp(); + let op0 = &operands[0]; + self.src.js(&format!("const union{tmp} = {op0};\n")); + + for i in 0..result_types.len() { + self.src.js(&format!("let union{tmp}_{i};\n")); + results.push(format!("union{tmp}_{i}")); + } + + self.src.js(&format!("switch (union{tmp}.tag) {{\n")); + for (i, (_case, (block, block_results))) in + union.cases.iter().zip(blocks).enumerate() + { + self.src.js(&format!("case {i}: {{\n")); + self.src.js(&format!("const e = union{tmp}.val;\n")); + self.src.js(&block); + for (i, result) in block_results.iter().enumerate() { + self.src.js(&format!("union{tmp}_{i} = {result};\n")); + } + self.src.js("break;\n}\n"); + } + let name = name.to_camel_case(); + self.src.js("default:\n"); + self.src.js(&format!( + "throw new RangeError(\"invalid union specified for {name}\");\n", + )); + self.src.js("}\n"); + } + + Instruction::UnionLift { union, name, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + + let tmp = self.tmp(); + + self.src.js(&format!("let union{tmp};\n")); + self.src.js(&format!("switch ({}) {{\n", operands[0])); + for (i, (_case, (block, block_results))) in + union.cases.iter().zip(blocks).enumerate() + { + assert!(block_results.len() == 1); + let block_result = &block_results[0]; + self.src.js(&format!( + "case {i}: {{ + {block} + union{tmp} = {{ + tag: {i}, + val: {block_result}, + }}; + break; + }}\n" + )); + } + let name = name.to_camel_case(); + self.src.js("default:\n"); + self.src.js(&format!( + "throw new RangeError(\"invalid union discriminant for {name}\");\n", + )); + self.src.js("}\n"); + results.push(format!("union{tmp}")); + } + + Instruction::OptionLower { + payload, + results: result_types, + .. + } => { + let (mut some, some_results) = self.blocks.pop().unwrap(); + let (mut none, none_results) = self.blocks.pop().unwrap(); + + let tmp = self.tmp(); + self.src + .js(&format!("const variant{tmp} = {};\n", operands[0])); + + for i in 0..result_types.len() { + self.src.js(&format!("let variant{tmp}_{i};\n")); + results.push(format!("variant{tmp}_{i}")); + + let some_result = &some_results[i]; + let none_result = &none_results[i]; + some.push_str(&format!("variant{tmp}_{i} = {some_result};\n")); + none.push_str(&format!("variant{tmp}_{i} = {none_result};\n")); + } + + if self.gen.maybe_null(iface, payload) { + self.src.js(&format!( + " + switch (variant{tmp}.tag) {{ + case \"none\": {{ + {none} + break; + }} + case \"some\": {{ + const e = variant{tmp}.val; + {some} + break; + }} + default: {{ + throw new RangeError(\"invalid variant specified for option\"); + }} + }} + " + )); + } else { + self.src.js(&format!( + " + switch (variant{tmp}) {{ + case null: {{ + {none} + break; + }} + default: {{ + const e = variant{tmp}; + {some} + break; + }} + }} + " + )); + } + } + + Instruction::OptionLift { payload, .. } => { + let (some, some_results) = self.blocks.pop().unwrap(); + let (none, none_results) = self.blocks.pop().unwrap(); + assert!(none_results.len() == 1); + assert!(some_results.len() == 1); + let some_result = &some_results[0]; + assert_eq!(none_results[0], "undefined"); + + let tmp = self.tmp(); + + self.src.js(&format!("let variant{tmp};\n")); + self.src.js(&format!("switch ({}) {{\n", operands[0])); + + if self.gen.maybe_null(iface, payload) { + self.src.js(&format!( + " + case 0: {{ + {none} + variant{tmp} = {{ tag: \"none\" }}; + break; + }} + case 1: {{ + {some} + variant{tmp} = {{ tag: \"some\", val: {some_result} }}; + break; + }} + ", + )); + } else { + self.src.js(&format!( + " + case 0: {{ + {none} + variant{tmp} = null; + break; + }} + case 1: {{ + {some} + variant{tmp} = {some_result}; + break; + }} + ", + )); + } + self.src.js(" + default: + throw new RangeError(\"invalid variant discriminant for option\"); + "); + self.src.js("}\n"); + results.push(format!("variant{tmp}")); + } + + Instruction::ExpectedLower { + results: result_types, + .. + } => { + let (mut err, err_results) = self.blocks.pop().unwrap(); + let (mut ok, ok_results) = self.blocks.pop().unwrap(); + + let tmp = self.tmp(); + self.src + .js(&format!("const variant{tmp} = {};\n", operands[0])); + + for i in 0..result_types.len() { + self.src.js(&format!("let variant{tmp}_{i};\n")); + results.push(format!("variant{tmp}_{i}")); + + let ok_result = &ok_results[i]; + let err_result = &err_results[i]; + ok.push_str(&format!("variant{tmp}_{i} = {ok_result};\n")); + err.push_str(&format!("variant{tmp}_{i} = {err_result};\n")); + } + + self.src.js(&format!( + " + switch (variant{tmp}.tag) {{ + case \"ok\": {{ + const e = variant{tmp}.val; + {ok} + break; + }} + case \"err\": {{ + const e = variant{tmp}.val; + {err} + break; + }} + default: {{ + throw new RangeError(\"invalid variant specified for expected\"); + }} + }} + " + )); + } + + Instruction::ExpectedLift { .. } => { + let (err, err_results) = self.blocks.pop().unwrap(); + let (ok, ok_results) = self.blocks.pop().unwrap(); + let err_result = &err_results[0]; + let ok_result = &ok_results[0]; + let tmp = self.tmp(); + let op0 = &operands[0]; + self.src.js(&format!( + " + let variant{tmp}; + switch ({op0}) {{ + case 0: {{ + {ok} + variant{tmp} = {{ tag: \"ok\", val: {ok_result} }}; + break; + }} + case 1: {{ + {err} + variant{tmp} = {{ tag: \"err\", val: {err_result} }}; + break; + }} + default: {{ + throw new RangeError(\"invalid variant discriminant for expected\"); + }} + }} + ", + )); + results.push(format!("variant{tmp}")); + } + + // Lowers an enum in accordance with https://webidl.spec.whatwg.org/#es-enumeration. + Instruction::EnumLower { name, enum_, .. } => { + let tmp = self.tmp(); + + let to_string = self.gen.intrinsic(Intrinsic::ToString); + self.src + .js(&format!("const val{tmp} = {to_string}({});\n", operands[0])); + + // Declare a variable to hold the result. + self.src.js(&format!("let enum{tmp};\n")); + + self.src.js(&format!("switch (val{tmp}) {{\n")); + for (i, case) in enum_.cases.iter().enumerate() { + self.src.js(&format!( + "\ + case \"{case}\": {{ + enum{tmp} = {i}; + break; + }} + ", + case = case.name + )); + } + self.src.js(&format!("\ + default: {{ + throw new TypeError(`\"${{val{tmp}}}\" is not one of the cases of {name}`); + }} + }} + ")); + + results.push(format!("enum{tmp}")); + } + + Instruction::EnumLift { name, enum_, .. } => { + let tmp = self.tmp(); + + self.src.js(&format!("let enum{tmp};\n")); + + self.src.js(&format!("switch ({}) {{\n", operands[0])); + for (i, case) in enum_.cases.iter().enumerate() { + self.src.js(&format!( + "\ + case {i}: {{ + enum{tmp} = \"{case}\"; + break; + }} + ", + case = case.name + )); + } + self.src.js(&format!( + "\ + default: {{ + throw new RangeError(\"invalid discriminant specified for {name}\"); + }} + }} + ", + name = name.to_camel_case() + )); + + results.push(format!("enum{tmp}")); + } + + Instruction::ListCanonLower { element, realloc } => { + // Lowering only happens when we're passing lists into wasm, + // which forces us to always allocate, so this should always be + // `Some`. + let realloc = realloc.unwrap(); + self.gen.needs_get_export = true; + self.needs_memory = true; + self.needs_realloc = Some(realloc.to_string()); + let tmp = self.tmp(); + + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + self.src + .js(&format!("const val{} = {};\n", tmp, operands[0])); + self.src.js(&format!("const len{} = val{0}.length;\n", tmp)); + self.src.js(&format!( + "const ptr{} = realloc(0, 0, {}, len{0} * {});\n", + tmp, align, size, + )); + // TODO: this is the wrong endianness + self.src.js(&format!( + "(new Uint8Array(memory.buffer, ptr{0}, len{0} * {1})).set(new Uint8Array(val{0}.buffer, val{0}.byteOffset, len{0} * {1}));\n", + tmp, size, + )); + results.push(format!("ptr{}", tmp)); + results.push(format!("len{}", tmp)); + } + Instruction::ListCanonLift { element, free, .. } => { + self.needs_memory = true; + let tmp = self.tmp(); + self.src + .js(&format!("const ptr{} = {};\n", tmp, operands[0])); + self.src + .js(&format!("const len{} = {};\n", tmp, operands[1])); + // TODO: this is the wrong endianness + let array_ty = self.gen.array_ty(iface, element).unwrap(); + let result = format!( + "new {}(memory.buffer.slice(ptr{}, ptr{1} + len{1} * {}))", + array_ty, + tmp, + self.gen.sizes.size(element), + ); + let align = self.gen.sizes.align(element); + match free { + Some(free) => { + self.needs_free = Some(free.to_string()); + self.src.js(&format!("const list{} = {};\n", tmp, result)); + self.src + .js(&format!("free(ptr{}, len{0}, {});\n", tmp, align)); + results.push(format!("list{}", tmp)); + } + None => results.push(result), + } + } + Instruction::StringLower { realloc } => { + // Lowering only happens when we're passing strings into wasm, + // which forces us to always allocate, so this should always be + // `Some`. + let realloc = realloc.unwrap(); + self.gen.needs_get_export = true; + self.needs_memory = true; + self.needs_realloc = Some(realloc.to_string()); + let tmp = self.tmp(); + + let encode = self.gen.intrinsic(Intrinsic::Utf8Encode); + self.src.js(&format!( + "const ptr{} = {}({}, realloc, memory);\n", + tmp, encode, operands[0], + )); + let encoded_len = self.gen.intrinsic(Intrinsic::Utf8EncodedLen); + self.src + .js(&format!("const len{} = {};\n", tmp, encoded_len)); + results.push(format!("ptr{}", tmp)); + results.push(format!("len{}", tmp)); + } + Instruction::StringLift { free } => { + self.needs_memory = true; + let tmp = self.tmp(); + self.src + .js(&format!("const ptr{} = {};\n", tmp, operands[0])); + self.src + .js(&format!("const len{} = {};\n", tmp, operands[1])); + let decoder = self.gen.intrinsic(Intrinsic::Utf8Decoder); + let result = format!( + "{}.decode(new Uint8Array(memory.buffer, ptr{}, len{1}))", + decoder, tmp, + ); + match free { + Some(free) => { + self.needs_free = Some(free.to_string()); + self.src.js(&format!("const list{} = {};\n", tmp, result)); + self.src.js(&format!("free(ptr{}, len{0}, 1);\n", tmp)); + results.push(format!("list{}", tmp)); + } + None => results.push(result), + } + } + + Instruction::ListLower { element, realloc } => { + let realloc = realloc.unwrap(); + let (body, body_results) = self.blocks.pop().unwrap(); + assert!(body_results.is_empty()); + let tmp = self.tmp(); + let vec = format!("vec{}", tmp); + let result = format!("result{}", tmp); + let len = format!("len{}", tmp); + self.needs_realloc = Some(realloc.to_string()); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + + // first store our vec-to-lower in a temporary since we'll + // reference it multiple times. + self.src.js(&format!("const {} = {};\n", vec, operands[0])); + self.src.js(&format!("const {} = {}.length;\n", len, vec)); + + // ... then realloc space for the result in the guest module + self.src.js(&format!( + "const {} = realloc(0, 0, {}, {} * {});\n", + result, align, len, size, + )); + + // ... then consume the vector and use the block to lower the + // result. + self.src + .js(&format!("for (let i = 0; i < {}.length; i++) {{\n", vec)); + self.src.js(&format!("const e = {}[i];\n", vec)); + self.src + .js(&format!("const base = {} + i * {};\n", result, size)); + self.src.js(&body); + self.src.js("}\n"); + + results.push(result); + results.push(len); + } + + Instruction::ListLift { element, free, .. } => { + let (body, body_results) = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + let len = format!("len{}", tmp); + self.src.js(&format!("const {} = {};\n", len, operands[1])); + let base = format!("base{}", tmp); + self.src.js(&format!("const {} = {};\n", base, operands[0])); + let result = format!("result{}", tmp); + self.src.js(&format!("const {} = [];\n", result)); + results.push(result.clone()); + + self.src + .js(&format!("for (let i = 0; i < {}; i++) {{\n", len)); + self.src + .js(&format!("const base = {} + i * {};\n", base, size)); + self.src.js(&body); + assert_eq!(body_results.len(), 1); + self.src + .js(&format!("{}.push({});\n", result, body_results[0])); + self.src.js("}\n"); + + if let Some(free) = free { + self.needs_free = Some(free.to_string()); + self.src + .js(&format!("free({}, {} * {}, {});\n", base, len, size, align,)); + } + } + + Instruction::IterElem { .. } => results.push("e".to_string()), + + Instruction::IterBasePointer => results.push("base".to_string()), + + Instruction::CallWasm { + iface: _, + name, + sig, + } => { + self.bind_results(sig.results.len(), results); + self.src.js(&self.src_object); + self.src.js("._exports['"); + self.src.js(&name); + self.src.js("']("); + self.src.js(&operands.join(", ")); + self.src.js(");\n"); + } + + Instruction::CallWasmAsyncExport { + module: _, + name, + params: _, + results: wasm_results, + } => { + self.bind_results(wasm_results.len(), results); + let promises = self.gen.intrinsic(Intrinsic::Promises); + self.src.js(&format!( + "\ + await new Promise((resolve, reject) => {{ + const promise_ctx = {promises}.insert(val => {{ + if (typeof val !== 'number') + return reject(val); + resolve(\ + ", + promises = promises + )); + + if wasm_results.len() > 0 { + self.src.js("["); + let operands = &["val".to_string()]; + let mut results = Vec::new(); + for (i, result) in wasm_results.iter().enumerate() { + if i > 0 { + self.src.js(", "); + } + let method = match result { + WasmType::I32 => "getInt32", + WasmType::I64 => "getBigInt64", + WasmType::F32 => "getFloat32", + WasmType::F64 => "getFloat64", + }; + self.load(method, (i * 8) as i32, operands, &mut results); + self.src.js(&results.pop().unwrap()); + } + self.src.js("]"); + } + + // Finish the blocks from above + self.src.js(");\n"); // `resolve(...)` + self.src.js("});\n"); // `promises.insert(...)` + + let with = self.gen.intrinsic(Intrinsic::WithCurrentPromise); + self.src.js(&with); + self.src.js("(promise_ctx, _prev => {\n"); + self.src.js(&self.src_object); + self.src.js("._exports['"); + self.src.js(&name); + self.src.js("']("); + for op in operands { + self.src.js(op); + self.src.js(", "); + } + self.src.js("promise_ctx);\n"); + self.src.js("});\n"); // call to `with` + self.src.js("});\n"); // `await new Promise(...)` + } + + Instruction::CallInterface { module: _, func } => { + let call = |me: &mut FunctionBindgen<'_>| match &func.kind { + FunctionKind::Freestanding | FunctionKind::Static { .. } => { + me.src.js(&format!( + "obj.{}({})", + func.name.to_mixed_case(), + operands.join(", "), + )); + } + FunctionKind::Method { name, .. } => { + me.src.js(&format!( + "{}.{}({})", + operands[0], + name.to_mixed_case(), + operands[1..].join(", "), + )); + } + }; + let mut bind_results = |me: &mut FunctionBindgen<'_>| match &func.result { + Type::Unit => { + results.push("".to_string()); + } + _ => { + me.src.js("const ret = "); + results.push("ret".to_string()); + } + }; + + if func.is_async { + let with = self.gen.intrinsic(Intrinsic::WithCurrentPromise); + let promises = self.gen.intrinsic(Intrinsic::Promises); + self.src.js(&with); + self.src.js("(null, cur_promise => {\n"); + self.src.js(&format!( + "const catch_closure = e => {}.remove(cur_promise)(e);\n", + promises + )); + call(self); + self.src.js(".then(e => {\n"); + match &func.result { + Type::Unit => { + results.push("".to_string()); + } + _ => { + bind_results(self); + self.src.js("e;\n"); + } + } + } else { + bind_results(self); + call(self); + self.src.js(";\n"); + } + } + + Instruction::Return { amt, func: _ } => match amt { + 0 => {} + 1 => self.src.js(&format!("return {};\n", operands[0])), + _ => { + assert!(self.in_import); + self.src.js(&format!("return [{}];\n", operands.join(", "))); + } + }, + + Instruction::ReturnAsyncImport { .. } => { + // When we reenter webassembly successfully that means that the + // host's promise resolved without exception. Take the current + // promise index saved as part of `CallInterface` and update the + // `CUR_PROMISE` global with what's currently being executed. + // This'll get reset once the wasm returns again. + // + // Note that the name `cur_promise` used here is introduced in + // the `CallInterface` codegen above in the closure for + // `with_current_promise` which we're using here. + // + // TODO: hardcoding `__indirect_function_table` and no help if + // it's not actually defined. + self.gen.needs_get_export = true; + let with = self.gen.intrinsic(Intrinsic::WithCurrentPromise); + self.src.js(&format!( + "\ + {with}(cur_promise, _prev => {{ + get_export(\"__indirect_function_table\").get({})({}); + }}); + ", + operands[0], + operands[1..].join(", "), + with = with, + )); + } + + Instruction::I32Load { offset } => self.load("getInt32", *offset, operands, results), + Instruction::I64Load { offset } => self.load("getBigInt64", *offset, operands, results), + Instruction::F32Load { offset } => self.load("getFloat32", *offset, operands, results), + Instruction::F64Load { offset } => self.load("getFloat64", *offset, operands, results), + Instruction::I32Load8U { offset } => self.load("getUint8", *offset, operands, results), + Instruction::I32Load8S { offset } => self.load("getInt8", *offset, operands, results), + Instruction::I32Load16U { offset } => { + self.load("getUint16", *offset, operands, results) + } + Instruction::I32Load16S { offset } => self.load("getInt16", *offset, operands, results), + Instruction::I32Store { offset } => self.store("setInt32", *offset, operands), + Instruction::I64Store { offset } => self.store("setBigInt64", *offset, operands), + Instruction::F32Store { offset } => self.store("setFloat32", *offset, operands), + Instruction::F64Store { offset } => self.store("setFloat64", *offset, operands), + Instruction::I32Store8 { offset } => self.store("setInt8", *offset, operands), + Instruction::I32Store16 { offset } => self.store("setInt16", *offset, operands), + + Instruction::Malloc { + realloc, + size, + align, + } => { + self.needs_realloc = Some(realloc.to_string()); + let tmp = self.tmp(); + let ptr = format!("ptr{}", tmp); + self.src.js(&format!( + "const {} = realloc(0, 0, {}, {});\n", + ptr, align, size + )); + results.push(ptr); + } + + i => unimplemented!("{:?}", i), + } + } +} + +impl Js { + fn print_intrinsics(&mut self) { + if self.all_intrinsics.contains(&Intrinsic::I32ToF32) + || self.all_intrinsics.contains(&Intrinsic::F32ToI32) + { + self.src.js(" + const I32_TO_F32_I = new Int32Array(1); + const I32_TO_F32_F = new Float32Array(I32_TO_F32_I.buffer); + "); + } + if self.all_intrinsics.contains(&Intrinsic::I64ToF64) + || self.all_intrinsics.contains(&Intrinsic::F64ToI64) + { + self.src.js(" + const I64_TO_F64_I = new BigInt64Array(1); + const I64_TO_F64_F = new Float64Array(I64_TO_F64_I.buffer); + "); + } + + if self.all_intrinsics.contains(&Intrinsic::Promises) { + self.all_intrinsics.insert(Intrinsic::Slab); + } + + for i in mem::take(&mut self.all_intrinsics) { + self.print_intrinsic(i); + } + } + + fn print_intrinsic(&mut self, i: Intrinsic) { + match i { + Intrinsic::ClampGuest => self.src.js(" + export function clamp_guest(i, min, max) { + if (i < min || i > max) \ + throw new RangeError(`must be between ${min} and ${max}`); + return i; + } + "), + Intrinsic::ClampHost => self.src.js(" + export function clamp_host(i, min, max) { + if (!Number.isInteger(i)) \ + throw new TypeError(`must be an integer`); + if (i < min || i > max) \ + throw new RangeError(`must be between ${min} and ${max}`); + return i; + } + "), + + Intrinsic::DataView => self.src.js(" + let DATA_VIEW = new DataView(new ArrayBuffer()); + + export function data_view(mem) { + if (DATA_VIEW.buffer !== mem.buffer) \ + DATA_VIEW = new DataView(mem.buffer); + return DATA_VIEW; + } + "), + + Intrinsic::ClampHost64 => self.src.js(" + export function clamp_host64(i, min, max) { + if (typeof i !== 'bigint') \ + throw new TypeError(`must be a bigint`); + if (i < min || i > max) \ + throw new RangeError(`must be between ${min} and ${max}`); + return i; + } + "), + + Intrinsic::ValidateGuestChar => self.src.js(" + export function validate_guest_char(i) { + if ((i > 0x10ffff) || (i >= 0xd800 && i <= 0xdfff)) \ + throw new RangeError(`not a valid char`); + return String.fromCodePoint(i); + } + "), + + // TODO: this is incorrect. It at least allows strings of length > 0 + // but it probably doesn't do the right thing for unicode or invalid + // utf16 strings either. + Intrinsic::ValidateHostChar => self.src.js(" + export function validate_host_char(s) { + if (typeof s !== 'string') \ + throw new TypeError(`must be a string`); + return s.codePointAt(0); + } + "), + + Intrinsic::ValidateFlags => self.src.js(" + export function validate_flags(flags, mask) { + if (!Number.isInteger(flags)) \ + throw new TypeError('flags were not an integer'); + if ((flags & ~mask) != 0) + throw new TypeError('flags have extraneous bits set'); + return flags; + } + "), + + Intrinsic::ValidateFlags64 => self.src.js(" + export function validate_flags64(flags, mask) { + if (typeof flags !== 'bigint') + throw new TypeError('flags were not a bigint'); + if ((flags & ~mask) != 0n) + throw new TypeError('flags have extraneous bits set'); + return flags; + } + "), + + Intrinsic::ToString => self.src.js(" + export function to_string(val) { + if (typeof val === 'symbol') { + throw new TypeError('symbols cannot be converted to strings'); + } else { + // Calling `String` almost directly calls `ToString`, except that it also allows symbols, + // which is why we have the symbol-rejecting branch above. + // + // Definition of `String`: https://tc39.es/ecma262/#sec-string-constructor-string-value + return String(val); + } + } + "), + + Intrinsic::I32ToF32 => self.src.js(" + export function i32ToF32(i) { + I32_TO_F32_I[0] = i; + return I32_TO_F32_F[0]; + } + "), + Intrinsic::F32ToI32 => self.src.js(" + export function f32ToI32(f) { + I32_TO_F32_F[0] = f; + return I32_TO_F32_I[0]; + } + "), + Intrinsic::I64ToF64 => self.src.js(" + export function i64ToF64(i) { + I64_TO_F64_I[0] = i; + return I64_TO_F64_F[0]; + } + "), + Intrinsic::F64ToI64 => self.src.js(" + export function f64ToI64(f) { + I64_TO_F64_F[0] = f; + return I64_TO_F64_I[0]; + } + "), + + Intrinsic::Utf8Decoder => self + .src + .js("export const UTF8_DECODER = new TextDecoder('utf-8');\n"), + + Intrinsic::Utf8EncodedLen => self.src.js("export let UTF8_ENCODED_LEN = 0;\n"), + + Intrinsic::Utf8Encode => self.src.js(" + const UTF8_ENCODER = new TextEncoder('utf-8'); + + export function utf8_encode(s, realloc, memory) { + if (typeof s !== 'string') \ + throw new TypeError('expected a string'); + + if (s.length === 0) { + UTF8_ENCODED_LEN = 0; + return 1; + } + + let alloc_len = 0; + let ptr = 0; + let writtenTotal = 0; + while (s.length > 0) { + ptr = realloc(ptr, alloc_len, 1, alloc_len + s.length); + alloc_len += s.length; + const { read, written } = UTF8_ENCODER.encodeInto( + s, + new Uint8Array(memory.buffer, ptr + writtenTotal, alloc_len - writtenTotal), + ); + writtenTotal += written; + s = s.slice(read); + } + if (alloc_len > writtenTotal) + ptr = realloc(ptr, alloc_len, 1, writtenTotal); + UTF8_ENCODED_LEN = writtenTotal; + return ptr; + } + "), + + Intrinsic::Slab => self.src.js(" + export class Slab { + constructor() { + this.list = []; + this.head = 0; + } + + insert(val) { + if (this.head >= this.list.length) { + this.list.push({ + next: this.list.length + 1, + val: undefined, + }); + } + const ret = this.head; + const slot = this.list[ret]; + this.head = slot.next; + slot.next = -1; + slot.val = val; + return ret; + } + + get(idx) { + if (idx >= this.list.length) + throw new RangeError('handle index not valid'); + const slot = this.list[idx]; + if (slot.next === -1) + return slot.val; + throw new RangeError('handle index not valid'); + } + + remove(idx) { + const ret = this.get(idx); // validate the slot + const slot = this.list[idx]; + slot.val = undefined; + slot.next = this.head; + this.head = idx; + return ret; + } + } + "), + + Intrinsic::Promises => self.src.js("export const PROMISES = new Slab();\n"), + Intrinsic::WithCurrentPromise => self.src.js(" + let CUR_PROMISE = null; + export function with_current_promise(val, closure) { + const prev = CUR_PROMISE; + CUR_PROMISE = val; + try { + closure(prev); + } finally { + CUR_PROMISE = prev; + } + } + "), + Intrinsic::ThrowInvalidBool => self.src.js(" + export function throw_invalid_bool() { + throw new RangeError(\"invalid variant discriminant for bool\"); + } + "), + } + } +} + +pub fn to_js_ident(name: &str) -> &str { + match name { + "in" => "in_", + "import" => "import_", + s => s, + } +} + +#[derive(Default)] +struct Source { + js: wit_bindgen_gen_core::Source, + ts: wit_bindgen_gen_core::Source, +} + +impl Source { + fn js(&mut self, s: &str) { + self.js.push_str(s); + } + fn ts(&mut self, s: &str) { + self.ts.push_str(s); + } +} + +enum JsFlagsRepr { + Number, + Bigint, +} + +impl JsFlagsRepr { + fn ty(&self) -> &'static str { + match self { + JsFlagsRepr::Number => "number", + JsFlagsRepr::Bigint => "bigint", + } + } + fn suffix(&self) -> &'static str { + match self { + JsFlagsRepr::Number => "", + JsFlagsRepr::Bigint => "n", + } + } +} + +fn js_flags_repr(f: &Flags) -> JsFlagsRepr { + match f.repr() { + FlagsRepr::U8 | FlagsRepr::U16 | FlagsRepr::U32(1) => JsFlagsRepr::Number, + FlagsRepr::U32(_) => JsFlagsRepr::Bigint, + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/codegen.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/codegen.rs new file mode 100644 index 0000000..e776439 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/codegen.rs @@ -0,0 +1,38 @@ +use std::path::Path; +use std::process::Command; + +mod exports { + test_helpers::codegen_js_export!( + // ... + "*.wit" + ); +} + +mod imports { + test_helpers::codegen_js_import!( + "*.wit" + + // This uses buffers, which we don't support in imports just yet + // TODO: should support this + "!wasi-next.wit" + "!host.wit" + ); +} + +fn verify(dir: &str, name: &str) { + let (cmd, args) = if cfg!(windows) { + ("cmd.exe", &["/c", "npx.cmd"] as &[&str]) + } else { + ("npx", &[] as &[&str]) + }; + + let status = Command::new(cmd) + .args(args) + .arg("eslint") + .arg("-c") + .arg(".eslintrc.js") + .arg(Path::new(dir).join(&format!("{}.js", name))) + .status() + .unwrap(); + assert!(status.success()); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/helpers.d.ts b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/helpers.d.ts new file mode 100644 index 0000000..724a209 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/helpers.d.ts @@ -0,0 +1,7 @@ +export function getWasm(): Uint8Array; + +export interface Wasi { + start(instance: WebAssembly.Instance): void; +} + +export function addWasiToImports(importObj: any): Wasi; diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/helpers.js b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/helpers.js new file mode 100644 index 0000000..13c0a7d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/helpers.js @@ -0,0 +1,26 @@ +import { readFileSync } from 'fs'; +import { WASI } from 'wasi'; + +export function getWasm() { + return readFileSync(process.argv[2]); +} + +class MyWasi { + constructor(wasi) { + this.wasi = wasi; + } + + start(instance) { + if ('_start' in instance.exports) { + this.wasi.start(instance); + } else { + this.wasi.initialize(instance); + } + } +} + +export function addWasiToImports(importObj) { + const wasi = new WASI(); + importObj.wasi_snapshot_preview1 = wasi.wasiImport; + return new MyWasi(wasi); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/runtime.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/runtime.rs new file mode 100644 index 0000000..1141561 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-js/tests/runtime.rs @@ -0,0 +1,99 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use wit_bindgen_gen_core::Generator; + +test_helpers::runtime_tests!("ts"); + +fn execute(name: &str, wasm: &Path, ts: &Path, imports: &Path, exports: &Path) { + let mut dir = PathBuf::from(env!("OUT_DIR")); + dir.push(name); + drop(fs::remove_dir_all(&dir)); + fs::create_dir_all(&dir).unwrap(); + + println!("OUT_DIR = {:?}", dir); + println!("Generating bindings..."); + // We call `generate_all` with exports from the imports.wit file, and + // imports from the exports.wit wit file. It's reversed because we're + // implementing the host side of these APIs. + let imports = wit_bindgen_gen_core::wit_parser::Interface::parse_file(imports).unwrap(); + let exports = wit_bindgen_gen_core::wit_parser::Interface::parse_file(exports).unwrap(); + let mut files = Default::default(); + wit_bindgen_gen_js::Opts::default() + .build() + .generate_all(&[exports], &[imports], &mut files); + for (file, contents) in files.iter() { + fs::write(dir.join(file), contents).unwrap(); + } + + let (cmd, args) = if cfg!(windows) { + ("cmd.exe", &["/c", "npx.cmd"] as &[&str]) + } else { + ("npx", &[] as &[&str]) + }; + + fs::copy(ts, dir.join("host.ts")).unwrap(); + fs::copy("tests/helpers.d.ts", dir.join("helpers.d.ts")).unwrap(); + fs::copy("tests/helpers.js", dir.join("helpers.js")).unwrap(); + let config = dir.join("tsconfig.json"); + fs::write( + &config, + format!( + r#" + {{ + "files": ["host.ts"], + "compilerOptions": {{ + "module": "esnext", + "target": "es2020", + "strict": true, + "strictNullChecks": true, + "baseUrl": {0:?}, + "outDir": {0:?} + }} + }} + "#, + dir, + ), + ) + .unwrap(); + + run(Command::new(cmd) + .args(args) + .arg("tsc") + .arg("--project") + .arg(&config)); + + // Currently there's mysterious uvwasi errors creating a `WASI` on Windows. + // Unsure what's happening so let's ignore these tests for now since there's + // not much Windows-specific here anyway. + if cfg!(windows) { + return; + } + + fs::write(dir.join("package.json"), "{\"type\":\"module\"}").unwrap(); + let mut path = Vec::new(); + path.push(env::current_dir().unwrap()); + path.push(dir.clone()); + println!("{:?}", std::env::join_paths(&path)); + run(Command::new("node") + .arg("--experimental-wasi-unstable-preview1") + .arg(dir.join("host.js")) + .env("NODE_PATH", std::env::join_paths(&path).unwrap()) + .arg(wasm)); +} + +fn run(cmd: &mut Command) { + println!("running {:?}", cmd); + let output = cmd.output().expect("failed to executed"); + println!("status: {}", output.status); + println!( + "stdout:\n {}", + String::from_utf8_lossy(&output.stdout).replace("\n", "\n ") + ); + println!( + "stderr:\n {}", + String::from_utf8_lossy(&output.stderr).replace("\n", "\n ") + ); + assert!(output.status.success()); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-markdown/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-markdown/Cargo.toml new file mode 100644 index 0000000..a969654 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-markdown/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "wit-bindgen-gen-markdown" +version = "0.1.0" +edition = "2018" + +[lib] +doctest = false +test = false + +[dependencies] +heck = "0.3" +pulldown-cmark = { version = "0.8", default-features = false } +structopt = { version = "0.3", default-features = false, optional = true } +wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' } diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-markdown/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-markdown/src/lib.rs new file mode 100644 index 0000000..bd07c1f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-markdown/src/lib.rs @@ -0,0 +1,466 @@ +use heck::*; +use pulldown_cmark::{html, Event, LinkType, Parser, Tag}; +use std::collections::HashMap; +use wit_bindgen_gen_core::{wit_parser, Direction, Files, Generator, Source}; +use wit_parser::*; + +#[derive(Default)] +pub struct Markdown { + src: Source, + opts: Opts, + sizes: SizeAlign, + hrefs: HashMap, + funcs: usize, + types: usize, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + // ... +} + +impl Opts { + pub fn build(&self) -> Markdown { + let mut r = Markdown::new(); + r.opts = self.clone(); + r + } +} + +impl Markdown { + pub fn new() -> Markdown { + Markdown::default() + } + + fn print_ty(&mut self, iface: &Interface, ty: &Type, skip_name: bool) { + match ty { + Type::Unit => self.src.push_str("`unit`"), + Type::Bool => self.src.push_str("`bool`"), + Type::U8 => self.src.push_str("`u8`"), + Type::S8 => self.src.push_str("`s8`"), + Type::U16 => self.src.push_str("`u16`"), + Type::S16 => self.src.push_str("`s16`"), + Type::U32 => self.src.push_str("`u32`"), + Type::S32 => self.src.push_str("`s32`"), + Type::U64 => self.src.push_str("`u64`"), + Type::S64 => self.src.push_str("`s64`"), + Type::Float32 => self.src.push_str("`float32`"), + Type::Float64 => self.src.push_str("`float64`"), + Type::Char => self.src.push_str("`char`"), + Type::String => self.src.push_str("`string`"), + Type::Handle(id) => { + self.src.push_str("handle<"); + self.src.push_str(&iface.resources[*id].name); + self.src.push_str(">"); + } + Type::Id(id) => { + let ty = &iface.types[*id]; + if !skip_name { + if let Some(name) = &ty.name { + self.src.push_str("[`"); + self.src.push_str(name); + self.src.push_str("`](#"); + self.src.push_str(&name.to_snake_case()); + self.src.push_str(")"); + return; + } + } + match &ty.kind { + TypeDefKind::Type(t) => self.print_ty(iface, t, false), + TypeDefKind::Tuple(t) => { + self.src.push_str("("); + for (i, t) in t.types.iter().enumerate() { + if i > 0 { + self.src.push_str(", "); + } + self.print_ty(iface, t, false); + } + self.src.push_str(")"); + } + TypeDefKind::Record(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Union(_) => { + unreachable!() + } + TypeDefKind::Option(t) => { + self.src.push_str("option<"); + self.print_ty(iface, t, false); + self.src.push_str(">"); + } + TypeDefKind::Expected(e) => { + self.src.push_str("expected<"); + self.print_ty(iface, &e.ok, false); + self.src.push_str(", "); + self.print_ty(iface, &e.err, false); + self.src.push_str(">"); + } + TypeDefKind::List(t) => { + self.src.push_str("list<"); + self.print_ty(iface, t, false); + self.src.push_str(">"); + } + TypeDefKind::Future(t) => { + self.src.push_str("future<"); + self.print_ty(iface, t, false); + self.src.push_str(">"); + } + TypeDefKind::Stream(s) => { + self.src.push_str("stream<"); + self.print_ty(iface, &s.element, false); + self.src.push_str(", "); + self.print_ty(iface, &s.end, false); + self.src.push_str(">"); + } + } + } + } + } + + fn docs(&mut self, docs: &Docs) { + let docs = match &docs.contents { + Some(docs) => docs, + None => return, + }; + for line in docs.lines() { + self.src.push_str(line.trim()); + self.src.push_str("\n"); + } + } + + fn print_type_header(&mut self, name: &str) { + if self.types == 0 { + self.src.push_str("# Types\n\n"); + } + self.types += 1; + self.src.push_str(&format!( + "## `{}`: ", + name.to_snake_case(), + name, + )); + self.hrefs + .insert(name.to_string(), format!("#{}", name.to_snake_case())); + } + + fn print_type_info(&mut self, ty: TypeId, docs: &Docs) { + self.docs(docs); + self.src.push_str("\n"); + self.src + .push_str(&format!("Size: {}, ", self.sizes.size(&Type::Id(ty)))); + self.src + .push_str(&format!("Alignment: {}\n", self.sizes.align(&Type::Id(ty)))); + } +} + +impl Generator for Markdown { + fn preprocess_one(&mut self, iface: &Interface, _dir: Direction) { + self.sizes.fill(iface); + } + + fn type_record( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ) { + self.print_type_header(name); + self.src.push_str("record\n\n"); + self.print_type_info(id, docs); + self.src.push_str("\n### Record Fields\n\n"); + for field in record.fields.iter() { + self.src.push_str(&format!( + "- [`{name}`](#{r}.{f}): ", + r = name.to_snake_case(), + f = field.name.to_snake_case(), + name = field.name, + )); + self.hrefs.insert( + format!("{}::{}", name, field.name), + format!("#{}.{}", name.to_snake_case(), field.name.to_snake_case()), + ); + self.print_ty(iface, &field.ty, false); + self.src.indent(1); + self.src.push_str("\n\n"); + self.docs(&field.docs); + self.src.deindent(1); + self.src.push_str("\n"); + } + } + + fn type_tuple( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + tuple: &Tuple, + docs: &Docs, + ) { + self.print_type_header(name); + self.src.push_str("tuple\n\n"); + self.print_type_info(id, docs); + self.src.push_str("\n### Tuple Fields\n\n"); + for (i, ty) in tuple.types.iter().enumerate() { + self.src.push_str(&format!( + "- [`{name}`](#{r}.{f}): ", + r = name.to_snake_case(), + f = i, + name = i, + )); + self.hrefs.insert( + format!("{}::{}", name, i), + format!("#{}.{}", name.to_snake_case(), i), + ); + self.print_ty(iface, ty, false); + self.src.push_str("\n"); + } + } + + fn type_flags( + &mut self, + _iface: &Interface, + id: TypeId, + name: &str, + flags: &Flags, + docs: &Docs, + ) { + self.print_type_header(name); + self.src.push_str("record\n\n"); + self.print_type_info(id, docs); + self.src.push_str("\n### Record Fields\n\n"); + for (i, flag) in flags.flags.iter().enumerate() { + self.src.push_str(&format!( + "- [`{name}`](#{r}.{f}): ", + r = name.to_snake_case(), + f = flag.name.to_snake_case(), + name = flag.name, + )); + self.hrefs.insert( + format!("{}::{}", name, flag.name), + format!("#{}.{}", name.to_snake_case(), flag.name.to_snake_case()), + ); + self.src.indent(1); + self.src.push_str("\n\n"); + self.docs(&flag.docs); + self.src.deindent(1); + self.src.push_str(&format!("Bit: {}\n", i)); + self.src.push_str("\n"); + } + } + + fn type_variant( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + variant: &Variant, + docs: &Docs, + ) { + self.print_type_header(name); + self.src.push_str("variant\n\n"); + self.print_type_info(id, docs); + self.src.push_str("\n### Variant Cases\n\n"); + for case in variant.cases.iter() { + self.src.push_str(&format!( + "- [`{name}`](#{v}.{c})", + v = name.to_snake_case(), + c = case.name.to_snake_case(), + name = case.name, + )); + self.hrefs.insert( + format!("{}::{}", name, case.name), + format!("#{}.{}", name.to_snake_case(), case.name.to_snake_case()), + ); + self.src.push_str(": "); + self.print_ty(iface, &case.ty, false); + self.src.indent(1); + self.src.push_str("\n\n"); + self.docs(&case.docs); + self.src.deindent(1); + self.src.push_str("\n"); + } + } + + fn type_union( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + union: &Union, + docs: &Docs, + ) { + self.print_type_header(name); + self.src.push_str("union\n\n"); + self.print_type_info(id, docs); + self.src.push_str("\n### Union Cases\n\n"); + let snake = name.to_snake_case(); + for (i, case) in union.cases.iter().enumerate() { + self.src.push_str(&format!( + "- [`{i}`](#{snake}.{i})", + )); + self.hrefs + .insert(format!("{name}::{i}"), format!("#{snake}.{i}")); + self.src.push_str(": "); + self.print_ty(iface, &case.ty, false); + self.src.indent(1); + self.src.push_str("\n\n"); + self.docs(&case.docs); + self.src.deindent(1); + self.src.push_str("\n"); + } + } + + fn type_enum(&mut self, _iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + self.print_type_header(name); + self.src.push_str("enum\n\n"); + self.print_type_info(id, docs); + self.src.push_str("\n### Enum Cases\n\n"); + for case in enum_.cases.iter() { + self.src.push_str(&format!( + "- [`{name}`](#{v}.{c})", + v = name.to_snake_case(), + c = case.name.to_snake_case(), + name = case.name, + )); + self.hrefs.insert( + format!("{}::{}", name, case.name), + format!("#{}.{}", name.to_snake_case(), case.name.to_snake_case()), + ); + self.src.indent(1); + self.src.push_str("\n\n"); + self.docs(&case.docs); + self.src.deindent(1); + self.src.push_str("\n"); + } + } + + fn type_option( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + payload: &Type, + docs: &Docs, + ) { + self.print_type_header(name); + self.src.push_str("option<"); + self.print_ty(iface, payload, false); + self.src.push_str(">"); + self.print_type_info(id, docs); + } + + fn type_expected( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + expected: &Expected, + docs: &Docs, + ) { + self.print_type_header(name); + self.src.push_str("expected<"); + self.print_ty(iface, &expected.ok, false); + self.src.push_str(", "); + self.print_ty(iface, &expected.err, false); + self.src.push_str(">"); + self.print_type_info(id, docs); + } + + fn type_resource(&mut self, iface: &Interface, ty: ResourceId) { + drop((iface, ty)); + } + + fn type_alias(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.print_type_header(name); + self.print_ty(iface, ty, true); + self.src.push_str("\n\n"); + self.print_type_info(id, docs); + self.src.push_str("\n"); + } + + fn type_list(&mut self, iface: &Interface, id: TypeId, name: &str, _ty: &Type, docs: &Docs) { + self.type_alias(iface, id, name, &Type::Id(id), docs); + } + + fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.type_alias(iface, id, name, ty, docs) + } + + fn import(&mut self, iface: &Interface, func: &Function) { + if self.funcs == 0 { + self.src.push_str("# Functions\n\n"); + } + self.funcs += 1; + + self.src.push_str("----\n\n"); + self.src.push_str(&format!( + "#### `", + func.name.to_snake_case() + )); + self.hrefs + .insert(func.name.clone(), format!("#{}", func.name.to_snake_case())); + self.src.push_str(&func.name); + self.src.push_str("` "); + self.src.push_str("\n\n"); + self.docs(&func.docs); + + if func.params.len() > 0 { + self.src.push_str("##### Params\n\n"); + for (name, ty) in func.params.iter() { + self.src.push_str(&format!( + "- `{}`: ", + name, + f = func.name.to_snake_case(), + p = name.to_snake_case(), + )); + self.print_ty(iface, ty, false); + self.src.push_str("\n"); + } + } + match &func.result { + Type::Unit => {} + ty => { + self.src.push_str("##### Results\n\n"); + self.src.push_str(&format!( + "- `{}`: ", + "result", + f = func.name.to_snake_case(), + p = "result", + )); + self.print_ty(iface, ty, false); + self.src.push_str("\n"); + } + } + + self.src.push_str("\n"); + } + + fn export(&mut self, iface: &Interface, func: &Function) { + self.import(iface, func); + } + + fn finish_one(&mut self, _iface: &Interface, files: &mut Files) { + let parser = Parser::new(&self.src); + let mut events = Vec::new(); + for event in parser { + if let Event::Code(code) = &event { + if let Some(dst) = self.hrefs.get(code.as_ref()) { + let tag = Tag::Link(LinkType::Inline, dst.as_str().into(), "".into()); + events.push(Event::Start(tag.clone())); + events.push(event.clone()); + events.push(Event::End(tag)); + continue; + } + } + events.push(event); + } + let mut html_output = String::new(); + html::push_html(&mut html_output, events.into_iter()); + + files.push("bindings.md", self.src.as_bytes()); + files.push("bindings.html", html_output.as_bytes()); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/Cargo.toml new file mode 100644 index 0000000..4eea70d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/Cargo.toml @@ -0,0 +1,19 @@ +[package] +name = "wit-bindgen-gen-rust-wasm" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +test = false +doctest = false + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' } +wit-bindgen-gen-rust = { path = '../gen-rust', version = '0.1.0' } +heck = "0.3" +structopt = { version = "0.3", default-features = false, optional = true } + +[dev-dependencies] +wit-bindgen-rust = { path = '../rust-wasm' } +test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-rust-wasm'] } diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/build.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/build.rs new file mode 100644 index 0000000..d7c7fc2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + // this build script is currently only here so OUT_DIR is set for testing. +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/src/lib.rs new file mode 100644 index 0000000..d3c7a67 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/src/lib.rs @@ -0,0 +1,1691 @@ +use heck::*; +use std::collections::BTreeMap; +use std::io::{Read, Write}; +use std::mem; +use std::process::{Command, Stdio}; +use wit_bindgen_gen_core::wit_parser::abi::{ + AbiVariant, Bindgen, Instruction, LiftLower, WasmType, +}; +use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator, Source, TypeInfo, Types}; +use wit_bindgen_gen_rust::{ + int_repr, wasm_type, FnSig, RustFlagsRepr, RustFunctionGenerator, RustGenerator, TypeMode, +}; + +#[derive(Default)] +pub struct RustWasm { + src: Source, + opts: Opts, + types: Types, + in_import: bool, + traits: BTreeMap, + in_trait: bool, + trait_name: String, + return_pointer_area_size: usize, + return_pointer_area_align: usize, + sizes: SizeAlign, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + /// Whether or not `rustfmt` is executed to format generated code. + #[cfg_attr(feature = "structopt", structopt(long))] + pub rustfmt: bool, + + /// Adds the wit module name into import binding names when enabled. + #[cfg_attr(feature = "structopt", structopt(long))] + pub multi_module: bool, + + /// Whether or not the bindings assume interface values are always + /// well-formed or whether checks are performed. + #[cfg_attr(feature = "structopt", structopt(long))] + pub unchecked: bool, + + /// A prefix to prepend to all exported symbols. Note that this is only + /// intended for testing because it breaks the general form of the ABI. + #[cfg_attr(feature = "structopt", structopt(skip))] + pub symbol_namespace: String, + + /// If true, the code generation is intended for standalone crates. + /// + /// Standalone mode generates bindings without a wrapping module. + /// + /// For exported interfaces, an `export!` macro is also generated + /// that can be used to export an implementation from a different + /// crate. + #[cfg_attr(feature = "structopt", structopt(skip))] + pub standalone: bool, +} + +#[derive(Default)] +struct Trait { + methods: Vec, + resource_methods: BTreeMap>, +} + +impl Opts { + pub fn build(self) -> RustWasm { + let mut r = RustWasm::new(); + r.opts = self; + r + } +} + +impl RustWasm { + pub fn new() -> RustWasm { + RustWasm::default() + } + + fn abi_variant(dir: Direction) -> AbiVariant { + // This generator uses the obvious direction to ABI variant mapping. + match dir { + Direction::Export => AbiVariant::GuestExport, + Direction::Import => AbiVariant::GuestImport, + } + } + + fn ret_area_name(iface: &Interface) -> String { + format!("{}_RET_AREA", iface.name.to_shouty_snake_case()) + } +} + +impl RustGenerator for RustWasm { + fn default_param_mode(&self) -> TypeMode { + if self.in_import { + // We default to borrowing as much as possible to maximize the ability + // for host to take views into our memory without forcing wasm modules + // to allocate anything. + TypeMode::AllBorrowed("'a") + } else { + // In exports everythig is always owned, slices and handles and all. + // Nothing is borrowed. + TypeMode::Owned + } + } + + fn handle_projection(&self) -> Option<(&'static str, String)> { + None + } + + fn handle_in_super(&self) -> bool { + !self.in_import + } + + fn handle_wrapper(&self) -> Option<&'static str> { + if self.in_import { + None + } else { + Some("wit_bindgen_rust::Handle") + } + } + + fn push_str(&mut self, s: &str) { + self.src.push_str(s); + } + + fn info(&self, ty: TypeId) -> TypeInfo { + self.types.get(ty) + } + + fn types_mut(&mut self) -> &mut Types { + &mut self.types + } + + fn print_borrowed_slice( + &mut self, + iface: &Interface, + mutbl: bool, + ty: &Type, + lifetime: &'static str, + ) { + self.print_rust_slice(iface, mutbl, ty, lifetime); + } + + fn print_borrowed_str(&mut self, lifetime: &'static str) { + self.push_str("&"); + if lifetime != "'_" { + self.push_str(lifetime); + self.push_str(" "); + } + self.push_str(" str"); + } +} + +impl Generator for RustWasm { + fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + let variant = Self::abi_variant(dir); + self.in_import = variant == AbiVariant::GuestImport; + self.types.analyze(iface); + self.trait_name = iface.name.to_camel_case(); + + if !self.opts.standalone { + self.src.push_str(&format!( + "#[allow(clippy::all)]\nmod {} {{\n", + iface.name.to_snake_case(), + )); + } + + self.sizes.fill(iface); + } + + fn type_record( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + record: &Record, + docs: &Docs, + ) { + self.print_typedef_record(iface, id, record, docs); + } + + fn type_tuple( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + tuple: &Tuple, + docs: &Docs, + ) { + self.print_typedef_tuple(iface, id, tuple, docs); + } + + fn type_flags( + &mut self, + _iface: &Interface, + _id: TypeId, + name: &str, + flags: &Flags, + docs: &Docs, + ) { + self.src + .push_str("wit_bindgen_rust::bitflags::bitflags! {\n"); + self.rustdoc(docs); + let repr = RustFlagsRepr::new(flags); + self.src + .push_str(&format!("pub struct {}: {repr} {{\n", name.to_camel_case(),)); + for (i, flag) in flags.flags.iter().enumerate() { + self.rustdoc(&flag.docs); + self.src.push_str(&format!( + "const {} = 1 << {};\n", + flag.name.to_shouty_snake_case(), + i, + )); + } + self.src.push_str("}\n"); + self.src.push_str("}\n"); + + // Add a `from_bits_preserve` method. + self.src + .push_str(&format!("impl {} {{\n", name.to_camel_case())); + self.src.push_str(&format!( + " /// Convert from a raw integer, preserving any unknown bits. See\n" + )); + self.src.push_str(&format!( + " /// \n" + )); + self.src.push_str(&format!( + " pub fn from_bits_preserve(bits: {repr}) -> Self {{\n", + )); + self.src.push_str(&format!(" Self {{ bits }}\n")); + self.src.push_str(&format!(" }}\n")); + self.src.push_str(&format!("}}\n")); + } + + fn type_variant( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + variant: &Variant, + docs: &Docs, + ) { + self.print_typedef_variant(iface, id, variant, docs); + } + + fn type_union( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + union: &Union, + docs: &Docs, + ) { + self.print_typedef_union(iface, id, union, docs); + } + + fn type_option( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + payload: &Type, + docs: &Docs, + ) { + self.print_typedef_option(iface, id, payload, docs); + } + + fn type_expected( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + expected: &Expected, + docs: &Docs, + ) { + self.print_typedef_expected(iface, id, expected, docs); + } + + fn type_enum(&mut self, _iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + self.print_typedef_enum(id, name, enum_, docs); + } + + fn type_resource(&mut self, iface: &Interface, ty: ResourceId) { + // For exported handles we synthesize some trait implementations + // automatically for runtime-required traits. + if !self.in_import { + let panic = " + #[cfg(not(target_arch = \"wasm32\"))] + { + panic!(\"handles can only be used on wasm32\"); + } + #[cfg(target_arch = \"wasm32\")] + "; + self.src.push_str(&format!( + " + unsafe impl wit_bindgen_rust::HandleType for super::{ty} {{ + #[inline] + fn clone(_val: i32) -> i32 {{ + {panic_not_wasm} + {{ + #[link(wasm_import_module = \"canonical_abi\")] + extern \"C\" {{ + #[link_name = \"resource_clone_{name}\"] + fn clone(val: i32) -> i32; + }} + unsafe {{ clone(_val) }} + }} + }} + + #[inline] + fn drop(_val: i32) {{ + {panic_not_wasm} + {{ + #[link(wasm_import_module = \"canonical_abi\")] + extern \"C\" {{ + #[link_name = \"resource_drop_{name}\"] + fn drop(val: i32); + }} + unsafe {{ drop(_val) }} + }} + }} + }} + + unsafe impl wit_bindgen_rust::LocalHandle for super::{ty} {{ + #[inline] + fn new(_val: i32) -> i32 {{ + {panic_not_wasm} + {{ + #[link(wasm_import_module = \"canonical_abi\")] + extern \"C\" {{ + #[link_name = \"resource_new_{name}\"] + fn new(val: i32) -> i32; + }} + unsafe {{ new(_val) }} + }} + }} + + #[inline] + fn get(_val: i32) -> i32 {{ + {panic_not_wasm} + {{ + #[link(wasm_import_module = \"canonical_abi\")] + extern \"C\" {{ + #[link_name = \"resource_get_{name}\"] + fn get(val: i32) -> i32; + }} + unsafe {{ get(_val) }} + }} + }} + }} + + const _: () = {{ + #[export_name = \"{ns}canonical_abi_drop_{name}\"] + extern \"C\" fn drop(ty: Box) {{ + ::drop_{name_snake}(*ty) + }} + }}; + ", + ty = iface.resources[ty].name.to_camel_case(), + name = iface.resources[ty].name, + name_snake = iface.resources[ty].name.to_snake_case(), + iface = iface.name.to_camel_case(), + ns = self.opts.symbol_namespace, + panic_not_wasm = panic, + )); + let trait_ = self + .traits + .entry(iface.name.to_camel_case()) + .or_insert(Trait::default()); + trait_.methods.push(format!( + " + /// An optional callback invoked when a handle is finalized + /// and destroyed. + fn drop_{}(val: super::{}) {{ + drop(val); + }} + ", + iface.resources[ty].name.to_snake_case(), + iface.resources[ty].name.to_camel_case(), + )); + return; + } + + let resource = &iface.resources[ty]; + let name = &resource.name; + + self.rustdoc(&resource.docs); + self.src.push_str("#[derive(Debug)]\n"); + self.src.push_str("#[repr(transparent)]\n"); + self.src + .push_str(&format!("pub struct {}(i32);\n", name.to_camel_case())); + self.src.push_str("impl "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str( + " { + pub unsafe fn from_raw(raw: i32) -> Self { + Self(raw) + } + + pub fn into_raw(self) -> i32 { + let ret = self.0; + core::mem::forget(self); + return ret; + } + + pub fn as_raw(&self) -> i32 { + self.0 + } + }\n", + ); + + self.src.push_str("impl Drop for "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str(&format!( + "{{ + fn drop(&mut self) {{ + #[link(wasm_import_module = \"canonical_abi\")] + extern \"C\" {{ + #[link_name = \"resource_drop_{}\"] + fn close(fd: i32); + }} + unsafe {{ + close(self.0); + }} + }} + }}\n", + name, + )); + + self.src.push_str("impl Clone for "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str(&format!( + "{{ + fn clone(&self) -> Self {{ + #[link(wasm_import_module = \"canonical_abi\")] + extern \"C\" {{ + #[link_name = \"resource_clone_{}\"] + fn clone(val: i32) -> i32; + }} + unsafe {{ + Self(clone(self.0)) + }} + }} + }}\n", + name, + )); + } + + fn type_alias(&mut self, iface: &Interface, id: TypeId, _name: &str, ty: &Type, docs: &Docs) { + self.print_typedef_alias(iface, id, ty, docs); + } + + fn type_list(&mut self, iface: &Interface, id: TypeId, _name: &str, ty: &Type, docs: &Docs) { + self.print_type_list(iface, id, ty, docs); + } + + fn type_builtin(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.rustdoc(docs); + self.src + .push_str(&format!("pub type {}", name.to_camel_case())); + self.src.push_str(" = "); + self.print_ty(iface, ty, TypeMode::Owned); + self.src.push_str(";\n"); + } + + fn preprocess_functions(&mut self, _iface: &Interface, dir: Direction) { + if self.opts.standalone && dir == Direction::Export { + self.src.push_str( + "/// Declares the export of the interface for the given type.\n\ + #[macro_export]\n\ + macro_rules! export(($t:ident) => {\n", + ); + } + } + + fn import(&mut self, iface: &Interface, func: &Function) { + let mut sig = FnSig::default(); + let param_mode = TypeMode::AllBorrowed("'_"); + sig.async_ = func.is_async; + match &func.kind { + FunctionKind::Freestanding => {} + FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => { + sig.use_item_name = true; + self.src.push_str(&format!( + "impl {} {{\n", + iface.resources[*resource].name.to_camel_case() + )); + } + } + if let FunctionKind::Method { .. } = func.kind { + sig.self_arg = Some("&self".to_string()); + sig.self_is_first_param = true; + } + let params = self.print_signature(iface, func, param_mode, &sig); + self.src.push_str("{\n"); + self.src.push_str("unsafe {\n"); + + let mut f = FunctionBindgen::new(self, params); + iface.call( + AbiVariant::GuestImport, + LiftLower::LowerArgsLiftResults, + func, + &mut f, + ); + let FunctionBindgen { + needs_cleanup_list, + src, + .. + } = f; + + if needs_cleanup_list { + self.src.push_str("let mut cleanup_list = Vec::new();\n"); + } + self.src.push_str(&String::from(src)); + + self.src.push_str("}\n"); + self.src.push_str("}\n"); + + match &func.kind { + FunctionKind::Freestanding => {} + FunctionKind::Static { .. } | FunctionKind::Method { .. } => { + self.src.push_str("}\n"); + } + } + } + + fn export(&mut self, iface: &Interface, func: &Function) { + let iface_name = iface.name.to_snake_case(); + + self.src.push_str("#[export_name = \""); + match &iface.module { + Some(module) => { + self.src.push_str(module); + self.src.push_str("#"); + self.src.push_str(&func.name); + } + None => { + self.src.push_str(&self.opts.symbol_namespace); + self.src.push_str(&func.name); + } + } + self.src.push_str("\"]\n"); + self.src.push_str("unsafe extern \"C\" fn __wit_bindgen_"); + self.src.push_str(&iface_name); + self.src.push_str("_"); + self.src.push_str(&func.name.to_snake_case()); + self.src.push_str("("); + let sig = iface.wasm_signature(AbiVariant::GuestExport, func); + let mut params = Vec::new(); + for (i, param) in sig.params.iter().enumerate() { + let name = format!("arg{}", i); + self.src.push_str(&name); + self.src.push_str(": "); + self.wasm_type(*param); + self.src.push_str(", "); + params.push(name); + } + self.src.push_str(")"); + + match sig.results.len() { + 0 => {} + 1 => { + self.src.push_str(" -> "); + self.wasm_type(sig.results[0]); + } + _ => unimplemented!(), + } + + self.push_str("{\n"); + + if self.opts.standalone { + // Force the macro code to reference wit_bindgen_rust for standalone crates. + // Also ensure any referenced types are also used from the external crate. + self.src + .push_str("#[allow(unused_imports)]\nuse wit_bindgen_rust;\nuse "); + self.src.push_str(&iface_name); + self.src.push_str("::*;\n"); + } + + if func.is_async { + self.src.push_str("let future = async move {\n"); + } + + let mut f = FunctionBindgen::new(self, params); + iface.call( + AbiVariant::GuestExport, + LiftLower::LiftArgsLowerResults, + func, + &mut f, + ); + let FunctionBindgen { + needs_cleanup_list, + src, + .. + } = f; + assert!(!needs_cleanup_list); + self.src.push_str(&String::from(src)); + if func.is_async { + self.src.push_str("};\n"); + self.src + .push_str("wit_bindgen_rust::rt::execute(Box::pin(future));\n"); + } + self.src.push_str("}\n"); + + let prev = mem::take(&mut self.src); + self.in_trait = true; + let mut sig = FnSig::default(); + sig.private = true; + sig.async_ = func.is_async; + match &func.kind { + FunctionKind::Freestanding => {} + FunctionKind::Static { .. } => sig.use_item_name = true, + FunctionKind::Method { .. } => { + sig.use_item_name = true; + sig.self_is_first_param = true; + sig.self_arg = Some("&self".to_string()); + } + } + self.print_signature(iface, func, TypeMode::Owned, &sig); + self.src.push_str(";"); + self.in_trait = false; + let trait_ = self + .traits + .entry(iface.name.to_camel_case()) + .or_insert(Trait::default()); + let dst = match &func.kind { + FunctionKind::Freestanding => &mut trait_.methods, + FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => trait_ + .resource_methods + .entry(*resource) + .or_insert(Vec::new()), + }; + dst.push(mem::replace(&mut self.src, prev).into()); + } + + fn finish_functions(&mut self, iface: &Interface, dir: Direction) { + if self.return_pointer_area_align > 0 { + self.src.push_str(&format!( + " + #[repr(align({align}))] + struct RetArea([u8; {size}]); + static mut {name}: RetArea = RetArea([0; {size}]); + ", + name = Self::ret_area_name(iface), + align = self.return_pointer_area_align, + size = self.return_pointer_area_size, + )); + } + + // For standalone generation, close the export! macro + if self.opts.standalone && dir == Direction::Export { + self.src.push_str("});\n"); + } + } + + fn finish_one(&mut self, iface: &Interface, files: &mut Files) { + let mut src = mem::take(&mut self.src); + + let any_async = iface.functions.iter().any(|f| f.is_async); + for (name, trait_) in self.traits.iter() { + if any_async { + src.push_str("#[wit_bindgen_rust::async_trait(?Send)]\n"); + } + src.push_str("pub trait "); + src.push_str(&name); + src.push_str(" {\n"); + for f in trait_.methods.iter() { + src.push_str(&f); + src.push_str("\n"); + } + src.push_str("}\n"); + + for (id, methods) in trait_.resource_methods.iter() { + if any_async { + src.push_str("#[wit_bindgen_rust::async_trait(?Send)]\n"); + } + src.push_str(&format!( + "pub trait {} {{\n", + iface.resources[*id].name.to_camel_case() + )); + for f in methods { + src.push_str(&f); + src.push_str("\n"); + } + src.push_str("}\n"); + } + } + + // Close the opening `mod`. + if !self.opts.standalone { + src.push_str("}\n"); + } + + if self.opts.rustfmt { + let mut child = Command::new("rustfmt") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to spawn `rustfmt`"); + child + .stdin + .take() + .unwrap() + .write_all(src.as_bytes()) + .unwrap(); + src.as_mut_string().truncate(0); + child + .stdout + .take() + .unwrap() + .read_to_string(src.as_mut_string()) + .unwrap(); + let status = child.wait().unwrap(); + assert!(status.success()); + } + + files.push("bindings.rs", src.as_bytes()); + } +} + +struct FunctionBindgen<'a> { + gen: &'a mut RustWasm, + params: Vec, + src: Source, + blocks: Vec, + block_storage: Vec<(Source, Vec<(String, String)>)>, + tmp: usize, + needs_cleanup_list: bool, + cleanup: Vec<(String, String)>, +} + +impl FunctionBindgen<'_> { + fn new(gen: &mut RustWasm, params: Vec) -> FunctionBindgen<'_> { + FunctionBindgen { + gen, + params, + src: Default::default(), + blocks: Vec::new(), + block_storage: Vec::new(), + tmp: 0, + needs_cleanup_list: false, + cleanup: Vec::new(), + } + } + + fn emit_cleanup(&mut self) { + for (ptr, layout) in mem::take(&mut self.cleanup) { + self.push_str(&format!("std::alloc::dealloc({}, {});\n", ptr, layout)); + } + if self.needs_cleanup_list { + self.push_str( + "for (ptr, layout) in cleanup_list { + std::alloc::dealloc(ptr, layout); + }\n", + ); + } + } + + fn declare_import( + &mut self, + iface: &Interface, + name: &str, + params: &[WasmType], + results: &[WasmType], + ) -> String { + let module = iface.module.as_deref().unwrap_or(&iface.name); + + // Define the actual function we're calling inline + self.push_str("#[link(wasm_import_module = \""); + self.push_str(module); + self.push_str("\")]\n"); + self.push_str("extern \"C\" {\n"); + self.push_str("#[cfg_attr(target_arch = \"wasm32\", link_name = \""); + self.push_str(name); + self.push_str("\")]\n"); + self.push_str("#[cfg_attr(not(target_arch = \"wasm32\"), link_name = \""); + self.push_str(module); + self.push_str("_"); + self.push_str(name); + self.push_str("\")]\n"); + self.push_str("fn wit_import("); + for param in params.iter() { + self.push_str("_: "); + self.push_str(wasm_type(*param)); + self.push_str(", "); + } + self.push_str(")"); + assert!(results.len() < 2); + for result in results.iter() { + self.push_str(" -> "); + self.push_str(wasm_type(*result)); + } + self.push_str(";\n}\n"); + "wit_import".to_string() + } +} + +impl RustFunctionGenerator for FunctionBindgen<'_> { + fn push_str(&mut self, s: &str) { + self.src.push_str(s); + } + + fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + ret + } + + fn rust_gen(&self) -> &dyn RustGenerator { + self.gen + } + + fn lift_lower(&self) -> LiftLower { + if self.gen.in_import { + LiftLower::LowerArgsLiftResults + } else { + LiftLower::LiftArgsLowerResults + } + } +} + +impl Bindgen for FunctionBindgen<'_> { + type Operand = String; + + fn push_block(&mut self) { + let prev_src = mem::take(&mut self.src); + let prev_cleanup = mem::take(&mut self.cleanup); + self.block_storage.push((prev_src, prev_cleanup)); + } + + fn finish_block(&mut self, operands: &mut Vec) { + if self.cleanup.len() > 0 { + self.needs_cleanup_list = true; + self.push_str("cleanup_list.extend_from_slice(&["); + for (ptr, layout) in mem::take(&mut self.cleanup) { + self.push_str("("); + self.push_str(&ptr); + self.push_str(", "); + self.push_str(&layout); + self.push_str("),"); + } + self.push_str("]);\n"); + } + let (prev_src, prev_cleanup) = self.block_storage.pop().unwrap(); + let src = mem::replace(&mut self.src, prev_src); + self.cleanup = prev_cleanup; + let expr = match operands.len() { + 0 => "()".to_string(), + 1 => operands[0].clone(), + _ => format!("({})", operands.join(", ")), + }; + if src.is_empty() { + self.blocks.push(expr); + } else if operands.is_empty() { + self.blocks.push(format!("{{\n{}\n}}", &src[..])); + } else { + self.blocks.push(format!("{{\n{}\n{}\n}}", &src[..], expr)); + } + } + + fn return_pointer(&mut self, iface: &Interface, size: usize, align: usize) -> String { + self.gen.return_pointer_area_size = self.gen.return_pointer_area_size.max(size); + self.gen.return_pointer_area_align = self.gen.return_pointer_area_align.max(align); + let tmp = self.tmp(); + + self.push_str(&format!( + "let ptr{} = {}.0.as_mut_ptr() as i32;\n", + tmp, + RustWasm::ret_area_name(iface), + )); + format!("ptr{}", tmp) + } + + fn sizes(&self) -> &SizeAlign { + &self.gen.sizes + } + + fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { + iface.all_bits_valid(ty) + } + + fn emit( + &mut self, + iface: &Interface, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + let unchecked = self.gen.opts.unchecked; + let mut top_as = |cvt: &str| { + let mut s = operands.pop().unwrap(); + s.push_str(" as "); + s.push_str(cvt); + results.push(s); + }; + + match inst { + Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), + Instruction::I32Const { val } => results.push(format!("{}i32", val)), + Instruction::ConstZero { tys } => { + for ty in tys.iter() { + match ty { + WasmType::I32 => results.push("0i32".to_string()), + WasmType::I64 => results.push("0i64".to_string()), + WasmType::F32 => results.push("0.0f32".to_string()), + WasmType::F64 => results.push("0.0f64".to_string()), + } + } + } + + Instruction::I64FromU64 | Instruction::I64FromS64 => { + let s = operands.pop().unwrap(); + results.push(format!("wit_bindgen_rust::rt::as_i64({})", s)); + } + Instruction::I32FromChar + | Instruction::I32FromU8 + | Instruction::I32FromS8 + | Instruction::I32FromU16 + | Instruction::I32FromS16 + | Instruction::I32FromU32 + | Instruction::I32FromS32 => { + let s = operands.pop().unwrap(); + results.push(format!("wit_bindgen_rust::rt::as_i32({})", s)); + } + + Instruction::F32FromFloat32 => { + let s = operands.pop().unwrap(); + results.push(format!("wit_bindgen_rust::rt::as_f32({})", s)); + } + Instruction::F64FromFloat64 => { + let s = operands.pop().unwrap(); + results.push(format!("wit_bindgen_rust::rt::as_f64({})", s)); + } + Instruction::Float32FromF32 + | Instruction::Float64FromF64 + | Instruction::S32FromI32 + | Instruction::S64FromI64 => { + results.push(operands.pop().unwrap()); + } + Instruction::S8FromI32 => top_as("i8"), + Instruction::U8FromI32 => top_as("u8"), + Instruction::S16FromI32 => top_as("i16"), + Instruction::U16FromI32 => top_as("u16"), + Instruction::U32FromI32 => top_as("u32"), + Instruction::U64FromI64 => top_as("u64"), + Instruction::CharFromI32 => { + if unchecked { + results.push(format!( + "core::char::from_u32_unchecked({} as u32)", + operands[0] + )); + } else { + results.push(format!( + "core::char::from_u32({} as u32).unwrap()", + operands[0] + )); + } + } + + Instruction::Bitcasts { casts } => { + wit_bindgen_gen_rust::bitcast(casts, operands, results) + } + + Instruction::UnitLower => { + self.push_str(&format!("let () = {};\n", operands[0])); + } + Instruction::UnitLift => { + results.push("()".to_string()); + } + + Instruction::I32FromBool => { + results.push(format!("match {} {{ true => 1, false => 0 }}", operands[0])); + } + Instruction::BoolFromI32 => { + if unchecked { + results.push(format!( + "core::mem::transmute::({} as u8)", + operands[0], + )); + } else { + results.push(format!( + "match {} {{ + 0 => false, + 1 => true, + _ => panic!(\"invalid bool discriminant\"), + }}", + operands[0], + )); + } + } + + // handles in exports + Instruction::I32FromOwnedHandle { .. } => { + results.push(format!( + "wit_bindgen_rust::Handle::into_raw({})", + operands[0] + )); + } + Instruction::HandleBorrowedFromI32 { .. } => { + results.push(format!( + "wit_bindgen_rust::Handle::from_raw({})", + operands[0], + )); + } + + // handles in imports + Instruction::I32FromBorrowedHandle { .. } => { + results.push(format!("{}.0", operands[0])); + } + Instruction::HandleOwnedFromI32 { ty } => { + results.push(format!( + "{}({})", + iface.resources[*ty].name.to_camel_case(), + operands[0] + )); + } + + Instruction::FlagsLower { flags, .. } => { + let tmp = self.tmp(); + self.push_str(&format!("let flags{} = {};\n", tmp, operands[0])); + for i in 0..flags.repr().count() { + results.push(format!("(flags{}.bits() >> {}) as i32", tmp, i * 32)); + } + } + Instruction::FlagsLift { name, flags, .. } => { + let repr = RustFlagsRepr::new(flags); + let name = name.to_camel_case(); + let mut result = format!("{}::empty()", name); + for (i, op) in operands.iter().enumerate() { + result.push_str(&format!( + " | {}::from_bits_preserve((({} as {repr}) << {}) as _)", + name, + op, + i * 32 + )); + } + results.push(result); + } + + Instruction::RecordLower { ty, record, .. } => { + self.record_lower(iface, *ty, record, &operands[0], results); + } + Instruction::RecordLift { ty, record, .. } => { + self.record_lift(iface, *ty, record, operands, results); + } + + Instruction::TupleLower { tuple, .. } => { + self.tuple_lower(tuple, &operands[0], results); + } + Instruction::TupleLift { .. } => { + self.tuple_lift(operands, results); + } + + Instruction::VariantPayloadName => results.push("e".to_string()), + + Instruction::VariantLower { + variant, + results: result_types, + ty, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + self.let_results(result_types.len(), results); + let op0 = &operands[0]; + self.push_str(&format!("match {op0} {{\n")); + let name = self.typename_lower(iface, *ty); + for (case, block) in variant.cases.iter().zip(blocks) { + let case_name = case.name.to_camel_case(); + self.push_str(&format!("{name}::{case_name}")); + if case.ty == Type::Unit { + self.push_str(&format!(" => {{\nlet e = ();\n{block}\n}}\n")); + } else { + self.push_str(&format!("(e) => {block},\n")); + } + } + self.push_str("};\n"); + } + + // In unchecked mode when this type is a named enum then we know we + // defined the type so we can transmute directly into it. + Instruction::VariantLift { name, variant, .. } + if variant.cases.iter().all(|c| c.ty == Type::Unit) && unchecked => + { + self.blocks.drain(self.blocks.len() - variant.cases.len()..); + let mut result = format!("core::mem::transmute::<_, "); + result.push_str(&name.to_camel_case()); + result.push_str(">("); + result.push_str(&operands[0]); + result.push_str(" as "); + result.push_str(int_repr(variant.tag())); + result.push_str(")"); + results.push(result); + } + + Instruction::VariantLift { variant, ty, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + let op0 = &operands[0]; + let mut result = format!("match {op0} {{\n"); + let name = self.typename_lift(iface, *ty); + for (i, (case, block)) in variant.cases.iter().zip(blocks).enumerate() { + let pat = if i == variant.cases.len() - 1 && unchecked { + String::from("_") + } else { + i.to_string() + }; + let block = if case.ty != Type::Unit { + format!("({block})") + } else { + String::new() + }; + let case = case.name.to_camel_case(); + result.push_str(&format!("{pat} => {name}::{case}{block},\n")); + } + if !unchecked { + result.push_str("_ => panic!(\"invalid enum discriminant\"),\n"); + } + result.push_str("}"); + results.push(result); + } + + Instruction::UnionLower { + union, + results: result_types, + ty, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + self.let_results(result_types.len(), results); + let op0 = &operands[0]; + self.push_str(&format!("match {op0} {{\n")); + let name = self.typename_lower(iface, *ty); + for (case_name, block) in self + .gen + .union_case_names(iface, union) + .into_iter() + .zip(blocks) + { + self.push_str(&format!("{name}::{case_name}(e) => {block},\n")); + } + self.push_str("};\n"); + } + + Instruction::UnionLift { union, ty, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + let op0 = &operands[0]; + let mut result = format!("match {op0} {{\n"); + for (i, (case_name, block)) in self + .gen + .union_case_names(iface, union) + .into_iter() + .zip(blocks) + .enumerate() + { + let pat = if i == union.cases.len() - 1 && unchecked { + String::from("_") + } else { + i.to_string() + }; + let name = self.typename_lift(iface, *ty); + result.push_str(&format!("{pat} => {name}::{case_name}({block}),\n")); + } + if !unchecked { + result.push_str("_ => panic!(\"invalid union discriminant\"),\n"); + } + result.push_str("}"); + results.push(result); + } + + Instruction::OptionLower { + results: result_types, + .. + } => { + let some = self.blocks.pop().unwrap(); + let none = self.blocks.pop().unwrap(); + self.let_results(result_types.len(), results); + let operand = &operands[0]; + self.push_str(&format!( + "match {operand} {{ + Some(e) => {some}, + None => {{\nlet e = ();\n{none}\n}}, + }};" + )); + } + + Instruction::OptionLift { .. } => { + let some = self.blocks.pop().unwrap(); + let none = self.blocks.pop().unwrap(); + assert_eq!(none, "()"); + let operand = &operands[0]; + let invalid = if unchecked { + "std::hint::unreachable_unchecked()" + } else { + "panic!(\"invalid enum discriminant\")" + }; + results.push(format!( + "match {operand} {{ + 0 => None, + 1 => Some({some}), + _ => {invalid}, + }}" + )); + } + + Instruction::ExpectedLower { + results: result_types, + .. + } => { + let err = self.blocks.pop().unwrap(); + let ok = self.blocks.pop().unwrap(); + self.let_results(result_types.len(), results); + let operand = &operands[0]; + self.push_str(&format!( + "match {operand} {{ + Ok(e) => {{ {ok} }}, + Err(e) => {{ {err} }}, + }};" + )); + } + + Instruction::ExpectedLift { .. } => { + let err = self.blocks.pop().unwrap(); + let ok = self.blocks.pop().unwrap(); + let operand = &operands[0]; + let invalid = if unchecked { + "std::hint::unreachable_unchecked()" + } else { + "panic!(\"invalid enum discriminant\")" + }; + results.push(format!( + "match {operand} {{ + 0 => Ok({ok}), + 1 => Err({err}), + _ => {invalid}, + }}" + )); + } + + Instruction::EnumLower { enum_, name, .. } => { + let mut result = format!("match {} {{\n", operands[0]); + let name = name.to_camel_case(); + for (i, case) in enum_.cases.iter().enumerate() { + let case = case.name.to_camel_case(); + result.push_str(&format!("{name}::{case} => {i},\n")); + } + result.push_str("}"); + results.push(result); + } + + // In unchecked mode when this type is a named enum then we know we + // defined the type so we can transmute directly into it. + Instruction::EnumLift { enum_, name, .. } if unchecked => { + let mut result = format!("core::mem::transmute::<_, "); + result.push_str(&name.to_camel_case()); + result.push_str(">("); + result.push_str(&operands[0]); + result.push_str(" as "); + result.push_str(int_repr(enum_.tag())); + result.push_str(")"); + results.push(result); + } + + Instruction::EnumLift { enum_, name, .. } => { + let mut result = format!("match "); + result.push_str(&operands[0]); + result.push_str(" {\n"); + let name = name.to_camel_case(); + for (i, case) in enum_.cases.iter().enumerate() { + let case = case.name.to_camel_case(); + result.push_str(&format!("{i} => {name}::{case},\n")); + } + result.push_str("_ => panic!(\"invalid enum discriminant\"),\n"); + result.push_str("}"); + results.push(result); + } + + Instruction::ListCanonLower { realloc, .. } => { + let tmp = self.tmp(); + let val = format!("vec{}", tmp); + let ptr = format!("ptr{}", tmp); + let len = format!("len{}", tmp); + if realloc.is_none() { + self.push_str(&format!("let {} = {};\n", val, operands[0])); + } else { + let op0 = operands.pop().unwrap(); + self.push_str(&format!("let {} = ({}).into_boxed_slice();\n", val, op0)); + } + self.push_str(&format!("let {} = {}.as_ptr() as i32;\n", ptr, val)); + self.push_str(&format!("let {} = {}.len() as i32;\n", len, val)); + if realloc.is_some() { + self.push_str(&format!("core::mem::forget({});\n", val)); + } + results.push(ptr); + results.push(len); + } + + Instruction::ListCanonLift { free, .. } => { + // This only happens when we're receiving a list from the + // outside world, so `free` should always be `Some`. + assert!(free.is_some()); + let tmp = self.tmp(); + let len = format!("len{}", tmp); + self.push_str(&format!("let {} = {} as usize;\n", len, operands[1])); + let result = format!( + "Vec::from_raw_parts({} as *mut _, {1}, {1})", + operands[0], len + ); + results.push(result); + } + + Instruction::StringLower { realloc } => { + let tmp = self.tmp(); + let val = format!("vec{}", tmp); + let ptr = format!("ptr{}", tmp); + let len = format!("len{}", tmp); + if realloc.is_none() { + self.push_str(&format!("let {} = {};\n", val, operands[0])); + } else { + let op0 = format!("{}.into_bytes()", operands[0]); + self.push_str(&format!("let {} = ({}).into_boxed_slice();\n", val, op0)); + } + self.push_str(&format!("let {} = {}.as_ptr() as i32;\n", ptr, val)); + self.push_str(&format!("let {} = {}.len() as i32;\n", len, val)); + if realloc.is_some() { + self.push_str(&format!("core::mem::forget({});\n", val)); + } + results.push(ptr); + results.push(len); + } + + Instruction::StringLift { free, .. } => { + // This only happens when we're receiving a string from the + // outside world, so `free` should always be `Some`. + assert!(free.is_some()); + let tmp = self.tmp(); + let len = format!("len{}", tmp); + self.push_str(&format!("let {} = {} as usize;\n", len, operands[1])); + let result = format!( + "Vec::from_raw_parts({} as *mut _, {1}, {1})", + operands[0], len + ); + if unchecked { + results.push(format!("String::from_utf8_unchecked({})", result)); + } else { + results.push(format!("String::from_utf8({}).unwrap()", result)); + } + } + + Instruction::ListLower { element, realloc } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let vec = format!("vec{}", tmp); + let result = format!("result{}", tmp); + let layout = format!("layout{}", tmp); + let len = format!("len{}", tmp); + self.push_str(&format!("let {} = {};\n", vec, operands[0])); + self.push_str(&format!("let {} = {}.len() as i32;\n", len, vec)); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + self.push_str(&format!( + "let {} = core::alloc::Layout::from_size_align_unchecked({}.len() * {}, {});\n", + layout, vec, size, align, + )); + self.push_str(&format!( + "let {} = std::alloc::alloc({});\n", + result, layout, + )); + self.push_str(&format!( + "if {}.is_null() {{ std::alloc::handle_alloc_error({}); }}\n", + result, layout, + )); + self.push_str(&format!( + "for (i, e) in {}.into_iter().enumerate() {{\n", + vec + )); + self.push_str(&format!( + "let base = {} as i32 + (i as i32) * {};\n", + result, size, + )); + self.push_str(&body); + self.push_str("}\n"); + results.push(format!("{} as i32", result)); + results.push(len); + + if realloc.is_none() { + // If an allocator isn't requested then we must clean up the + // allocation ourselves since our callee isn't taking + // ownership. + self.cleanup.push((result, layout)); + } + } + + Instruction::ListLift { element, free, .. } => { + // This only happens when we're receiving a list from the + // outside world, so `free` should always be `Some`. + assert!(free.is_some()); + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + let len = format!("len{}", tmp); + let base = format!("base{}", tmp); + let result = format!("result{}", tmp); + self.push_str(&format!("let {} = {};\n", base, operands[0])); + self.push_str(&format!("let {} = {};\n", len, operands[1],)); + self.push_str(&format!( + "let mut {} = Vec::with_capacity({} as usize);\n", + result, len, + )); + + self.push_str("for i in 0.."); + self.push_str(&len); + self.push_str(" {\n"); + self.push_str("let base = "); + self.push_str(&base); + self.push_str(" + i *"); + self.push_str(&size.to_string()); + self.push_str(";\n"); + self.push_str(&result); + self.push_str(".push("); + self.push_str(&body); + self.push_str(");\n"); + self.push_str("}\n"); + results.push(result); + self.push_str(&format!( + "std::alloc::dealloc( + {} as *mut _, + std::alloc::Layout::from_size_align_unchecked( + ({} as usize) * {}, + {}, + ), + );\n", + base, len, size, align + )); + } + + Instruction::IterElem { .. } => results.push("e".to_string()), + + Instruction::IterBasePointer => results.push("base".to_string()), + + Instruction::CallWasm { iface, name, sig } => { + let func = self.declare_import(iface, name, &sig.params, &sig.results); + + // ... then call the function with all our operands + if sig.results.len() > 0 { + self.push_str("let ret = "); + results.push("ret".to_string()); + } + self.push_str(&func); + self.push_str("("); + self.push_str(&operands.join(", ")); + self.push_str(");\n"); + } + + Instruction::CallWasmAsyncImport { + iface, + name, + params: wasm_params, + results: wasm_results, + } => { + // The first thing we do here is define the completion callback + // which the host will invoke when the asynchronous call + // actually finishes. This receives our own custom state + // parameter as the first parameter which is the `Sender` + // converted to a `usize`. Afterwards it receives all the + // results which we'll transfer ove the `sender`, the canonical + // ABI of the results. + self.push_str("unsafe extern \"C\" fn completion_callback(sender: usize"); + for (i, result) in wasm_results.iter().enumerate() { + self.push_str(", "); + self.push_str(&format!("ret{}: ", i)); + self.push_str(wasm_type(*result)); + } + self.push_str(") {\n"); + self.push_str("wit_bindgen_rust::rt::Sender::from_usize(sender).send(("); + for i in 0..wasm_results.len() { + self.push_str(&format!("ret{},", i)); + } + self.push_str("));\n"); + self.push_str("}\n"); + + // Next we create the future channel which will be used to track + // the state of this import. The "oneshot" here means that the + // sender (`tx`) will send something once over `rx`. The type of + // the `Oneshot` is the type of the `wasm_results` which is the + // canonical ABI of the results that this function produces. + self.push_str("let (rx, tx) = wit_bindgen_rust::rt::Oneshot::<("); + for ty in *wasm_results { + self.push_str(wasm_type(*ty)); + self.push_str(", "); + } + self.push_str(")>::new();\n"); + + // Then we can actually call the function now that we have + // all the parameters. The first parameters to the import are + // the canonical ABI `operands` we were provided, and the last + // two arguments are our completion callback and the context for + // the callback, our `tx` sender. + let func = self.declare_import(iface, name, wasm_params, &[]); + self.push_str(&func); + self.push_str("("); + for op in operands { + self.push_str(op); + self.push_str(", "); + } + self.push_str("completion_callback as i32, "); + self.push_str("tx.into_usize() as i32"); + self.push_str(");\n"); + + // And finally we want to "appear synchronous" with an async + // function, so we immediately `.await` the results of the + // oneshot. This binds all the canonical ABI results to then get + // translated in further instructions to the result of this + // function call. + let tmp = self.tmp(); + self.push_str("let ("); + for i in 0..wasm_results.len() { + let name = format!("ret{}_{}", tmp, i); + self.push_str(&name); + self.push_str(","); + results.push(name); + } + self.push_str(") = rx.await;\n"); + } + + Instruction::CallWasmAsyncExport { .. } => unreachable!(), + + Instruction::CallInterface { module, func } => { + self.push_str("let result = "); + results.push("result".to_string()); + match &func.kind { + FunctionKind::Freestanding => { + if self.gen.opts.standalone { + // For standalone mode, use the macro identifier + self.push_str(&format!( + "<$t as {t}>::{}", + func.name.to_snake_case(), + t = module.to_camel_case(), + )); + } else { + self.push_str(&format!( + "::{}", + func.name.to_snake_case(), + m = module.to_camel_case() + )); + } + } + FunctionKind::Static { resource, name } + | FunctionKind::Method { resource, name } => { + self.push_str(&format!( + "::{}", + name.to_snake_case(), + r = iface.resources[*resource].name.to_camel_case(), + )); + } + } + self.push_str("("); + if let FunctionKind::Method { .. } = func.kind { + self.push_str("&"); + } + self.push_str(&operands.join(", ")); + self.push_str(")"); + if func.is_async { + self.push_str(".await"); + } + self.push_str(";\n"); + } + + Instruction::Return { amt, .. } => { + self.emit_cleanup(); + match amt { + 0 => {} + 1 => { + self.push_str(&operands[0]); + self.push_str("\n"); + } + _ => { + self.push_str("("); + self.push_str(&operands.join(", ")); + self.push_str(")\n"); + } + } + } + + Instruction::ReturnAsyncExport { .. } => { + self.emit_cleanup(); + self.push_str(&format!( + "wit_bindgen_rust::rt::async_export_done({}, {});\n", + operands[0], operands[1] + )); + } + Instruction::ReturnAsyncImport { .. } => unreachable!(), + + Instruction::I32Load { offset } => { + results.push(format!("*(({} + {}) as *const i32)", operands[0], offset)); + } + Instruction::I32Load8U { offset } => { + results.push(format!( + "i32::from(*(({} + {}) as *const u8))", + operands[0], offset + )); + } + Instruction::I32Load8S { offset } => { + results.push(format!( + "i32::from(*(({} + {}) as *const i8))", + operands[0], offset + )); + } + Instruction::I32Load16U { offset } => { + results.push(format!( + "i32::from(*(({} + {}) as *const u16))", + operands[0], offset + )); + } + Instruction::I32Load16S { offset } => { + results.push(format!( + "i32::from(*(({} + {}) as *const i16))", + operands[0], offset + )); + } + Instruction::I64Load { offset } => { + results.push(format!("*(({} + {}) as *const i64)", operands[0], offset)); + } + Instruction::F32Load { offset } => { + results.push(format!("*(({} + {}) as *const f32)", operands[0], offset)); + } + Instruction::F64Load { offset } => { + results.push(format!("*(({} + {}) as *const f64)", operands[0], offset)); + } + Instruction::I32Store { offset } => { + self.push_str(&format!( + "*(({} + {}) as *mut i32) = {};\n", + operands[1], offset, operands[0] + )); + } + Instruction::I32Store8 { offset } => { + self.push_str(&format!( + "*(({} + {}) as *mut u8) = ({}) as u8;\n", + operands[1], offset, operands[0] + )); + } + Instruction::I32Store16 { offset } => { + self.push_str(&format!( + "*(({} + {}) as *mut u16) = ({}) as u16;\n", + operands[1], offset, operands[0] + )); + } + Instruction::I64Store { offset } => { + self.push_str(&format!( + "*(({} + {}) as *mut i64) = {};\n", + operands[1], offset, operands[0] + )); + } + Instruction::F32Store { offset } => { + self.push_str(&format!( + "*(({} + {}) as *mut f32) = {};\n", + operands[1], offset, operands[0] + )); + } + Instruction::F64Store { offset } => { + self.push_str(&format!( + "*(({} + {}) as *mut f64) = {};\n", + operands[1], offset, operands[0] + )); + } + + Instruction::Malloc { .. } => unimplemented!(), + Instruction::Free { + free: _, + size, + align, + } => { + self.push_str(&format!( + "wit_bindgen_rust::rt::canonical_abi_free({} as *mut u8, {}, {});\n", + operands[0], size, align + )); + } + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/tests/codegen.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/tests/codegen.rs new file mode 100644 index 0000000..ff3a08f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust-wasm/tests/codegen.rs @@ -0,0 +1,32 @@ +#![allow(dead_code, type_alias_bounds)] + +#[test] +fn ok() {} + +#[rustfmt::skip] +mod imports { + test_helpers::codegen_rust_wasm_import!( + "*.wit" + + // If you want to exclude a specific test you can include it here with + // gitignore glob syntax: + // + // "!wasm.wit" + // "!host.wit" + // + // + // Similarly you can also just remove the `*.wit` glob and list tests + // individually if you're debugging. + ); +} + +mod exports { + test_helpers::codegen_rust_wasm_export!( + "*.wit" + + // TODO: these use push/pull buffer which isn't implemented in the test + // generator just yet + "!wasi-next.wit" + "!host.wit" + ); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust/Cargo.toml new file mode 100644 index 0000000..3414c03 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "wit-bindgen-gen-rust" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +doctest = false +test = false + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core' } +heck = "0.3" diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust/src/lib.rs new file mode 100644 index 0000000..970f926 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-rust/src/lib.rs @@ -0,0 +1,1143 @@ +use heck::*; +use std::collections::HashMap; +use std::fmt::{self, Write}; +use std::iter::zip; +use wit_bindgen_gen_core::wit_parser::abi::{Bitcast, LiftLower, WasmType}; +use wit_bindgen_gen_core::{wit_parser::*, TypeInfo, Types}; + +#[derive(Debug, Copy, Clone, PartialEq)] +pub enum TypeMode { + Owned, + AllBorrowed(&'static str), + LeafBorrowed(&'static str), + HandlesBorrowed(&'static str), +} + +pub trait RustGenerator { + fn push_str(&mut self, s: &str); + fn info(&self, ty: TypeId) -> TypeInfo; + fn types_mut(&mut self) -> &mut Types; + fn print_borrowed_slice( + &mut self, + iface: &Interface, + mutbl: bool, + ty: &Type, + lifetime: &'static str, + ); + fn print_borrowed_str(&mut self, lifetime: &'static str); + fn default_param_mode(&self) -> TypeMode; + fn handle_projection(&self) -> Option<(&'static str, String)>; + fn handle_wrapper(&self) -> Option<&'static str>; + fn handle_in_super(&self) -> bool { + false + } + + fn rustdoc(&mut self, docs: &Docs) { + let docs = match &docs.contents { + Some(docs) => docs, + None => return, + }; + for line in docs.trim().lines() { + self.push_str("/// "); + self.push_str(line); + self.push_str("\n"); + } + } + + fn rustdoc_params(&mut self, docs: &[(String, Type)], header: &str) { + drop((docs, header)); + // let docs = docs + // .iter() + // .filter(|param| param.docs.trim().len() > 0) + // .collect::>(); + // if docs.len() == 0 { + // return; + // } + + // self.push_str("///\n"); + // self.push_str("/// ## "); + // self.push_str(header); + // self.push_str("\n"); + // self.push_str("///\n"); + + // for param in docs { + // for (i, line) in param.docs.lines().enumerate() { + // self.push_str("/// "); + // // Currently wasi only has at most one return value, so there's no + // // need to indent it or name it. + // if header != "Return" { + // if i == 0 { + // self.push_str("* `"); + // self.push_str(to_rust_ident(param.name.as_str())); + // self.push_str("` - "); + // } else { + // self.push_str(" "); + // } + // } + // self.push_str(line); + // self.push_str("\n"); + // } + // } + } + + fn print_signature( + &mut self, + iface: &Interface, + func: &Function, + param_mode: TypeMode, + sig: &FnSig, + ) -> Vec { + let params = self.print_docs_and_params(iface, func, param_mode, &sig); + self.push_str(" -> "); + self.print_ty(iface, &func.result, TypeMode::Owned); + params + } + + fn print_docs_and_params( + &mut self, + iface: &Interface, + func: &Function, + param_mode: TypeMode, + sig: &FnSig, + ) -> Vec { + self.rustdoc(&func.docs); + self.rustdoc_params(&func.params, "Parameters"); + // TODO: re-add this when docs are back + // self.rustdoc_params(&func.results, "Return"); + + if !sig.private { + self.push_str("pub "); + } + if sig.unsafe_ { + self.push_str("unsafe "); + } + if sig.async_ { + self.push_str("async "); + } + self.push_str("fn "); + let func_name = if sig.use_item_name { + func.item_name() + } else { + &func.name + }; + self.push_str(&to_rust_ident(&func_name)); + if let Some(generics) = &sig.generics { + self.push_str(generics); + } + self.push_str("("); + if let Some(arg) = &sig.self_arg { + self.push_str(arg); + self.push_str(","); + } + let mut params = Vec::new(); + for (i, (name, param)) in func.params.iter().enumerate() { + if i == 0 && sig.self_is_first_param { + params.push("self".to_string()); + continue; + } + let name = to_rust_ident(name); + self.push_str(&name); + params.push(name); + self.push_str(": "); + self.print_ty(iface, param, param_mode); + self.push_str(","); + } + self.push_str(")"); + params + } + + fn print_ty(&mut self, iface: &Interface, ty: &Type, mode: TypeMode) { + match ty { + Type::Id(t) => self.print_tyid(iface, *t, mode), + Type::Handle(r) => { + let mut info = TypeInfo::default(); + info.has_handle = true; + let lt = self.lifetime_for(&info, mode); + // Borrowed handles are always behind a reference since + // in that case we never take ownership of the handle. + if let Some(lt) = lt { + self.push_str("&"); + if lt != "'_" { + self.push_str(lt); + } + self.push_str(" "); + } + + let suffix = match self.handle_wrapper() { + Some(wrapper) => { + self.push_str(wrapper); + self.push_str("<"); + ">" + } + None => "", + }; + if self.handle_in_super() { + self.push_str("super::"); + } + if let Some((proj, _)) = self.handle_projection() { + self.push_str(proj); + self.push_str("::"); + } + self.push_str(&iface.resources[*r].name.to_camel_case()); + self.push_str(suffix); + } + + Type::Unit => self.push_str("()"), + Type::Bool => self.push_str("bool"), + Type::U8 => self.push_str("u8"), + Type::U16 => self.push_str("u16"), + Type::U32 => self.push_str("u32"), + Type::U64 => self.push_str("u64"), + Type::S8 => self.push_str("i8"), + Type::S16 => self.push_str("i16"), + Type::S32 => self.push_str("i32"), + Type::S64 => self.push_str("i64"), + Type::Float32 => self.push_str("f32"), + Type::Float64 => self.push_str("f64"), + Type::Char => self.push_str("char"), + Type::String => match mode { + TypeMode::AllBorrowed(lt) | TypeMode::LeafBorrowed(lt) => { + self.print_borrowed_str(lt) + } + TypeMode::Owned | TypeMode::HandlesBorrowed(_) => self.push_str("String"), + }, + } + } + + fn print_tyid(&mut self, iface: &Interface, id: TypeId, mode: TypeMode) { + let info = self.info(id); + let lt = self.lifetime_for(&info, mode); + let ty = &iface.types[id]; + if ty.name.is_some() { + let name = if lt.is_some() { + self.param_name(iface, id) + } else { + self.result_name(iface, id) + }; + self.push_str(&name); + + // If the type recursively owns data and it's a + // variant/record/list, then we need to place the + // lifetime parameter on the type as well. + if info.owns_data() && needs_generics(iface, &ty.kind) { + self.print_generics(&info, lt, false); + } + + return; + + fn needs_generics(iface: &Interface, ty: &TypeDefKind) -> bool { + match ty { + TypeDefKind::Variant(_) + | TypeDefKind::Record(_) + | TypeDefKind::Option(_) + | TypeDefKind::Expected(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::List(_) + | TypeDefKind::Flags(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Tuple(_) + | TypeDefKind::Union(_) => true, + TypeDefKind::Type(Type::Id(t)) => needs_generics(iface, &iface.types[*t].kind), + TypeDefKind::Type(Type::String) => true, + TypeDefKind::Type(Type::Handle(_)) => true, + TypeDefKind::Type(_) => false, + } + } + } + + match &ty.kind { + TypeDefKind::List(t) => self.print_list(iface, t, mode), + + TypeDefKind::Option(t) => { + self.push_str("Option<"); + self.print_ty(iface, t, mode); + self.push_str(">"); + } + + TypeDefKind::Expected(e) => { + self.push_str("Result<"); + self.print_ty(iface, &e.ok, mode); + self.push_str(","); + self.print_ty(iface, &e.err, mode); + self.push_str(">"); + } + + TypeDefKind::Variant(_) => panic!("unsupported anonymous variant"), + + // Tuple-like records are mapped directly to Rust tuples of + // types. Note the trailing comma after each member to + // appropriately handle 1-tuples. + TypeDefKind::Tuple(t) => { + self.push_str("("); + for ty in t.types.iter() { + self.print_ty(iface, ty, mode); + self.push_str(","); + } + self.push_str(")"); + } + TypeDefKind::Record(_) => { + panic!("unsupported anonymous type reference: record") + } + TypeDefKind::Flags(_) => { + panic!("unsupported anonymous type reference: flags") + } + TypeDefKind::Enum(_) => { + panic!("unsupported anonymous type reference: enum") + } + TypeDefKind::Union(_) => { + panic!("unsupported anonymous type reference: union") + } + TypeDefKind::Future(_) => { + todo!("unsupported anonymous type reference: future") + } + TypeDefKind::Stream(_) => { + todo!("unsupported anonymous type reference: stream") + } + + TypeDefKind::Type(t) => self.print_ty(iface, t, mode), + } + } + + fn print_list(&mut self, iface: &Interface, ty: &Type, mode: TypeMode) { + match mode { + TypeMode::AllBorrowed(lt) => { + self.print_borrowed_slice(iface, false, ty, lt); + } + TypeMode::LeafBorrowed(lt) => { + if iface.all_bits_valid(ty) { + self.print_borrowed_slice(iface, false, ty, lt); + } else { + self.push_str("Vec<"); + self.print_ty(iface, ty, mode); + self.push_str(">"); + } + } + TypeMode::HandlesBorrowed(_) | TypeMode::Owned => { + self.push_str("Vec<"); + self.print_ty(iface, ty, mode); + self.push_str(">"); + } + } + } + + fn print_rust_slice( + &mut self, + iface: &Interface, + mutbl: bool, + ty: &Type, + lifetime: &'static str, + ) { + self.push_str("&"); + if lifetime != "'_" { + self.push_str(lifetime); + self.push_str(" "); + } + if mutbl { + self.push_str(" mut "); + } + self.push_str("["); + self.print_ty(iface, ty, TypeMode::AllBorrowed(lifetime)); + self.push_str("]"); + } + + fn print_generics(&mut self, info: &TypeInfo, lifetime: Option<&str>, bound: bool) { + let proj = if info.has_handle { + self.handle_projection() + } else { + None + }; + if lifetime.is_none() && proj.is_none() { + return; + } + self.push_str("<"); + if let Some(lt) = lifetime { + self.push_str(lt); + self.push_str(","); + } + if let Some((proj, trait_bound)) = proj { + self.push_str(proj); + if bound { + self.push_str(": "); + self.push_str(&trait_bound); + } + } + self.push_str(">"); + } + + fn int_repr(&mut self, repr: Int) { + self.push_str(int_repr(repr)); + } + + fn wasm_type(&mut self, ty: WasmType) { + self.push_str(wasm_type(ty)); + } + + fn modes_of(&self, iface: &Interface, ty: TypeId) -> Vec<(String, TypeMode)> { + let info = self.info(ty); + let mut result = Vec::new(); + if info.param { + result.push((self.param_name(iface, ty), self.default_param_mode())); + } + if info.result && (!info.param || self.uses_two_names(&info)) { + result.push((self.result_name(iface, ty), TypeMode::Owned)); + } + return result; + } + + /// Writes the camel-cased 'name' of the passed type to `out`, as used to name union variants. + fn write_name(&self, iface: &Interface, ty: &Type, out: &mut String) { + match ty { + Type::Unit => out.push_str("Unit"), + Type::Bool => out.push_str("Bool"), + Type::U8 => out.push_str("U8"), + Type::U16 => out.push_str("U16"), + Type::U32 => out.push_str("U32"), + Type::U64 => out.push_str("U64"), + Type::S8 => out.push_str("I8"), + Type::S16 => out.push_str("I16"), + Type::S32 => out.push_str("I32"), + Type::S64 => out.push_str("I64"), + Type::Float32 => out.push_str("F32"), + Type::Float64 => out.push_str("F64"), + Type::Char => out.push_str("Char"), + Type::String => out.push_str("String"), + Type::Handle(id) => out.push_str(&iface.resources[*id].name.to_camel_case()), + Type::Id(id) => { + let ty = &iface.types[*id]; + match &ty.name { + Some(name) => out.push_str(&name.to_camel_case()), + None => match &ty.kind { + TypeDefKind::Option(ty) => { + out.push_str("Optional"); + self.write_name(iface, ty, out); + } + TypeDefKind::Expected(_) => out.push_str("Result"), + TypeDefKind::Tuple(_) => out.push_str("Tuple"), + TypeDefKind::List(ty) => { + self.write_name(iface, ty, out); + out.push_str("List") + } + TypeDefKind::Future(ty) => { + self.write_name(iface, ty, out); + out.push_str("Future"); + } + TypeDefKind::Stream(s) => { + self.write_name(iface, &s.element, out); + self.write_name(iface, &s.end, out); + out.push_str("Stream"); + } + + TypeDefKind::Type(ty) => self.write_name(iface, ty, out), + TypeDefKind::Record(_) => out.push_str("Record"), + TypeDefKind::Flags(_) => out.push_str("Flags"), + TypeDefKind::Variant(_) => out.push_str("Variant"), + TypeDefKind::Enum(_) => out.push_str("Enum"), + TypeDefKind::Union(_) => out.push_str("Union"), + }, + } + } + } + } + + /// Returns the names for the cases of the passed union. + fn union_case_names(&self, iface: &Interface, union: &Union) -> Vec { + enum UsedState<'a> { + /// This name has been used once before. + /// + /// Contains a reference to the name given to the first usage so that a suffix can be added to it. + Once(&'a mut String), + /// This name has already been used multiple times. + /// + /// Contains the number of times this has already been used. + Multiple(usize), + } + + // A `Vec` of the names we're assigning each of the union's cases in order. + let mut case_names = vec![String::new(); union.cases.len()]; + // A map from case names to their `UsedState`. + let mut used = HashMap::new(); + for (case, name) in union.cases.iter().zip(case_names.iter_mut()) { + self.write_name(iface, &case.ty, name); + + match used.get_mut(name.as_str()) { + None => { + // Initialise this name's `UsedState`, with a mutable reference to this name + // in case we have to add a suffix to it later. + used.insert(name.clone(), UsedState::Once(name)); + // Since this is the first (and potentially only) usage of this name, + // we don't need to add a suffix here. + } + Some(state) => match state { + UsedState::Multiple(n) => { + // Add a suffix of the index of this usage. + write!(name, "{n}").unwrap(); + // Add one to the number of times this type has been used. + *n += 1; + } + UsedState::Once(first) => { + // Add a suffix of 0 to the first usage. + first.push('0'); + // We now get a suffix of 1. + name.push('1'); + // Then update the state. + *state = UsedState::Multiple(2); + } + }, + } + } + + case_names + } + + fn print_typedef_record( + &mut self, + iface: &Interface, + id: TypeId, + record: &Record, + docs: &Docs, + ) { + let info = self.info(id); + for (name, mode) in self.modes_of(iface, id) { + let lt = self.lifetime_for(&info, mode); + self.rustdoc(docs); + if !info.owns_data() { + self.push_str("#[repr(C)]\n"); + self.push_str("#[derive(Copy, Clone)]\n"); + } else if !info.has_handle { + self.push_str("#[derive(Clone)]\n"); + } + self.push_str(&format!("pub struct {}", name)); + self.print_generics(&info, lt, true); + self.push_str(" {\n"); + for field in record.fields.iter() { + self.rustdoc(&field.docs); + self.push_str("pub "); + self.push_str(&to_rust_ident(&field.name)); + self.push_str(": "); + self.print_ty(iface, &field.ty, mode); + self.push_str(",\n"); + } + self.push_str("}\n"); + + self.push_str("impl"); + self.print_generics(&info, lt, true); + self.push_str(" core::fmt::Debug for "); + self.push_str(&name); + self.print_generics(&info, lt, false); + self.push_str(" {\n"); + self.push_str( + "fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n", + ); + self.push_str(&format!("f.debug_struct(\"{}\")", name)); + for field in record.fields.iter() { + self.push_str(&format!( + ".field(\"{}\", &self.{})", + field.name, + to_rust_ident(&field.name) + )); + } + self.push_str(".finish()"); + self.push_str("}\n"); + self.push_str("}\n"); + } + } + + fn print_typedef_tuple(&mut self, iface: &Interface, id: TypeId, tuple: &Tuple, docs: &Docs) { + let info = self.info(id); + for (name, mode) in self.modes_of(iface, id) { + let lt = self.lifetime_for(&info, mode); + self.rustdoc(docs); + self.push_str(&format!("pub type {}", name)); + self.print_generics(&info, lt, true); + self.push_str(" = ("); + for ty in tuple.types.iter() { + self.print_ty(iface, ty, mode); + self.push_str(","); + } + self.push_str(");\n"); + } + } + + fn print_typedef_variant( + &mut self, + iface: &Interface, + id: TypeId, + variant: &Variant, + docs: &Docs, + ) where + Self: Sized, + { + self.print_rust_enum( + iface, + id, + variant + .cases + .iter() + .map(|c| (c.name.to_camel_case(), &c.docs, &c.ty)), + docs, + ); + } + + fn print_typedef_union(&mut self, iface: &Interface, id: TypeId, union: &Union, docs: &Docs) + where + Self: Sized, + { + self.print_rust_enum( + iface, + id, + zip(self.union_case_names(iface, union), &union.cases) + .map(|(name, case)| (name, &case.docs, &case.ty)), + docs, + ); + } + + fn print_rust_enum<'a>( + &mut self, + iface: &Interface, + id: TypeId, + cases: impl IntoIterator + Clone, + docs: &Docs, + ) where + Self: Sized, + { + let info = self.info(id); + + for (name, mode) in self.modes_of(iface, id) { + let name = name.to_camel_case(); + self.rustdoc(docs); + let lt = self.lifetime_for(&info, mode); + if !info.owns_data() { + self.push_str("#[derive(Clone, Copy)]\n"); + } else if !info.has_handle { + self.push_str("#[derive(Clone)]\n"); + } + self.push_str(&format!("pub enum {name}")); + self.print_generics(&info, lt, true); + self.push_str("{\n"); + for (case_name, docs, payload) in cases.clone() { + self.rustdoc(docs); + self.push_str(&case_name); + if *payload != Type::Unit { + self.push_str("("); + self.print_ty(iface, payload, mode); + self.push_str(")") + } + self.push_str(",\n"); + } + self.push_str("}\n"); + + self.print_rust_enum_debug( + id, + mode, + &name, + cases + .clone() + .into_iter() + .map(|(name, _docs, ty)| (name, ty)), + ); + } + } + + fn print_rust_enum_debug<'a>( + &mut self, + id: TypeId, + mode: TypeMode, + name: &str, + cases: impl IntoIterator, + ) where + Self: Sized, + { + let info = self.info(id); + let lt = self.lifetime_for(&info, mode); + self.push_str("impl"); + self.print_generics(&info, lt, true); + self.push_str(" core::fmt::Debug for "); + self.push_str(name); + self.print_generics(&info, lt, false); + self.push_str(" {\n"); + self.push_str("fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n"); + self.push_str("match self {\n"); + for (case_name, payload) in cases { + self.push_str(name); + self.push_str("::"); + self.push_str(&case_name); + if *payload != Type::Unit { + self.push_str("(e)"); + } + self.push_str(" => {\n"); + self.push_str(&format!("f.debug_tuple(\"{}::{}\")", name, case_name)); + if *payload != Type::Unit { + self.push_str(".field(e)"); + } + self.push_str(".finish()\n"); + self.push_str("}\n"); + } + self.push_str("}\n"); + self.push_str("}\n"); + self.push_str("}\n"); + } + + fn print_typedef_option(&mut self, iface: &Interface, id: TypeId, payload: &Type, docs: &Docs) { + let info = self.info(id); + + for (name, mode) in self.modes_of(iface, id) { + self.rustdoc(docs); + let lt = self.lifetime_for(&info, mode); + self.push_str(&format!("pub type {}", name)); + self.print_generics(&info, lt, true); + self.push_str("= Option<"); + self.print_ty(iface, payload, mode); + self.push_str(">;\n"); + } + } + + fn print_typedef_expected( + &mut self, + iface: &Interface, + id: TypeId, + expected: &Expected, + docs: &Docs, + ) { + let info = self.info(id); + + for (name, mode) in self.modes_of(iface, id) { + self.rustdoc(docs); + let lt = self.lifetime_for(&info, mode); + self.push_str(&format!("pub type {}", name)); + self.print_generics(&info, lt, true); + self.push_str("= Result<"); + self.print_ty(iface, &expected.ok, mode); + self.push_str(","); + self.print_ty(iface, &expected.err, mode); + self.push_str(">;\n"); + } + } + + fn print_typedef_enum(&mut self, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) + where + Self: Sized, + { + // TODO: should this perhaps be an attribute in the wit file? + let is_error = name.contains("errno"); + + let name = name.to_camel_case(); + self.rustdoc(docs); + self.push_str("#[repr("); + self.int_repr(enum_.tag()); + self.push_str(")]\n#[derive(Clone, Copy, PartialEq, Eq)]\n"); + self.push_str(&format!("pub enum {} {{\n", name.to_camel_case())); + for case in enum_.cases.iter() { + self.rustdoc(&case.docs); + self.push_str(&case.name.to_camel_case()); + self.push_str(",\n"); + } + self.push_str("}\n"); + + // Auto-synthesize an implementation of the standard `Error` trait for + // error-looking types based on their name. + if is_error { + self.push_str("impl "); + self.push_str(&name); + self.push_str("{\n"); + + self.push_str("pub fn name(&self) -> &'static str {\n"); + self.push_str("match self {\n"); + for case in enum_.cases.iter() { + self.push_str(&name); + self.push_str("::"); + self.push_str(&case.name.to_camel_case()); + self.push_str(" => \""); + self.push_str(case.name.as_str()); + self.push_str("\",\n"); + } + self.push_str("}\n"); + self.push_str("}\n"); + + self.push_str("pub fn message(&self) -> &'static str {\n"); + self.push_str("match self {\n"); + for case in enum_.cases.iter() { + self.push_str(&name); + self.push_str("::"); + self.push_str(&case.name.to_camel_case()); + self.push_str(" => \""); + if let Some(contents) = &case.docs.contents { + self.push_str(contents.trim()); + } + self.push_str("\",\n"); + } + self.push_str("}\n"); + self.push_str("}\n"); + + self.push_str("}\n"); + + self.push_str("impl core::fmt::Debug for "); + self.push_str(&name); + self.push_str( + "{\nfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n", + ); + self.push_str("f.debug_struct(\""); + self.push_str(&name); + self.push_str("\")\n"); + self.push_str(".field(\"code\", &(*self as i32))\n"); + self.push_str(".field(\"name\", &self.name())\n"); + self.push_str(".field(\"message\", &self.message())\n"); + self.push_str(".finish()\n"); + self.push_str("}\n"); + self.push_str("}\n"); + + self.push_str("impl core::fmt::Display for "); + self.push_str(&name); + self.push_str( + "{\nfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n", + ); + self.push_str("write!(f, \"{} (error {})\", self.name(), *self as i32)"); + self.push_str("}\n"); + self.push_str("}\n"); + self.push_str("\n"); + self.push_str("impl std::error::Error for "); + self.push_str(&name); + self.push_str("{}\n"); + } else { + self.print_rust_enum_debug( + id, + TypeMode::Owned, + &name, + enum_ + .cases + .iter() + .map(|c| (c.name.to_camel_case(), &Type::Unit)), + ) + } + } + + fn print_typedef_alias(&mut self, iface: &Interface, id: TypeId, ty: &Type, docs: &Docs) { + let info = self.info(id); + for (name, mode) in self.modes_of(iface, id) { + self.rustdoc(docs); + self.push_str(&format!("pub type {}", name)); + let lt = self.lifetime_for(&info, mode); + self.print_generics(&info, lt, true); + self.push_str(" = "); + self.print_ty(iface, ty, mode); + self.push_str(";\n"); + } + } + + fn print_type_list(&mut self, iface: &Interface, id: TypeId, ty: &Type, docs: &Docs) { + let info = self.info(id); + for (name, mode) in self.modes_of(iface, id) { + let lt = self.lifetime_for(&info, mode); + self.rustdoc(docs); + self.push_str(&format!("pub type {}", name)); + self.print_generics(&info, lt, true); + self.push_str(" = "); + self.print_list(iface, ty, mode); + self.push_str(";\n"); + } + } + + fn param_name(&self, iface: &Interface, ty: TypeId) -> String { + let info = self.info(ty); + let name = iface.types[ty].name.as_ref().unwrap().to_camel_case(); + if self.uses_two_names(&info) { + format!("{}Param", name) + } else { + name + } + } + + fn result_name(&self, iface: &Interface, ty: TypeId) -> String { + let info = self.info(ty); + let name = iface.types[ty].name.as_ref().unwrap().to_camel_case(); + if self.uses_two_names(&info) { + format!("{}Result", name) + } else { + name + } + } + + fn uses_two_names(&self, info: &TypeInfo) -> bool { + info.owns_data() + && info.param + && info.result + && match self.default_param_mode() { + TypeMode::AllBorrowed(_) | TypeMode::LeafBorrowed(_) => true, + TypeMode::HandlesBorrowed(_) => info.has_handle, + TypeMode::Owned => false, + } + } + + fn lifetime_for(&self, info: &TypeInfo, mode: TypeMode) -> Option<&'static str> { + match mode { + TypeMode::AllBorrowed(s) | TypeMode::LeafBorrowed(s) + if info.has_list || info.has_handle => + { + Some(s) + } + TypeMode::HandlesBorrowed(s) if info.has_handle => Some(s), + _ => None, + } + } +} + +#[derive(Default)] +pub struct FnSig { + pub async_: bool, + pub unsafe_: bool, + pub private: bool, + pub use_item_name: bool, + pub generics: Option, + pub self_arg: Option, + pub self_is_first_param: bool, +} + +pub trait RustFunctionGenerator { + fn push_str(&mut self, s: &str); + fn tmp(&mut self) -> usize; + fn rust_gen(&self) -> &dyn RustGenerator; + fn lift_lower(&self) -> LiftLower; + + fn let_results(&mut self, amt: usize, results: &mut Vec) { + match amt { + 0 => {} + 1 => { + let tmp = self.tmp(); + let res = format!("result{}", tmp); + self.push_str("let "); + self.push_str(&res); + results.push(res); + self.push_str(" = "); + } + n => { + let tmp = self.tmp(); + self.push_str("let ("); + for i in 0..n { + let arg = format!("result{}_{}", tmp, i); + self.push_str(&arg); + self.push_str(","); + results.push(arg); + } + self.push_str(") = "); + } + } + } + + fn record_lower( + &mut self, + iface: &Interface, + id: TypeId, + record: &Record, + operand: &str, + results: &mut Vec, + ) { + let tmp = self.tmp(); + self.push_str("let "); + let name = self.typename_lower(iface, id); + self.push_str(&name); + self.push_str("{ "); + for field in record.fields.iter() { + let name = to_rust_ident(&field.name); + let arg = format!("{}{}", name, tmp); + self.push_str(&name); + self.push_str(":"); + self.push_str(&arg); + self.push_str(", "); + results.push(arg); + } + self.push_str("} = "); + self.push_str(operand); + self.push_str(";\n"); + } + + fn record_lift( + &mut self, + iface: &Interface, + id: TypeId, + ty: &Record, + operands: &[String], + results: &mut Vec, + ) { + let mut result = self.typename_lift(iface, id); + result.push_str("{"); + for (field, val) in ty.fields.iter().zip(operands) { + result.push_str(&to_rust_ident(&field.name)); + result.push_str(":"); + result.push_str(&val); + result.push_str(", "); + } + result.push_str("}"); + results.push(result); + } + + fn tuple_lower(&mut self, tuple: &Tuple, operand: &str, results: &mut Vec) { + let tmp = self.tmp(); + self.push_str("let ("); + for i in 0..tuple.types.len() { + let arg = format!("t{}_{}", tmp, i); + self.push_str(&arg); + self.push_str(", "); + results.push(arg); + } + self.push_str(") = "); + self.push_str(operand); + self.push_str(";\n"); + } + + fn tuple_lift(&mut self, operands: &[String], results: &mut Vec) { + if operands.len() == 1 { + results.push(format!("({},)", operands[0])); + } else { + results.push(format!("({})", operands.join(", "))); + } + } + + fn typename_lower(&self, iface: &Interface, id: TypeId) -> String { + match self.lift_lower() { + LiftLower::LowerArgsLiftResults => self.rust_gen().param_name(iface, id), + LiftLower::LiftArgsLowerResults => self.rust_gen().result_name(iface, id), + } + } + + fn typename_lift(&self, iface: &Interface, id: TypeId) -> String { + match self.lift_lower() { + LiftLower::LiftArgsLowerResults => self.rust_gen().param_name(iface, id), + LiftLower::LowerArgsLiftResults => self.rust_gen().result_name(iface, id), + } + } +} + +pub fn to_rust_ident(name: &str) -> String { + match name { + // Escape Rust keywords. + // Source: https://doc.rust-lang.org/reference/keywords.html + "as" => "as_".into(), + "break" => "break_".into(), + "const" => "const_".into(), + "continue" => "continue_".into(), + "crate" => "crate_".into(), + "else" => "else_".into(), + "enum" => "enum_".into(), + "extern" => "extern_".into(), + "false" => "false_".into(), + "fn" => "fn_".into(), + "for" => "for_".into(), + "if" => "if_".into(), + "impl" => "impl_".into(), + "in" => "in_".into(), + "let" => "let_".into(), + "loop" => "loop_".into(), + "match" => "match_".into(), + "mod" => "mod_".into(), + "move" => "move_".into(), + "mut" => "mut_".into(), + "pub" => "pub_".into(), + "ref" => "ref_".into(), + "return" => "return_".into(), + "self" => "self_".into(), + "static" => "static_".into(), + "struct" => "struct_".into(), + "super" => "super_".into(), + "trait" => "trait_".into(), + "true" => "true_".into(), + "type" => "type_".into(), + "unsafe" => "unsafe_".into(), + "use" => "use_".into(), + "where" => "where_".into(), + "while" => "while_".into(), + "async" => "async_".into(), + "await" => "await_".into(), + "dyn" => "dyn_".into(), + "abstract" => "abstract_".into(), + "become" => "become_".into(), + "box" => "box_".into(), + "do" => "do_".into(), + "final" => "final_".into(), + "macro" => "macro_".into(), + "override" => "override_".into(), + "priv" => "priv_".into(), + "typeof" => "typeof_".into(), + "unsized" => "unsized_".into(), + "virtual" => "virtual_".into(), + "yield" => "yield_".into(), + "try" => "try_".into(), + s => s.to_snake_case(), + } +} + +pub fn wasm_type(ty: WasmType) -> &'static str { + match ty { + WasmType::I32 => "i32", + WasmType::I64 => "i64", + WasmType::F32 => "f32", + WasmType::F64 => "f64", + } +} + +pub fn int_repr(repr: Int) -> &'static str { + match repr { + Int::U8 => "u8", + Int::U16 => "u16", + Int::U32 => "u32", + Int::U64 => "u64", + } +} + +trait TypeInfoExt { + fn owns_data(&self) -> bool; +} + +impl TypeInfoExt for TypeInfo { + fn owns_data(&self) -> bool { + self.has_list || self.has_handle + } +} + +pub fn bitcast(casts: &[Bitcast], operands: &[String], results: &mut Vec) { + for (cast, operand) in casts.iter().zip(operands) { + results.push(match cast { + Bitcast::None => operand.clone(), + Bitcast::I32ToI64 => format!("i64::from({})", operand), + Bitcast::F32ToI32 => format!("({}).to_bits() as i32", operand), + Bitcast::F64ToI64 => format!("({}).to_bits() as i64", operand), + Bitcast::I64ToI32 => format!("{} as i32", operand), + Bitcast::I32ToF32 => format!("f32::from_bits({} as u32)", operand), + Bitcast::I64ToF64 => format!("f64::from_bits({} as u64)", operand), + Bitcast::F32ToI64 => format!("i64::from(({}).to_bits())", operand), + Bitcast::I64ToF32 => format!("f32::from_bits({} as u32)", operand), + }); + } +} + +pub enum RustFlagsRepr { + U8, + U16, + U32, + U64, + U128, +} + +impl RustFlagsRepr { + pub fn new(f: &Flags) -> RustFlagsRepr { + match f.repr() { + FlagsRepr::U8 => RustFlagsRepr::U8, + FlagsRepr::U16 => RustFlagsRepr::U16, + FlagsRepr::U32(1) => RustFlagsRepr::U32, + FlagsRepr::U32(2) => RustFlagsRepr::U64, + FlagsRepr::U32(3 | 4) => RustFlagsRepr::U128, + FlagsRepr::U32(n) => panic!("unsupported number of flags: {}", n * 32), + } + } +} + +impl fmt::Display for RustFlagsRepr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RustFlagsRepr::U8 => "u8".fmt(f), + RustFlagsRepr::U16 => "u16".fmt(f), + RustFlagsRepr::U32 => "u32".fmt(f), + RustFlagsRepr::U64 => "u64".fmt(f), + RustFlagsRepr::U128 => "u128".fmt(f), + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/Cargo.toml new file mode 100644 index 0000000..ddef4a1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wit-bindgen-gen-spidermonkey" +version = "0.1.0" +authors = ["Nick Fitzgerald "] +edition = "2018" + +[lib] +test = false +doctest = false + +[dependencies] +lazy_static = "1.4.0" +structopt = { version = "0.3", optional = true } +wasm-encoder = "0.8.0" +wit-bindgen-gen-core = { path = "../gen-core" } +heck = "0.3" + +[dev-dependencies] +test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-spidermonkey'] } +wasmparser = "0.80" diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/build.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/build.rs new file mode 100644 index 0000000..d7c7fc2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + // this build script is currently only here so OUT_DIR is set for testing. +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/.gitignore b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/.gitignore new file mode 100644 index 0000000..37da288 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/.gitignore @@ -0,0 +1,7 @@ +spidermonkey-* +lib/* +*.o +spidermonkey.initial.wasm +wasi-sdk-12.0 +mozbuild +.exports diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/Makefile b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/Makefile new file mode 100644 index 0000000..9becb55 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/Makefile @@ -0,0 +1,142 @@ +SM_REPO := https://github.com/fitzgen/gecko-dev +SM_COMMIT := dafd3165f45c55023ece4787a86444029e4f475e + +# TODO: support building `spidermonkey.wasm` on other OSes. But for some reason +# the resulting `.wasm` binary is slower when the host compiler is on macOS. +WASI_SDK_URL := https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-12/wasi-sdk-12.0-linux.tar.gz + +CC := $(CURDIR)/wasi-sdk-12.0/bin/clang +CXX := $(CURDIR)/wasi-sdk-12.0/bin/clang++ + +# Set this to `1` to enable logging via all the `SMW_LOG(...)` calls. +LOGGING := 0 + +# Set this to `-DDEBUG` and uncomment the `--enable-debug` line in `mozconfig` +# to enable debug builds of SpiderMonkey. +DEBUG := "" + +# Set this to `""` in debug mode for better debugging. +OPT := "-O2" + +CFLAGS := \ + --sysroot=$(CURDIR)/wasi-sdk-12.0/share/wasi-sysroot \ + -Wall \ + --target=wasm32-unknown-wasi \ + -Ispidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/dist/include \ + -I$(CURDIR)/include \ + $(DEBUG) \ + $(OPT) \ + -DLOGGING=$(LOGGING) + +CXXFLAGS := \ + $(CFLAGS) \ + -fno-exceptions \ + -std=c++17 + +# Local object files. +LOCAL_OBJECTS := $(patsubst %.cpp,%.o,$(wildcard *.cpp)) + +# Object files needed within SpiderMonkey's obj dir. +SM_OBJECTS := \ + js/src/build/libjs_static.a \ + memory/build/Unified_cpp_memory_build0.o \ + memory/mozalloc/mozalloc_abort.o \ + memory/mozalloc/Unified_cpp_memory_mozalloc0.o \ + mfbt/Unified_cpp_mfbt0.o \ + mfbt/Unified_cpp_mfbt1.o \ + mfbt/lz4.o \ + mfbt/lz4frame.o \ + mfbt/lz4hc.o \ + mfbt/xxhash.o \ + mozglue/misc/AutoProfilerLabel.o \ + mozglue/misc/ConditionVariable_noop.o \ + mozglue/misc/Decimal.o \ + mozglue/misc/MmapFaultHandler.o \ + mozglue/misc/Mutex_noop.o \ + mozglue/misc/Printf.o \ + mozglue/misc/StackWalk.o \ + mozglue/misc/TimeStamp.o \ + mozglue/misc/TimeStamp_posix.o \ + mozglue/misc/Uptime.o \ + modules/zlib/src/compress.o \ + modules/zlib/src/gzclose.o \ + modules/zlib/src/infback.o \ + modules/zlib/src/uncompr.o \ + wasm32-wasi/release/libjsrust.a + +# The `./lib/*` copies of SpiderMonkey's object files that we check into the +# repo. +SM_LIB_OBJECTS := $(shell echo $(SM_OBJECTS) | xargs -d' ' -I{} basename {} | xargs -I{} echo lib/{}) + +.PHONY: all clean clean-all clean-spidermonkey clean-wasi-sdk + +all: spidermonkey.wasm + @echo "Done!" + +spidermonkey.initial.wasm: $(SM_LIB_OBJECTS) $(LOCAL_OBJECTS) + $(CXX) $(CXXFLAGS) \ + -mexec-model=reactor \ + $(LOCAL_OBJECTS) \ + $(SM_LIB_OBJECTS) \ + -o spidermonkey.initial.wasm \ + -Wl,--export-dynamic \ + -Wl,--growable-table \ + -Wl,--export-table \ + -Wl,--gc-sections + +spidermonkey.wasm: spidermonkey.initial.wasm + # Uncomment this `wasm-opt` invocation and comment the following one out to + # enable better debugging. + # + # wasm-opt -g --duplicate-import-elimination spidermonkey.initial.wasm -o spidermonkey.wasm + wasm-opt -O2 --strip-dwarf --duplicate-import-elimination spidermonkey.initial.wasm -o spidermonkey.wasm + + +# Build all `*.cpp` files into `*.o` files. +%.o: %.cpp $(SM_LIB_OBJECTS) + $(CXX) $(CXXFLAGS) -c $< -o $@ + +# Actually build SpiderMonkey. +$(SM_LIB_OBJECTS): spidermonkey-$(SM_COMMIT) mozbuild wasi-sdk-12.0 mozconfig + cd spidermonkey-$(SM_COMMIT) \ + && MOZBUILD_STATE_PATH=$(CURDIR)/mozbuild MOZCONFIG=$(CURDIR)/mozconfig ./mach build + mkdir -p lib + for x in $(SM_OBJECTS); do \ + cp spidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/$$x lib/; \ + done + +# Clone `mozilla-central` at the `SM_COMMIT` commit. +spidermonkey-$(SM_COMMIT): + -rm -rf spidermonkey-temp + mkdir spidermonkey-temp + cd spidermonkey-temp \ + && git init \ + && git remote add origin $(SM_REPO) \ + && git fetch origin $(SM_COMMIT) \ + && git checkout $(SM_COMMIT) + mv spidermonkey-temp spidermonkey-$(SM_COMMIT) + +mozbuild: spidermonkey-$(SM_COMMIT) + -mkdir mozbuild + cd spidermonkey-$(SM_COMMIT) \ + && MOZBUILD_STATE_PATH=$(CURDIR)/mozbuild ./mach bootstrap --application-choice js --no-system-changes \ + || rm -rf $(CURDIR)/mozbuild + +wasi-sdk-12.0: + curl -L $(WASI_SDK_URL) | tar -x -z + +clean-all: clean clean-spidermonkey clean-wasi-sdk + +clean-wasi-sdk: + -rm -rf wasi-sdk-12.0 + +clean-spidermonkey: + -rm -rf spidermonkey-$(SM_COMMIT) + -rm -rf spidermonkey-$(SM_COMMIT)/obj-wasm32-unknown-wasi/ + -rm -rf mozbuild + +clean: + @echo 'Only cleaning our own artifacts, not upstream deps. Run `make clean-{all,spidermonkey,wasi-sdk}` to clean others.' + -rm -rf spidermonkey-temp + -rm -rf ./*.o + -rm -rf spidermonkey.wasm diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/README.md b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/README.md new file mode 100644 index 0000000..4f9f0ed --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/README.md @@ -0,0 +1,13 @@ +# `spidermonkey.wasm` + +This directory contains the source code for `spidermonkey.wasm`, which is an +embedding of the SpiderMonkey JavaScript engine for targeting `wasm32-wasi` and +use with `wit-bindgen-gen-spidermonkey`. It exports a variety of helper +functions that are used by `wit-bindgen-gen-spidermonkey`'s generated glue +code. These helpers are typically named something like `SMW_whatever_function`. + +## Building `spidermonkey.wasm` + +``` +make +``` diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/abort.cpp b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/abort.cpp new file mode 100644 index 0000000..ae94556 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/abort.cpp @@ -0,0 +1,46 @@ +#include + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winvalid-offsetof" +#include "js/Exception.h" +#pragma clang diagnostic pop + +#include "smw/abort.h" +#include "smw/cx.h" +#include "smw/dump.h" + +namespace smw { + +void abort(const char* msg) { + abort(get_js_context(), msg); +} + +void abort(JSContext *cx, const char* msg) { + fprintf(stderr, "Error: %s", msg); + + if (JS_IsExceptionPending(cx)) { + fprintf(stderr, ":"); + JS::ExceptionStack exception(cx); + if (!JS::GetPendingExceptionStack(cx, &exception)) { + fprintf(stderr, " failed to get pending exception value and stack\n"); + } else { + fprintf(stderr, "\n exception value: "); + if (!dump_value(cx, exception.exception(), stderr)) { + fprintf(stderr, ""); + } + fprintf(stderr, "\n exception stack:\n"); + if (!dump_stack(cx, exception.stack(), stderr)) { + fprintf(stderr, "\n"); + } + } + } else { + fprintf(stderr, "\n"); + } + + // TODO: check for unhandled promise rejections. + + fflush(stderr); + ::abort(); +} + +} // namespace smw diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/bindgen.cpp b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/bindgen.cpp new file mode 100644 index 0000000..95ddbf3 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/bindgen.cpp @@ -0,0 +1,452 @@ +/*! + * This module implements the intrinsics used by code emitted in the + * `wit_bindgen_gen_spidermonkey::Bindgen` trait implementation. + */ + +#include +#include +#include + +#include "smw/abort.h" +#include "smw/cx.h" +#include "smw/logging.h" +#include "smw/wasm.h" + +#include "mozilla/UniquePtr.h" +#include "jsapi.h" +#include "js/Array.h" +#include "js/Conversions.h" +#include "js/ForOfIterator.h" +#include "js/Modules.h" + +#ifdef LOGGING +#include "js/friend/DumpFunctions.h" +#endif + +namespace smw { + +using UniqueChars = mozilla::UniquePtr; + +using PersistentRootedValueVector = JS::PersistentRooted>; + +// Used for general Wasm<-->JS conversions. +static PersistentRootedValueVector* OPERANDS; + +// Used for holding arguments to JS calls. +static PersistentRootedValueVector* ARGS; + +// Used for holding returns from Wasm calls. +static PersistentRootedValueVector* RETS; + +void init_operands(JSContext* cx) { + assert(!OPERANDS && "OPERANDS must only be initialized once"); + OPERANDS = new PersistentRootedValueVector(cx, cx); + if (!OPERANDS) { + abort(cx, "failed to allocate OPERANDS"); + } + + assert(!ARGS && "ARGS must only be initialized once"); + ARGS = new PersistentRootedValueVector(cx, cx); + if (!ARGS) { + abort(cx, "failed to allocate ARGS"); + } + + assert(!RETS && "RETS must only be initialized once"); + RETS = new PersistentRootedValueVector(cx, cx); + if (!RETS) { + abort(cx, "failed to allocate RETS"); + } +} + +PersistentRootedValueVector& operands() { + assert(OPERANDS && OPERANDS->initialized() && "OPERANDS must be initialized"); + return *OPERANDS; +} + +void save_operand(size_t dest, JS::HandleValue val) { +#if LOGGING==1 + SMW_LOG("operands[%zu] = ", dest); + js::DumpValue(val, stderr); +#endif // LOGGING==1 + + JSContext* cx = get_js_context(); + + if (operands().length() <= dest) { + size_t needed_capacity = 1 + dest - operands().length(); + if (!operands().reserve(needed_capacity)) { + abort("failed to reserve capacity for the OPERANDS vector"); + } + if (dest == operands().length()) { + bool ok = operands().append(val); + assert(ok && "already reserved space"); + return; + } + JS::RootedValue placeholder(cx, JS::UndefinedValue()); + for (size_t i = 0; i < needed_capacity; i++) { + bool ok = operands().append(placeholder); + assert(ok && "already reserved space"); + } + } + + operands()[dest].set(val); +} + +PersistentRootedValueVector& args() { + assert(ARGS && ARGS->initialized() && "ARGS must be initialized"); + return *ARGS; +} + +PersistentRootedValueVector& rets() { + assert(RETS && RETS->initialized() && "RETS must be initialized"); + return *RETS; +} + +WASM_EXPORT +void canonical_abi_free(void* ptr, size_t size, size_t align) { + (void) size; + (void) align; + free(ptr); +} + +WASM_EXPORT +void* canonical_abi_realloc(void* ptr, size_t old_size, size_t align, size_t new_size) { + (void) old_size; + (void) align; + return realloc(ptr, new_size); +} + +WASM_EXPORT +void SMW_fill_operands(unsigned argc, JS::Value* vp) { + SMW_LOG("SMW_fill_operands(argc = %d, vp = %p)\n", argc, vp); + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + + if (!operands().reserve(size_t(args.length()))) { + abort(get_js_context(), "failed to reserve space in the operands vector"); + } + for (unsigned i = 0; i < args.length(); i++) { +#if LOGGING==1 + SMW_LOG("operands[%d] = ", i); + js::DumpValue(args.get(i), stderr); +#endif // LOGGING==1 + + bool ok = operands().append(args.get(i)); + assert(ok && "already reserved space"); + } +} + +WASM_EXPORT +void SMW_clear_operands() { + SMW_LOG("SMW_clear_operands\n"); + operands().clear(); +} + +WASM_EXPORT +void SMW_push_arg(size_t i) { + SMW_LOG("SMW_push_arg(i = %zu)\n", i); + if (!args().append(operands()[i])) { + abort("failed to push arg"); + } +} + +WASM_EXPORT +void SMW_call(char *funcName, size_t funcNameLen, size_t numResults, size_t dest) { +#ifdef LOGGING + SMW_LOG("SMW_call(funcName = %p \"", funcName); + for (size_t i = 0; i < funcNameLen; i++) { + SMW_LOG("%c", funcName[i]); + } + SMW_LOG("\", funcNameLen = %zu, numResults = %zu, dest = %zu)\n", + funcNameLen, + numResults, + dest); +#endif + + UniqueChars uniqFuncName(funcName); + + JSContext *cx = get_js_context(); + + JS::RootedString funcNameAtom(cx, JS_AtomizeStringN(cx, uniqFuncName.get(), funcNameLen)); + if (!funcNameAtom) { + abort(cx, "failed to atomize function name"); + } + + JS::RootedObject module(cx, get_user_module()); + JS::RootedValue exportVal(cx); + bool hasExport = false; + if (!JS::GetModuleExport(cx, module, funcNameAtom, &exportVal, &hasExport)) { + abort(cx, "failed to get module export"); + } + if (!hasExport) { + // TODO: include the export name in this message to help users debug + // which export they're missing. + abort(cx, "user module does not have the requested export"); + } + + JS::RootedFunction exportFunc(cx, JS_ValueToFunction(cx, exportVal)); + if (!exportFunc) { + // TODO: include the export name in this message. + abort(cx, "exported value is not a function"); + } + + // XXX: we have to copy ARGS into a `JS::RootedVector` because + // `JS::Call` takes a `JS::HandleValueArray` and you can't construct that + // from a `JS::PersistentRooted>`, only a + // `JS::RootedVector`. And we can't make `ARGS` a + // `JS::RootedVector` because it is a global, not an on-stack + // RAII value as required by `JS::RootedVector`. Gross! + JS::RootedVector argsVector(cx); + if (!argsVector.reserve(args().length())) { + abort(cx, "failed to reserve space for arguments vector"); + } + for (size_t i = 0; i < args().length(); i++) { + bool ok = argsVector.append(args()[i]); + assert(ok && "already reserved space"); + } + + JS::RootedObject thisObj(cx); + JS::RootedValue result(cx); + if (!JS::Call(cx, thisObj, exportFunc, argsVector, &result)) { + // TODO: include the export name in this message. + abort(cx, "calling export function failed"); + } + + args().clear(); + + if (numResults == 0) { + // Nothing to push onto the operands vector. + } else if (numResults == 1) { + save_operand(dest, result); + } else { + // Treat the "physical" return value as an iterator and unpack the + // "logical" return values from within it. This allows JS to return + // multiple WIT values as an array or any other iterable. + JS::ForOfIterator iter(cx); + if (!iter.init(result)) { + // TODO: include the export name in this message. + abort(cx, "failed to convert return value to iterable"); + } + JS::RootedValue val(cx); + bool done = false; + for (size_t i = 0; i < numResults; i++) { + if (done) { + // TODO: include the export name in this message. + abort(cx, "function's returned iterator did not yield enough return values"); + } + if (!iter.next(&val, &done)) { + // TODO: include the export name in this message. + abort(cx, "failed to get the next value out of the return values iterator"); + } + save_operand(dest + i, val); + } + } +} + +WASM_EXPORT +void SMW_push_return_value(size_t i) { + SMW_LOG("SMW_push_return_value(i = %zu)\n", i); + if (!rets().append(operands()[i])) { + abort(get_js_context(), "failed to push arg"); + } +} + +WASM_EXPORT +void SMW_finish_returns(unsigned argc, JS::Value* vp) { + SMW_LOG("SMW_finish_returns(argc = %d, vp = %p)\n", argc, vp); + + JS::CallArgs args = JS::CallArgsFromVp(argc, vp); + switch (rets().length()) { + case 0: { + break; + } + case 1: { + args.rval().set(rets().back()); + break; + } + default: { + JSContext* cx = get_js_context(); + JS::RootedVector elems(cx); + if (!elems.reserve(rets().length())) { + abort(cx, "failed to reserve space for results vector"); + } + for (size_t i = 0; i < rets().length(); i++) { + bool ok = elems.append(rets()[i]); + assert(ok && "already reserved space"); + } + JS::RootedObject arr(cx, JS::NewArrayObject(cx, elems)); + if (!arr) { + abort(cx, "failed to allocate array for function's return values"); + } + args.rval().setObject(*arr.get()); + break; + } + } + + rets().clear(); +} + +WASM_EXPORT +uint32_t SMW_i32_from_u32(size_t i) { + SMW_LOG("SMW_i32_from_u32(i = %zu)\n", i); + + JSContext* cx = get_js_context(); + JS::RootedValue val(cx, operands()[i]); + double number = 0.0; + if (!JS::ToNumber(cx, val, &number)) { + abort(cx, "failed to convert value to number"); + } + number = std::round(number); + return uint32_t(number); +} + +WASM_EXPORT +void SMW_u32_from_i32(uint32_t x, size_t dest) { + SMW_LOG("SMW_u32_from_i32(x = %ull, dest = %zu)\n", x, dest); + + JSContext* cx = get_js_context(); + JS::RootedValue val(cx, JS::NumberValue(x)); + save_operand(dest, val); +} + +WASM_EXPORT +void SMW_string_canon_lower(uint32_t* ret_ptr, size_t i) { + SMW_LOG("SMW_string_canon_lower(ret_ptr = %p, i = %zu)\n", ret_ptr, i); + + JSContext* cx = get_js_context(); + JS::RootedValue strVal(cx, operands()[i]); + if (!strVal.isString()) { + abort(cx, "value is not a string"); + } + JS::RootedString str(cx, strVal.toString()); + JS::Rooted linearStr(cx, JS_EnsureLinearString(cx, str)); + if (!linearStr) { + abort(cx, "failed to linearize JS string"); + } + + size_t len = JS::GetDeflatedUTF8StringLength(linearStr); + char* ptr = static_cast(malloc(len)); + if (!ptr) { + abort(cx, "out of memory"); + } + + size_t num_written = JS::DeflateStringToUTF8Buffer(linearStr, mozilla::Span(ptr, len)); + assert(num_written == len); + + ret_ptr[0] = reinterpret_cast(ptr); + ret_ptr[1] = static_cast(len); +} + +WASM_EXPORT +void SMW_string_canon_lift(char* ptr, size_t len, size_t dest) { + SMW_LOG("SMW_string_canon_lift(ptr = %p, len = %zu, dest = %zu)\n", ptr, len, dest); + + JSContext* cx = get_js_context(); + JS::RootedString str(cx, JS_NewStringCopyUTF8N(cx, JS::UTF8Chars(ptr, len))); + if (!str) { + abort(cx, "failed to create JS string from UTF-8 buffer"); + } + JS::RootedValue strVal(cx, JS::StringValue(str)); + save_operand(dest, strVal); +} + +WASM_EXPORT +uint32_t SMW_spread_into_array(size_t i) { + SMW_LOG("SMW_spread_into_array; i = %zu\n", i); + + JSContext* cx = get_js_context(); + + JS::RootedValue iterable(cx, operands()[i]); + bool is_array = false; + if (!JS::IsArrayObject(cx, iterable, &is_array)) { + abort(cx, "failed to check if object is an array"); + } + + if (is_array) { + JS::RootedObject arr(cx, &iterable.toObject()); + uint32_t length = 0; + if (!JS::GetArrayLength(cx, arr, &length)) { + abort(cx, "failed to get array length"); + } + return length; + } + + JS::RootedVector elems(cx); + JS::ForOfIterator iter(cx); + if (!iter.init(iterable)) { + abort(cx, "failed to convert operand value to iterable"); + } + JS::RootedValue val(cx); + bool done = false; + while (!done) { + if (!iter.next(&val, &done)) { + abort(cx, "failed to get the next value out of iterator"); + } + if (done) { + break; + } + if (!elems.append(val)) { + abort(cx, "failed to append value to vector"); + } + } + + JS::RootedObject arr(cx, JS::NewArrayObject(cx, elems)); + if (!arr) { + abort(cx, "failed to allocate JS array object"); + } + operands()[i].setObject(*arr); + + return elems.length(); +} + +WASM_EXPORT +void SMW_get_array_element(size_t array, size_t index, size_t dest) { + SMW_LOG("SMW_get_array_element(array = %zu, index = %zu, dest = %zu)\n", array, index, dest); + + JSContext* cx = get_js_context(); + + JS::RootedValue array_val(cx, operands()[array]); + assert(array_val.isObject()); + JS::RootedObject array_obj(cx, &array_val.toObject()); + JS::RootedValue elem(cx); + if (!JS_GetElement(cx, array_obj, index, &elem)) { + abort(cx, "failed to get array element"); + } + + save_operand(dest, elem); +} + +WASM_EXPORT +void SMW_new_array(size_t dest) { + SMW_LOG("SMW_new_array(dest = %zu)\n", dest); + + JSContext* cx = get_js_context(); + JS::RootedObject arr(cx, JS::NewArrayObject(cx, 0)); + if (!arr) { + abort(cx, "failed to allocate a new JS array object"); + } + JS::RootedValue arr_val(cx, JS::ObjectValue(*arr)); + save_operand(dest, arr_val); +} + +WASM_EXPORT +void SMW_array_push(size_t array, size_t elem) { + SMW_LOG("SMW_array_push(array = %zu, elem = %zu)\n", array, elem); + + JSContext* cx = get_js_context(); + + JS::RootedValue array_val(cx, operands()[array]); + assert(array_val.isObject()); + JS::RootedObject array_obj(cx, &array_val.toObject()); + + uint32_t length = 0; + if (!JS::GetArrayLength(cx, array_obj, &length)) { + abort(cx, "failed to get JS array object length"); + } + + JS::RootedValue elem_val(cx, operands()[elem]); + if (!JS_SetElement(cx, array_obj, length, elem_val)) { + abort(cx, "failed to set JS array element"); + } +} + +} // namespace smw diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/cx.cpp b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/cx.cpp new file mode 100644 index 0000000..60e6db6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/cx.cpp @@ -0,0 +1,33 @@ +#include + +#include "smw/cx.h" + +#include "jsapi.h" + +namespace smw { + +static JSContext* CONTEXT = nullptr; + +void init_js_context(JSContext *cx) { + assert(!CONTEXT && "CONTEXT should only be initialized once"); + CONTEXT = cx; +} + +JSContext *get_js_context() { + assert(CONTEXT && "CONTEXT should be initialized"); + return CONTEXT; +} + +static JS::PersistentRooted USER_MODULE; + +void init_user_module(JSContext* cx, JSObject* user_module) { + assert(!USER_MODULE && "USER_MODULE should only be initialized once"); + USER_MODULE.init(cx, user_module); +} + +JSObject* get_user_module() { + assert(USER_MODULE && "USER_MODULE should be initialized"); + return USER_MODULE; +} + +} // namespace smw diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/dump.cpp b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/dump.cpp new file mode 100644 index 0000000..766b981 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/dump.cpp @@ -0,0 +1,48 @@ +#include "smw/dump.h" + +#include +#include "jsapi.h" +#include "smw/wasm.h" + +namespace smw { + +static JS::UniqueChars stringify_value(JSContext *cx, JS::HandleValue val) { + JS::RootedString str(cx, JS_ValueToSource(cx, val)); + if (!str) { + return nullptr; + } + return JS_EncodeStringToUTF8(cx, str); +} + +bool dump_value(JSContext *cx, JS::HandleValue val, FILE* fp) { + JS::UniqueChars str = stringify_value(cx, val); + if (!str) { + return false; + } + fprintf(fp, "%s\n", str.get()); + return true; +} + +bool dump_stack(JSContext *cx, JS::HandleObject stack, FILE* fp) { + JS::RootedString str(cx); + size_t indent = 4; + if (!JS::BuildStackString(cx, nullptr, stack, &str, indent)) { + return false; + } + + JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, str); + if (!utf8) { + return false; + } + + fprintf(fp, "%s\n", utf8.get()); + return true; +} + +WASM_EXPORT +int32_t dump_i32(int32_t x) { + fprintf(stderr, "dump_i32: %d\n", x); + return x; +} + +} // namespace smw diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/abort.h b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/abort.h new file mode 100644 index 0000000..e653f54 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/abort.h @@ -0,0 +1,16 @@ +#ifndef _smw_abort_h +#define _smw_abort_h + +struct JSContext; + +namespace smw { + +/** + * Print the given error message and abort. + */ +void abort(const char* msg); +void abort(JSContext *cx, const char* msg); + +} + +#endif // _smw_abort_h diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/bindgen.h b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/bindgen.h new file mode 100644 index 0000000..e2343f2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/bindgen.h @@ -0,0 +1,12 @@ +#ifndef _smw_bindgen_h +#define _smw_bindgen_h + +struct JSContext; + +namespace smw { + +void init_operands(JSContext* cx); + +} + +#endif // _smw_bindgen_h diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/cx.h b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/cx.h new file mode 100644 index 0000000..c9d4a79 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/cx.h @@ -0,0 +1,17 @@ +#ifndef _smw_cx_h +#define _smw_cx_h + +struct JSContext; +class JSObject; + +namespace smw { + +void init_js_context(JSContext* cx); +JSContext* get_js_context(); + +void init_user_module(JSContext* cx, JSObject* user_module); +JSObject* get_user_module(); + +} + +#endif // _smw_cx_h diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/dump.h b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/dump.h new file mode 100644 index 0000000..ee97664 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/dump.h @@ -0,0 +1,27 @@ +#ifndef _smw_dump_h +#define _smw_dump_h + +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Winvalid-offsetof" + +#include "js/TypeDecls.h" +#include "js/Value.h" + +#pragma clang diagnostic pop + +namespace smw { + +/** + * Dump a human-readable representation of the given JS value to the given file. + */ +bool dump_value(JSContext *cx, JS::HandleValue val, FILE* fp); + +/** + * Dump a human-readable representation of the given JS exception stack to the + * given file. + */ +bool dump_stack(JSContext *cx, JS::HandleObject stack, FILE* fp); + +} + +#endif // _smw_dump_h diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/logging.h b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/logging.h new file mode 100644 index 0000000..6e6c4a6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/logging.h @@ -0,0 +1,15 @@ +#ifndef _smw_logging_h +#define _smw_logging_h + +#if LOGGING==1 + +#include +#define SMW_LOG(msg, ...) fprintf(stderr, msg, ##__VA_ARGS__) + +#else // LOGGING==1 + +#define SMW_LOG(msg, ...) do { } while(false) + +#endif // LOGGING==1 + +#endif // _smw_logging_h diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/wasm.h b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/wasm.h new file mode 100644 index 0000000..8f2d3e2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/include/smw/wasm.h @@ -0,0 +1,18 @@ +#ifndef _smw_wasm_h +#define _smw_wasm_h + +/** + * An attribute for making a function exported from the final Wasm binary. + * + * Example usage: + * + * WASM_EXPORT + * int add(int a, int b) { + * return a + b; + * } + */ +#define WASM_EXPORT \ + __attribute__((visibility("default"))) \ + extern "C" + +#endif // _smw_wasm_h diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/initialize.cpp b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/initialize.cpp new file mode 100644 index 0000000..9634c04 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/initialize.cpp @@ -0,0 +1,380 @@ +/*! + * JS engine initialization and JS top-level evaluation. + * + * This file contains the code to start up the JS engine, define import-able + * modules from VM functions, and evaluate the user JS. + */ + +#include +#include + +#include "smw/abort.h" +#include "smw/bindgen.h" +#include "smw/cx.h" +#include "smw/wasm.h" + +#include "js/AllocPolicy.h" +#include "js/CompilationAndEvaluation.h" +#include "js/GCAPI.h" +#include "js/GCVector.h" +#include "js/Initialization.h" +#include "js/Modules.h" +#include "js/Promise.h" +#include "js/Realm.h" +#include "js/SourceText.h" +#include "js/TypeDecls.h" +#include "js/Warnings.h" +#include "jsapi.h" +#include "jsfriendapi.h" + +namespace smw { + +using UniqueChars = mozilla::UniquePtr; + +bool INITIALIZED = false; +JS::PersistentRootedObject GLOBAL; + +static JSClass global_class = { + "global", + JSCLASS_GLOBAL_FLAGS, + &JS::DefaultGlobalClassOps +}; + +/** + * Compile the given JS source as a module in the context of the given global. + * + * Takes ownership of `jsSource`. + * + * Does not take ownership of `jsFileName`. + * + * Sets `outModule` to the resulting source text module record object. + */ +bool compile_js_module(JSContext *cx, + const char *jsFileName, + char *jsSource, + size_t jsSourceLen, + JS::MutableHandleObject outModule) { + JS::CompileOptions copts(cx); + copts + .setFileAndLine(jsFileName, 1) + .setNoScriptRval(true) + .setForceFullParse(); + + JS::SourceText srcBuf; + if (!srcBuf.init(cx, jsSource, jsSourceLen, JS::SourceOwnership::TakeOwnership)) { + return false; + } + + JS::RootedObject module(cx); + + // Disabling generational GC during compilation seems to slightly reduce + // the number of pages touched post-wizening. (Whereas disabling it + // during execution meaningfully increases it, which is why this is + // scoped to just compilation.) + JS::AutoDisableGenerationalGC noGgc(cx); + module = JS::CompileModule(cx, copts, srcBuf); + if (!module) { + return false; + } + + outModule.set(module); + return true; +} + +/** + * A synthesized module that exports `JSNative` functions. + */ +struct SynthesizedModule { + JS::Heap moduleName; + JS::Heap moduleObject; + + SynthesizedModule(JS::HandleString moduleName, JS::HandleObject moduleObject) + : moduleName(moduleName) + , moduleObject(moduleObject) + { } + + void trace(JSTracer* tracer) { + JS::TraceEdge(tracer, &moduleObject, "SynthesizedModule.moduleObject"); + } +}; + +JS::PersistentRooted> MODULES; + +JSObject* module_resolve_hook(JSContext *cx, + JS::HandleValue referencing_private, + JS::HandleObject module_request) { + JS::RootedString specifier(cx, JS::GetModuleRequestSpecifier(cx, module_request)); + if (!specifier) { + abort(cx, "failed to get module request specifier"); + } + + size_t len = MODULES.length(); + for (size_t i = 0; i < len; i++) { + JS::RootedObject it_module(cx, MODULES[i].get().moduleObject); + JS::RootedString it_name(cx, MODULES[i].get().moduleName); + int32_t result = 0; + if (!JS_CompareStrings(cx, it_name, specifier, &result)) { + abort(cx, "failed to compare module specifier to registered module name"); + } + if (result == 0) { + return it_module.get(); + } + } + + + JS::UniqueChars utf8 = JS_EncodeStringToUTF8(cx, specifier); + if (!utf8) { + JS_ReportErrorASCII(cx, "failed to find module import"); + return nullptr; + } + JS_ReportErrorASCII(cx, "failed to find module import: `%s`", utf8.get()); + return nullptr; +} + +JS::RealmOptions make_realm_options() { + JS::RealmOptions options; + options + .creationOptions() + .setStreamsEnabled(true) + .setReadableByteStreamsEnabled(true) + .setBYOBStreamReadersEnabled(true) + .setReadableStreamPipeToEnabled(true) + .setWritableStreamsEnabled(true) + .setIteratorHelpersEnabled(true) + .setWeakRefsEnabled(JS::WeakRefSpecifier::EnabledWithoutCleanupSome); + return options; +} + +bool init_js(JSContext *cx) { + if (!js::UseInternalJobQueues(cx)) { + return false; + } + + if (!JS::InitSelfHostedCode(cx)) { + return false; + } + + JS::RealmOptions options = make_realm_options(); + + JS::DisableIncrementalGC(cx); + + JS::RootedObject global(cx, JS_NewGlobalObject(cx, &global_class, nullptr, + JS::FireOnNewGlobalHook, options)); + if (!global) { + return false; + } + + JS::EnterRealm(cx, global); + + if (!JS::InitRealmStandardClasses(cx)) { + return false; + } + + // JS::SetPromiseRejectionTrackerCallback(cx, rejection_tracker); + + JS::SetModuleResolveHook(JS_GetRuntime(cx), module_resolve_hook); + + GLOBAL.init(cx, global); + return true; +} + +// static void report_warning(JSContext *cx, JSErrorReport *report) { +// JS::PrintError(stderr, report, true); +// if (!report->isWarning()) { +// ::abort(); +// } +// } + +/** + * Initialize the JS engine and evaluate the top-level of the given JavaScript + * source. + * + * Takes ownership of its parameters. + */ +WASM_EXPORT +void SMW_initialize_engine() { + assert(!INITIALIZED); + + bool ok = true; + + ok = JS_Init(); + assert(ok && "JS_Init failed"); + + JSContext *cx = JS_NewContext(JS::DefaultHeapMaxBytes); + assert(cx != nullptr && "JS_NewContext failed"); + init_js_context(cx); + + // JS::SetWarningReporter(cx, report_warning); + + if (!init_js(cx)) { + abort(cx, "initializing the JavaScript engine failed"); + } + + init_operands(cx); + + MODULES.init(cx); + INITIALIZED = true; +} + +class ModuleBuilder { + JS::PersistentRootedString moduleName; + JS::PersistentRooted exports; + +public: + /** + * Construct a new `ModuleBuilder` and take ownership of `moduleName`. + */ + ModuleBuilder(JSContext *cx, JS::HandleString moduleName) + : moduleName(cx, moduleName) + , exports(cx, cx) + { + assert(moduleName && "moduleName must not be nullptr"); + } + + /** + * Add an exported function to this module and take ownership of `funcName`. + */ + void add_export(const char *funcName, size_t funcNameLen, JSNative func, unsigned numArgs) { + assert(funcName && "function name must not be nullptr"); + assert(funcNameLen > 0 && "function name length must be greater than zero"); + assert(func && "the function must not be nullptr"); + + JSContext *cx = get_js_context(); + + JS::RootedString jsFuncName(cx, JS_NewStringCopyN(cx, funcName, funcNameLen)); + if (!jsFuncName) { + abort(cx, "failed to create new JS string"); + } + + JS::RootedId funcNameId(cx); + if (!JS_StringToId(cx, jsFuncName, &funcNameId)) { + abort(cx, "failed to convert string to id"); + } + + JS::RootedFunction jsFunc(cx, JS_NewFunction(cx, func, numArgs, 0, funcName)); + if (!jsFunc) { + abort(cx, "failed to create new JS function"); + } + + JS::RootedObject jsFuncObj(cx, JS_GetFunctionObject(jsFunc)); + assert(jsFuncObj && "getting function object is infallible"); + JS::RootedValue jsFuncVal(cx, JS::ObjectValue(*jsFuncObj)); + + if (!exports.append(JS::IdValuePair(funcNameId, jsFuncVal))) { + abort(cx, "failed to append export to exports list"); + } + } + + void finish() { + JSContext *cx = get_js_context(); + + JS::RootedObject module(cx, JS::CreateModule(cx, exports)); + if (!module) { + abort(cx, "failed to create synthetic module"); + } + + if (!MODULES.append(SynthesizedModule(moduleName, module))) { + abort(cx, "failed to append to MODULES"); + } + + delete this; + } +}; + +WASM_EXPORT +ModuleBuilder *SMW_new_module_builder(char *module_name, size_t module_name_len) { + auto unique_module_name = UniqueChars(module_name); + + JSContext *cx = get_js_context(); + + JS::RootedString js_module_name(cx, JS_NewStringCopyN(cx, unique_module_name.get(), module_name_len)); + if (!js_module_name) { + abort(cx, "failed to allocate JS string"); + } + + auto b = new ModuleBuilder(cx, js_module_name); + if (!b) { + abort(cx, "failed to create new ModuleBuilder"); + } + + return b; +} + +WASM_EXPORT +void SMW_module_builder_add_export(ModuleBuilder *builder, + char *funcName, + size_t funcNameLen, + JSNative func, + unsigned numArgs) { + assert(builder && "builder must not be nullptr"); + assert(funcName && "funcName must not be nullptr"); + assert(funcNameLen > 0 && "funcNameLen must be greater than 0"); + assert(func && "func must not be nullptr"); + + auto uniqFuncName = UniqueChars(funcName); + builder->add_export(uniqFuncName.get(), funcNameLen, func, numArgs); +} + +WASM_EXPORT +void SMW_finish_module_builder(ModuleBuilder *builder) { + builder->finish(); +} + +WASM_EXPORT +void SMW_eval_module(char *jsFileName, char *jsSource, size_t jsSourceLen) { + JSContext *cx = get_js_context(); + + assert(GLOBAL && "GLOBAL should be initialized"); + JS::RootedObject global(cx, GLOBAL); + JSAutoRealm autoRealm(cx, global); + + JS::RootedObject module(cx); + if (!compile_js_module(cx, jsFileName, jsSource, jsSourceLen, &module)) { + abort(cx, "module compilation failed"); + } + + if (!JS::ModuleInstantiate(cx, module)) { + abort(cx, "failed to instantiate module"); + } + + JS::RootedValue result(cx); + if (!JS::ModuleEvaluate(cx, module, &result)) { + abort(cx, "failed to evaluate module"); + } + + // TODO: if `result` is a promise because of top-level await, then don't + // return until the micro task queue is empty. + if (result.isObject()) { + JS::RootedObject resultObj(cx, &result.toObject()); + if (!JS::IsPromiseObject(resultObj)) { + goto done_handling_promise; + } + switch (JS::GetPromiseState(resultObj)) { + case JS::PromiseState::Fulfilled: { + JS::RootedValue promiseResolution(cx, JS::GetPromiseResult(resultObj)); + break; + } + case JS::PromiseState::Rejected: { + JS::RootedValue promiseRejection(cx, JS::GetPromiseResult(resultObj)); + JS_SetPendingException(cx, promiseRejection); + abort(cx, "module evaluation failed"); + } + case JS::PromiseState::Pending: { + abort(cx, "module evaluation returned a pending promise, but top-level await isn't enabled yet"); + } + default: + abort(cx, "module evaluation returned a promise in an unknown promise state"); + } + } + + done_handling_promise: + + init_user_module(cx, module); + + JS::PrepareForFullGC(cx); + JS::NonIncrementalGC(cx, JS::GCOptions::Shrink, JS::GCReason::API); + + free(jsFileName); +} + +} // namespace smw diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/malloc.cpp b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/malloc.cpp new file mode 100644 index 0000000..07900fb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/malloc.cpp @@ -0,0 +1,18 @@ +#include +#include + +#include "smw/abort.h" +#include "smw/wasm.h" + +namespace smw { + +WASM_EXPORT +void* SMW_malloc(size_t size) { + auto p = malloc(size); + if (p == nullptr) { + abort("out of memory"); + } + return p; +} + +} // namespace smw diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/mozconfig b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/mozconfig new file mode 100644 index 0000000..5d1eda5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/spidermonkey-wasm/mozconfig @@ -0,0 +1,27 @@ +WASI_SDK="$(pwd)/../wasi-sdk-12.0" + +CC="$WASI_SDK/bin/clang --sysroot=$WASI_SDK/share/wasi-sysroot" +CXX="$WASI_SDK/bin/clang++ --sysroot=$WASI_SDK/share/wasi-sysroot" +AR="$WASI_SDK/bin/ar" + +HOST_CC=gcc +HOST_CXX=g++ + +ac_add_options --enable-application=js +ac_add_options --target=wasm32-unknown-wasi + +ac_add_options --without-system-zlib +ac_add_options --without-intl-api + +ac_add_options --disable-jit +ac_add_options --disable-shared-js +ac_add_options --disable-shared-memory + +# Comment out `--enable-optimize` and `--disable-debug` and then uncomment +# `--enable-debug` to switch to debug builds of SpiderMonkey. Also update +# `DEFINE=` in the `Makefile`. +ac_add_options --enable-optimize +ac_add_options --disable-debug +# ac_add_options --enable-debug + +mk_add_options AUTOCLOBBER=1 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/src/data_segments.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/src/data_segments.rs new file mode 100644 index 0000000..5c9ff10 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/src/data_segments.rs @@ -0,0 +1,72 @@ +//! Assigning static locations for data segments we will emit in the glue Wasm +//! module. + +use std::convert::TryFrom; + +#[derive(Debug)] +pub struct DataSegments { + data: wasm_encoder::DataSection, + next_offset: u32, + memory: u32, +} + +impl DataSegments { + /// Create a new collection of data segments for the given memory. + pub fn new(memory: u32) -> DataSegments { + DataSegments { + data: wasm_encoder::DataSection::new(), + next_offset: 0, + memory, + } + } + + /// Add a new segment to this `DataSegments`, returning the assigned offset + /// in memory. + pub fn add(&mut self, segment: S) -> u32 + where + S: IntoIterator, + S::IntoIter: ExactSizeIterator, + { + let segment = segment.into_iter(); + let offset = self.reserve_space(u32::try_from(segment.len()).unwrap()); + self.data.active( + self.memory, + &wasm_encoder::Instruction::I32Const(offset as i32), + segment, + ); + offset + } + + /// Reserve space in memory but don't emit any data segment to initialize + /// it. + /// + /// This effectively lets you add zero-initialized data segments, reserve + /// space for return pointer areas, or define shadow stack regions. + pub fn reserve_space(&mut self, num_bytes: u32) -> u32 { + // Leave an empty byte between each data segment. This helps when + // staring at disassemblies and heap dumps. + self.next_offset += 1; + + let offset = self.next_offset; + self.next_offset += num_bytes; + + offset + } + + /// Get the memory type required to hold these data segments. + pub fn memory_type(&self) -> wasm_encoder::MemoryType { + const WASM_PAGE_SIZE: u32 = 65_536; + wasm_encoder::MemoryType { + minimum: ((self.next_offset + WASM_PAGE_SIZE - 1) / WASM_PAGE_SIZE).into(), + maximum: None, + memory64: false, + } + } + + /// Take the constructed data section. + /// + /// No more data segments should be added after this is called. + pub fn take_data(&mut self) -> wasm_encoder::DataSection { + std::mem::replace(&mut self.data, wasm_encoder::DataSection::new()) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/src/lib.rs new file mode 100644 index 0000000..71c29e7 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/src/lib.rs @@ -0,0 +1,2234 @@ +//! Interface types bindings generator support for `spidermonkey.wasm`. + +#![deny(missing_docs)] + +mod data_segments; + +use data_segments::DataSegments; +use heck::SnakeCase; +use lazy_static::lazy_static; +use std::borrow::Cow; +use std::convert::TryFrom; +use std::ops::Range; +use std::path::PathBuf; +use std::{collections::HashMap, mem}; +use wasm_encoder::Instruction; +use wit_bindgen_gen_core::{ + wit_parser::{ + abi::{self, AbiVariant, WasmSignature, WasmType}, + Docs, Enum, Expected, Flags, Function, Interface, Record, ResourceId, SizeAlign, Tuple, + Type, TypeId, Union, Variant, + }, + Direction, Files, Generator, +}; + +#[allow(missing_docs)] +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + /// The path to the JavaScript module. + pub js: PathBuf, + #[cfg_attr(feature = "structopt", structopt(long))] + pub import_spidermonkey: bool, +} + +#[allow(missing_docs)] +impl Opts { + pub fn build<'a>(self, js_source: impl Into>) -> SpiderMonkeyWasm<'a> { + let mut builder = SpiderMonkeyWasm::new(self.js, js_source); + builder.import_spidermonkey(self.import_spidermonkey); + builder + } +} + +lazy_static! { + /// Functions exported from `spidermonkey.wasm` + static ref SMW_EXPORTS: Vec<(&'static str, WasmSignature)> = vec![ + ( + "_initialize", + WasmSignature { + params: vec![], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "canonical_abi_free", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "canonical_abi_realloc", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32], + results: vec![WasmType::I32], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_initialize_engine", + WasmSignature { + params: vec![], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_new_module_builder", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32], + results: vec![WasmType::I32], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_module_builder_add_export", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_finish_module_builder", + WasmSignature { + params: vec![WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_eval_module", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_malloc", + WasmSignature { + params: vec![WasmType::I32], + results: vec![WasmType::I32], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_fill_operands", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + } + ), + ( + "SMW_clear_operands", + WasmSignature { + params: vec![], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_push_arg", + WasmSignature { + params: vec![WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_call", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32, WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_push_return_value", + WasmSignature { + params: vec![WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_finish_returns", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_i32_from_u32", + WasmSignature { + params: vec![WasmType::I32], + results: vec![WasmType::I32], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_u32_from_i32", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_string_canon_lower", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_string_canon_lift", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_spread_into_array", + WasmSignature { + params: vec![WasmType::I32], + results: vec![WasmType::I32], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_get_array_element", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_array_push", + WasmSignature { + params: vec![WasmType::I32, WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "SMW_new_array", + WasmSignature { + params: vec![WasmType::I32], + results: vec![], + retptr: false, + indirect_params: false, + }, + ), + ( + "dump_i32", + WasmSignature { + params: vec![WasmType::I32], + results: vec![WasmType::I32], + retptr: false, + indirect_params: false, + }, + ), + ]; +} + +/// The `spidermonkey.wasm` bindings generator. +/// +/// ## Code Shape +/// +/// The output is a single Wasm file that imports and exports the functions +/// defined in the given WIT files and additionally +/// +/// * embeds or imports (configurable) a `spidermonkey.wasm` instance, and +/// +/// * exports a `wizer.initialize` function that initializes SpiderMonkey and +/// evaluates the top level of the JavaScript. +/// +/// ### Initialization +/// +/// As an API contract, the `wizer.initialize` function must be invoked before +/// any other function. It must only be invoked once. +/// +/// The initialization function performs the following tasks: +/// +/// * Calls `spidermonkey.wasm`'s `_initialize` function, which runs C++ global +/// contructors. +/// +/// * `malloc`s space in `spidermonkey.wasm`'s linear memory and copies the +/// JavaScript source code from its linear memory into the malloc'd space. +/// +/// * Evaluates the JavaScript source, compiling it to bytecode and initializing +/// globals and defining top-level functions in the process. +/// +/// ### Imports +/// +/// By the time an imported WIT function is called, we have the following +/// layers of code on the stack, listed from older to younger frames: +/// +/// * User JS code (inside `spidermonkey.wasm`'s internal JS stack) +/// +/// This is the user's JavaScript code that is running inside of +/// `spidermonkey.wasm` and which wants to call an external, imported function +/// that is described with WIT. +/// +/// * Import glue Wasm code (on the Wasm stack) +/// +/// This is a synthesized Wasm function that understands both the canonical +/// ABI and the SpiderMonkey API. It translates outgoing arguments from +/// SpiderMonkey values into the canonical ABI representation, calls the +/// actual imported Wasm function, and then translates the incoming results +/// from the canonical ABI representation into SpiderMonkey values. +/// +/// * Imported function (on the Wasm Stack) +/// +/// This is the actual Wasm function whose signature is described in WIT and +/// uses the canonical ABI. +/// +/// ### Exports +/// +/// By the time an exported JS function that implements a WIT signature is +/// called, we have the following frames on the stack, listed form older to +/// younger frames: +/// +/// * External caller (on the Wasm or native stack) +/// +/// This is whoever is calling our JS-implemented WIT export, using the +/// canonical ABI. This might be another Wasm module or it might be some +/// native code in the host. +/// +/// * Export glue Wasm code (on the Wasm stack) +/// +/// This is a synthesized function that understands both the canonical ABI and +/// the SpiderMonkey API. It translates incoming arguments from the canonical +/// ABI representation into SpiderMonkey values, calls the JS function that +/// implements this export with those values, and then translates the +/// function's outgoing results from SpiderMonkey values into the canonical +/// ABI representation. +/// +/// * JavaScript function implementing the WIT signature (inside +/// `spidermonkey.wasm`'s internal stack) +/// +/// This is the user-written JavaScript function that is being exported. It +/// accepts and returns the JavaScript values that correspond to the interface +/// types used in the WIT signature. +pub struct SpiderMonkeyWasm<'a> { + /// The filename to use for the JS. + js_name: PathBuf, + + /// The JS source code. + js: Cow<'a, str>, + + return_pointer_area_size: usize, + return_pointer_area_align: usize, + + num_import_functions: Option, + num_export_functions: Option, + + import_spidermonkey: bool, + + /// Function types that we use in this Wasm module. + types: wasm_encoder::TypeSection, + + /// A map from wasm signature to its index in the `self.types` types + /// section. We use this to reuse earlier type definitions when possible. + wasm_sig_to_index: HashMap, + + /// The imports section containing the raw canonical ABI function imports + /// for each imported function we are wrapping in glue. + imports: wasm_encoder::ImportSection, + + /// The glue functions we've generated for imported canonical ABI functions + /// thus far. + import_glue_fns: Vec, + + /// A map from `module_name -> func_name -> (index, num_args)`. + import_fn_name_to_index: HashMap>, + + exports: wasm_encoder::ExportSection, + + /// The glue functions we've generated for exported canonical ABI functions + /// thus far, and their type index. + export_glue_fns: Vec<(wasm_encoder::Function, u32)>, + + data_segments: DataSegments, + + sizes: SizeAlign, + function_names: Vec<(u32, String)>, + local_names: Vec<(u32, wasm_encoder::NameMap)>, +} + +impl<'a> SpiderMonkeyWasm<'a> { + /// Construct a new `SpiderMonkeyWasm` bindings generator using the given + /// JavaScript module. + pub fn new(js_name: impl Into, js: impl Into>) -> Self { + let js_name = js_name.into(); + let js = js.into(); + SpiderMonkeyWasm { + js_name, + js, + return_pointer_area_size: 0, + return_pointer_area_align: 0, + num_import_functions: None, + num_export_functions: None, + import_spidermonkey: false, + types: wasm_encoder::TypeSection::new(), + wasm_sig_to_index: Default::default(), + imports: wasm_encoder::ImportSection::new(), + import_glue_fns: Default::default(), + import_fn_name_to_index: Default::default(), + exports: wasm_encoder::ExportSection::new(), + export_glue_fns: Default::default(), + data_segments: DataSegments::new(1), + sizes: Default::default(), + function_names: Vec::new(), + local_names: Vec::new(), + } + } + + /// Configure how `spidermonkey.wasm` is linked. + /// + /// By default, the whole `spidermonkey.wasm` module is embedded inside our + /// generated glue module's `module` section, and then instantiated in the + /// `instance` section. + /// + /// If `import` is `true`, then `spidermonkey.wasm` is not embedded into the + /// generated glue module. Instead, the glue module imports a + /// `spidermonkey.wasm` instance. + pub fn import_spidermonkey(&mut self, import: bool) { + self.import_spidermonkey = import; + } + + fn intern_type(&mut self, wasm_sig: WasmSignature) -> u32 { + if let Some(idx) = self.wasm_sig_to_index.get(&&wasm_sig) { + return *idx; + } + + let idx = self.types.len(); + + self.types.function( + wasm_sig.params.iter().copied().map(convert_ty), + wasm_sig.results.iter().copied().map(convert_ty), + ); + + self.wasm_sig_to_index.insert(wasm_sig, idx); + + idx + } + + fn link_spidermonkey_wasm( + &mut self, + modules: &mut wasm_encoder::ModuleSection, + instances: &mut wasm_encoder::InstanceSection, + aliases: &mut wasm_encoder::AliasSection, + ) { + if self.import_spidermonkey { + // Import an instance that exports all the expected + // `spidermonkey.wasm` things. + let exports: Vec<_> = SMW_EXPORTS + .iter() + .map(|(name, sig)| { + let idx = self.intern_type(sig.clone()); + (*name, wasm_encoder::EntityType::Function(idx)) + }) + .chain(Some(( + "memory", + wasm_encoder::EntityType::Memory(wasm_encoder::MemoryType { + minimum: 0, + maximum: None, + memory64: false, + }), + ))) + .chain(Some(( + "__indirect_function_table", + wasm_encoder::EntityType::Table(wasm_encoder::TableType { + element_type: wasm_encoder::ValType::FuncRef, + minimum: 0, + maximum: None, + }), + ))) + .collect(); + let instance_type_index = self.types.len(); + self.types.instance(exports); + self.imports.import( + "spidermonkey", + None, + wasm_encoder::EntityType::Instance(instance_type_index), + ); + } else { + // Embded `spidermonkey.wasm` in the modules section and then + // instantiate it. This will involve adding its imports to our + // import section and fowarding them along. + let _ = (modules, instances); + todo!() + } + + // Regardless whether we imported an instance or instantiated an embedded + // module, we now have an instance of `spidermonkey.wasm`. Alias its + // exported functions and exported memory into this module's index + // spaces. + let instance_index = u32::try_from(self.import_fn_name_to_index.len()).unwrap(); + aliases.instance_export(instance_index, wasm_encoder::ItemKind::Memory, "memory"); + aliases.instance_export( + instance_index, + wasm_encoder::ItemKind::Table, + "__indirect_function_table", + ); + for (name, _) in &*SMW_EXPORTS { + aliases.instance_export(instance_index, wasm_encoder::ItemKind::Function, name); + let idx = self.spidermonkey_import(name); + self.function_names.push((idx, name.to_string())); + } + } + + /// Malloc `size` bytes and save the result to `local`. + /// + /// Note that `SMW_malloc` will never return `nullptr`. + /// + /// ```wat + /// (local.set ${local} (call $SMW_malloc (i32.const ${size}))) + /// ``` + fn malloc_static_size<'b, F>(&mut self, func: &mut F, size: u32, result_local: u32) + where + F: InstructionSink<'b>, + { + // [] + func.instruction(Instruction::I32Const(size as _)); + // [i32] + func.instruction(Instruction::Call(self.spidermonkey_import("SMW_malloc"))); + // [i32] + func.instruction(Instruction::LocalSet(result_local)); + // [] + } + + /// Malloc `size` bytes and save the result to `local`. Trap if `malloc` + /// returned `nullptr`. + /// + /// Note that `SMW_malloc` will never return `nullptr`. + /// + /// ```wat + /// (local.set ${result_local} (call $malloc (local.get ${size_local}))) + /// ``` + fn malloc_dynamic_size<'b, F>(&mut self, func: &mut F, size_local: u32, result_local: u32) + where + F: InstructionSink<'b>, + { + // [] + func.instruction(Instruction::LocalGet(size_local)); + // [i32] + func.instruction(Instruction::Call(self.spidermonkey_import("SMW_malloc"))); + // [i32] + func.instruction(Instruction::LocalSet(result_local)); + // [] + } + + /// Copy data from the root glue module's linear memory into + /// `spidermonkey.wasm`'s linear memory: + /// + /// ```wat + /// (memory.copy 0 1 (local.get ${to_local}) + /// (i32.const ${from_offset}) + /// (i32.const ${len})) + /// ``` + fn copy_to_smw<'b, F>(&self, func: &mut F, from_offset: u32, to_local: u32, len: u32) + where + F: InstructionSink<'b>, + { + // [] + func.instruction(Instruction::LocalGet(to_local)); + // [i32] + func.instruction(Instruction::I32Const(from_offset as _)); + // [i32 i32] + func.instruction(Instruction::I32Const(len as _)); + // [i32 i32 i32] + func.instruction(Instruction::MemoryCopy { + src: GLUE_MEMORY, + dst: SM_MEMORY, + }); + // [] + } + + fn clear_js_operands<'b, F>(&self, func: &mut F) + where + F: InstructionSink<'b>, + { + // [] + func.instruction(Instruction::Call( + self.spidermonkey_import("SMW_clear_operands"), + )); + // [] + } + + fn define_wizer_initialize( + &mut self, + funcs: &mut wasm_encoder::FunctionSection, + code: &mut wasm_encoder::CodeSection, + js_name_offset: u32, + js_name_len: u32, + js_offset: u32, + js_len: u32, + ) { + assert_eq!(funcs.len(), code.len()); + + let wizer_init_index = self.wit_import_functions_len() + + u32::try_from(SMW_EXPORTS.len()).unwrap() + + funcs.len(); + self.function_names + .push((wizer_init_index, format!("wizer.initialize"))); + + let ty_index = self.intern_type(WasmSignature { + params: vec![], + results: vec![], + retptr: false, + indirect_params: false, + }); + funcs.function(ty_index); + + let locals = vec![(7, wasm_encoder::ValType::I32)]; + let js_name_local = 0; + let js_local = 1; + let module_name_local = 2; + let module_builder_local = 3; + let table_size_local = 4; + let func_name_local = 5; + let ret_ptr_local = 6; + + let mut local_names = wasm_encoder::NameMap::new(); + local_names.append(js_name_local, "js_name"); + local_names.append(js_local, "js"); + local_names.append(module_name_local, "module_name"); + local_names.append(module_builder_local, "module_builder"); + local_names.append(table_size_local, "table_size"); + local_names.append(func_name_local, "func_name"); + local_names.append(ret_ptr_local, "ret_ptr"); + self.local_names.push((wizer_init_index, local_names)); + + let mut wizer_init = wasm_encoder::Function::new(locals); + + // Call `_initialize` because that must be called before any other + // exports per the WASI reactor ABI. + let init_index = self.spidermonkey_import("_initialize"); + wizer_init.instruction(&Instruction::Call(init_index)); + + // Malloc space in `spidermonkey.wasm`'s linear memory for the JS file + // name and the JS source. + self.malloc_static_size(&mut wizer_init, js_name_len, js_name_local); + self.malloc_static_size(&mut wizer_init, js_len, js_local); + + // Copy the data into the freshly allocated regions. + self.copy_to_smw(&mut wizer_init, js_name_offset, js_name_local, js_name_len); + self.copy_to_smw(&mut wizer_init, js_offset, js_local, js_len); + + // Allocate space in the `spidermonkey.wasm` memory for the return + // pointer area and save it to the return pointer global. + // + // TODO: handle `self.return_pointer_area_align` here + if self.return_pointer_area_size > 0 { + self.malloc_static_size( + &mut wizer_init, + u32::try_from(self.return_pointer_area_size).unwrap(), + ret_ptr_local, + ); + // [] + wizer_init.instruction(&Instruction::LocalGet(ret_ptr_local)); + // [i32] + wizer_init.instruction(&Instruction::GlobalSet(RET_PTR_GLOBAL)); + // [] + } + + // Call `SMW_initialize_engine`: + // + // (call $SMW_initialize_engine) + let smw_initialize_engine = self.spidermonkey_import("SMW_initialize_engine"); + wizer_init.instruction(&Instruction::Call(smw_initialize_engine)); + + // Define a JS module for each WIT module that is imported. This JS + // module will export each of our generated glue functions for that WIT + // module. + let smw_new_module_builder = self.spidermonkey_import("SMW_new_module_builder"); + let import_fn_name_to_index = + std::mem::replace(&mut self.import_fn_name_to_index, Default::default()); + for (module, funcs) in &import_fn_name_to_index { + // Malloc space for the module name. + self.malloc_static_size( + &mut wizer_init, + u32::try_from(module.len()).unwrap(), + module_name_local, + ); + + // Copy the module name into the malloc'd space. + let module_offset = self.data_segments.add(module.as_bytes().iter().copied()); + self.copy_to_smw( + &mut wizer_init, + module_offset, + module_name_local, + u32::try_from(module.len()).unwrap(), + ); + + // Call `SMW_new_module_builder`, passing it the module name: + // + // (call $SMW_new_module_builder (local.get ${module_name}) + // (i32.const ${module.len()})) + // local.set ${module_builder} + wizer_init + // [] + .instruction(&Instruction::LocalGet(module_name_local)) + // [i32] + .instruction(&Instruction::I32Const(i32::try_from(module.len()).unwrap())) + // [i32 i32] + .instruction(&Instruction::Call(smw_new_module_builder)) + // [i32] + .instruction(&Instruction::LocalSet(module_builder_local)); + // [] + + // Grow enough space in the function table for the functions we will + // add to it. Check for failure to allocate and trap if so. + // + // (table.grow (ref.null) (i32.const ${funcs.len()})) + // local.tee ${table_size} + // i32.const -1 + // i32.eq + // if + // unreachable + // end + wizer_init + // [] + .instruction(&Instruction::RefNull(wasm_encoder::ValType::FuncRef)) + // [funcref] + .instruction(&Instruction::I32Const(i32::try_from(funcs.len()).unwrap())) + // [funcref i32] + .instruction(&Instruction::TableGrow { table: 0 }) + // [i32] + .instruction(&Instruction::LocalTee(table_size_local)) + // [i32] + .instruction(&Instruction::I32Const(-1)) + // [i32 i32] + .instruction(&Instruction::I32Eq) + // [i32] + .instruction(&Instruction::If(wasm_encoder::BlockType::Empty)) + // [] + .instruction(&Instruction::Unreachable) + // [] + .instruction(&Instruction::End); + // [] + + for (i, (func, (func_index, num_args))) in funcs.iter().enumerate() { + // Malloc space for the function's name. + self.malloc_static_size( + &mut wizer_init, + u32::try_from(func.len()).unwrap(), + func_name_local, + ); + + // Copy the function's name into the malloc'd space. + let func_name_offset = self.data_segments.add(func.as_bytes().iter().copied()); + self.copy_to_smw( + &mut wizer_init, + func_name_offset, + func_name_local, + u32::try_from(func.len()).unwrap(), + ); + + // Set `table[orig_size + i]` to our synthesized import glue + // function: + // + // (table.set (i32.add (i32.const ${i}) (local.get ${table_size})) + // (ref.func ${func_index})) + let glue_func_index = self.wit_import_glue_fn(*func_index); + wizer_init + // [] + .instruction(&Instruction::I32Const(i32::try_from(i).unwrap())) + // [i32] + .instruction(&Instruction::LocalGet(table_size_local)) + // [i32 i32] + .instruction(&Instruction::I32Add) + // [i32] + .instruction(&Instruction::RefFunc(glue_func_index)) + // [i32 funcref] + .instruction(&Instruction::TableSet { table: 0 }); + // [] + + // Call `SMW_module_builder_add_export` passing the index of the + // function that we just inserted into the table: + // + // (call $SMW_module_builder_add_export (local.get ${module_builder}) + // (local.get ${func_name}) + // (i32.const ${func.len()}) + // (i32.add (i32.const ${i}) (local.get ${table_size})) + // (i32.const ${num_args})) + let smw_module_builder_add_export = + self.spidermonkey_import("SMW_module_builder_add_export"); + wizer_init + // [] + .instruction(&Instruction::LocalGet(module_builder_local)) + // [i32] + .instruction(&Instruction::LocalGet(func_name_local)) + // [i32 i32] + .instruction(&Instruction::I32Const(i32::try_from(func.len()).unwrap())) + // [i32 i32 i32] + .instruction(&Instruction::I32Const(i32::try_from(i).unwrap())) + // [i32 i32 i32 i32] + .instruction(&Instruction::LocalGet(table_size_local)) + // [i32 i32 i32 i32 i32] + .instruction(&Instruction::I32Add) + // [i32 i32 i32 i32] + .instruction(&Instruction::I32Const(i32::try_from(*num_args).unwrap())) + // [i32 i32 i32 i32 i32] + .instruction(&Instruction::Call(smw_module_builder_add_export)); + // [] + } + + // Call `SMW_finish_module_builder` to register the module: + // + // (call $SMW_finish_module_builder (local.get ${module_builder})) + let smw_finish_module_builder = self.spidermonkey_import("SMW_finish_module_builder"); + wizer_init + // [] + .instruction(&Instruction::LocalGet(module_builder_local)) + // [i32] + .instruction(&Instruction::Call(smw_finish_module_builder)); + // [] + } + + // Call `SMW_eval_module`, passing it the pointers to the JS file name + // and JS source: + // + // (call $SMW_eval_module (local.get 0) (local.get 1) (i32.const ${js_len})) + let smw_eval_module = self.spidermonkey_import("SMW_eval_module"); + wizer_init + // [] + .instruction(&Instruction::LocalGet(js_name_local)) + // [i32] + .instruction(&Instruction::LocalGet(js_local)) + // [i32 i32] + .instruction(&Instruction::I32Const(js_len as i32)) + // [i32 i32 i32] + .instruction(&Instruction::Call(smw_eval_module)); + // [] + + wizer_init.instruction(&Instruction::End); + code.function(&wizer_init); + + self.exports.export( + "wizer.initialize", + wasm_encoder::Export::Function(wizer_init_index), + ); + } +} + +// ### Function Index Space +// +// The generated glue module's function index space is laid out as follows: +// +// ```text +// |wit imports...|spidermonkey.wasm imports...|import glue...|export glue...|wizer.initialize| +// ``` +impl SpiderMonkeyWasm<'_> { + /// Get the number of imported WIT functions. + fn wit_import_functions_len(&self) -> u32 { + self.num_import_functions + .expect("must call `preprocess_all` before generating bindings") + } + + /// Get the function index for the i^th WIT import. + fn wit_import(&self, i: u32) -> u32 { + i + } + + /// Get the function index for the given spidermonkey function. + fn spidermonkey_import(&self, name: &str) -> u32 { + self.wit_import_functions_len() + + u32::try_from( + SMW_EXPORTS + .iter() + .position(|(n, _)| *n == name) + .unwrap_or_else(|| panic!("unknown `spidermonkey.wasm` export: {}", name)), + ) + .unwrap() + } + + /// Get the function index where WIT import glue functions start. + fn wit_import_glue_fns_start(&self) -> u32 { + self.wit_import_functions_len() + u32::try_from(SMW_EXPORTS.len()).unwrap() + } + + /// Get the range of indices for our synthesized glue functions for WIT + /// imports. + fn wit_import_glue_fn_range(&self) -> Range { + let start = self.wit_import_glue_fns_start(); + let end = self.wit_export_start(); + start..end + } + + /// Get the function index for the i^th synthesized glue function for a WIT + /// import. + fn wit_import_glue_fn(&self, i: u32) -> u32 { + assert!( + i < self.wit_import_functions_len(), + "{} < {}", + i, + self.wit_import_functions_len() + ); + let start = self.wit_import_glue_fns_start(); + start + i + } + + /// Get the function index where WIT export glue functions start. + fn wit_export_start(&self) -> u32 { + self.wit_import_glue_fns_start() + self.wit_import_functions_len() + } + + fn wit_exports_len(&self) -> u32 { + self.num_export_functions + .expect("must call `preprocess_all` before generating bindings") + } + + /// Get the function index for the i^th WIT export. + fn wit_export(&self, i: u32) -> u32 { + assert!(i < self.wit_exports_len()); + self.wit_export_start() + i + } +} + +impl Generator for SpiderMonkeyWasm<'_> { + fn preprocess_all(&mut self, imports: &[Interface], exports: &[Interface]) { + assert!( + self.num_import_functions.is_none() && self.num_export_functions.is_none(), + "must call `preprocess_all` exactly once" + ); + assert!( + exports.len() <= 1, + "only one exported interface is currently supported" + ); + self.num_import_functions = + Some(u32::try_from(imports.iter().map(|i| i.functions.len()).sum::()).unwrap()); + self.num_export_functions = + Some(u32::try_from(exports.iter().map(|i| i.functions.len()).sum::()).unwrap()); + } + + fn preprocess_one(&mut self, iface: &Interface, _dir: Direction) { + self.sizes.fill(iface); + } + + fn type_record( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ) { + let _ = (iface, id, name, record, docs); + todo!() + } + + fn type_tuple( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + tuple: &Tuple, + docs: &Docs, + ) { + let _ = (iface, id, name, tuple, docs); + todo!() + } + + fn type_flags( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + flags: &Flags, + docs: &Docs, + ) { + let _ = (iface, id, name, flags, docs); + todo!() + } + + fn type_variant( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + variant: &Variant, + docs: &Docs, + ) { + let _ = (iface, id, name, variant, docs); + todo!() + } + + fn type_union( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + union: &Union, + docs: &Docs, + ) { + let _ = (iface, id, name, union, docs); + todo!() + } + + fn type_option( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + payload: &Type, + docs: &Docs, + ) { + let _ = (iface, id, name, payload, docs); + todo!() + } + + fn type_expected( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + expected: &Expected, + docs: &Docs, + ) { + let _ = (iface, id, name, expected, docs); + todo!() + } + + fn type_enum(&mut self, iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + let _ = (iface, id, name, enum_, docs); + todo!() + } + + fn type_resource(&mut self, iface: &Interface, ty: ResourceId) { + let _ = (iface, ty); + todo!() + } + + fn type_alias(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let _ = (iface, id, name, ty, docs); + todo!() + } + + fn type_list(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let _ = (iface, id, name, ty, docs); + todo!() + } + + fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let _ = (iface, id, name, name, ty, docs); + todo!() + } + + fn import(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + + // Add the raw Wasm import. + let wasm_sig = iface.wasm_signature(AbiVariant::GuestImport, func); + let type_index = self.intern_type(wasm_sig.clone()); + let import_fn_index = self.wit_import(self.imports.len()); + self.imports.import( + &iface.name, + Some(&func.name), + wasm_encoder::EntityType::Function(type_index), + ); + + let existing = self + .import_fn_name_to_index + .entry(iface.name.clone()) + .or_default() + .insert( + func.name.clone(), + (import_fn_index, u32::try_from(func.params.len()).unwrap()), + ); + assert!(existing.is_none()); + + self.function_names + .push((import_fn_index, format!("{}.{}", iface.name, func.name))); + + let mut bindgen = Bindgen::new(self, &wasm_sig, func, abi::LiftLower::LowerArgsLiftResults); + iface.call( + AbiVariant::GuestImport, + abi::LiftLower::LowerArgsLiftResults, + func, + &mut bindgen, + ); + let func_encoder = bindgen.finish(); + self.import_glue_fns.push(func_encoder); + } + + fn export(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + + let wasm_sig = iface.wasm_signature(AbiVariant::GuestExport, func); + let type_index = self.intern_type(wasm_sig.clone()); + let export_fn_index = self.wit_export(self.exports.len()); + self.exports + .export(&func.name, wasm_encoder::Export::Function(export_fn_index)); + self.function_names + .push((export_fn_index, format!("{}.{}", iface.name, func.name))); + + let mut bindgen = Bindgen::new(self, &wasm_sig, func, abi::LiftLower::LiftArgsLowerResults); + iface.call( + AbiVariant::GuestExport, + abi::LiftLower::LiftArgsLowerResults, + func, + &mut bindgen, + ); + let func_encoder = bindgen.finish(); + self.export_glue_fns.push((func_encoder, type_index)); + } + + fn finish_one(&mut self, _iface: &Interface, _files: &mut Files) { + // Nothing to do until wil finish all interfaces and generate our Wasm + // glue code. + } + + fn finish_all(&mut self, files: &mut Files) { + let mut module = wasm_encoder::Module::new(); + let mut modules = wasm_encoder::ModuleSection::new(); + let mut instances = wasm_encoder::InstanceSection::new(); + let mut aliases = wasm_encoder::AliasSection::new(); + let mut mems = wasm_encoder::MemorySection::new(); + let mut funcs = wasm_encoder::FunctionSection::new(); + let mut globals = wasm_encoder::GlobalSection::new(); + let mut elems = wasm_encoder::ElementSection::new(); + let mut code = wasm_encoder::CodeSection::new(); + + self.link_spidermonkey_wasm(&mut modules, &mut instances, &mut aliases); + + // Define the return pointer global. + globals.global( + wasm_encoder::GlobalType { + val_type: wasm_encoder::ValType::I32, + mutable: true, + }, + &Instruction::I32Const(0), + ); + + // Re-export `spidermonkey.wasm`'s memory and canonical ABI functions. + self.exports + .export("memory", wasm_encoder::Export::Memory(SM_MEMORY)); + self.exports.export( + "canonical_abi_free", + wasm_encoder::Export::Function(self.spidermonkey_import("canonical_abi_free")), + ); + self.exports.export( + "canonical_abi_realloc", + wasm_encoder::Export::Function(self.spidermonkey_import("canonical_abi_realloc")), + ); + + // Add the WIT function imports (add their import glue functions) to + // the module. + // + // Each of these functions has the Wasm equivalent of this function + // signature: + // + // using JSNative = bool (JSContext* cx, unsigned argc, JSValue *vp); + let js_native_type_index = self.intern_type(WasmSignature { + params: vec![ + // JSContext *cx + WasmType::I32, + // unsigned argc + WasmType::I32, + // JSValue *vp + WasmType::I32, + ], + results: vec![ + // bool + WasmType::I32, + ], + retptr: false, + indirect_params: false, + }); + for f in &self.import_glue_fns { + funcs.function(js_native_type_index); + code.function(f); + } + for (f, ty_idx) in &self.export_glue_fns { + funcs.function(*ty_idx); + code.function(f); + } + + // We will use `ref.func` to get a reference to each of our synthesized + // import glue functions, so we need to declare them as reference-able. + let func_indices: Vec = self.wit_import_glue_fn_range().collect(); + if !func_indices.is_empty() { + elems.declared( + wasm_encoder::ValType::FuncRef, + wasm_encoder::Elements::Functions(&func_indices), + ); + } + + let js_name = self.js_name.display().to_string(); + let js_name_offset = self.data_segments.add(js_name.as_bytes().iter().copied()); + let js_offset = self.data_segments.add(self.js.as_bytes().iter().copied()); + + self.define_wizer_initialize( + &mut funcs, + &mut code, + js_name_offset, + u32::try_from(js_name.len()).unwrap(), + js_offset, + u32::try_from(self.js.len()).unwrap(), + ); + + module.section(&self.types).section(&self.imports); + + if !self.import_spidermonkey { + module.section(&modules).section(&instances); + } + + mems.memory(self.data_segments.memory_type()); + let data = self.data_segments.take_data(); + + // Fill out the `names` section to assist in debugging the generated + // wasm. + let mut names = wasm_encoder::NameSection::new(); + self.function_names.sort_by_key(|a| a.0); + let mut function_names = wasm_encoder::NameMap::new(); + for (i, name) in self.function_names.iter() { + function_names.append(*i, name); + } + names.functions(&function_names); + + self.local_names.sort_by_key(|a| a.0); + let mut local_names = wasm_encoder::IndirectNameMap::new(); + for (i, names) in self.local_names.iter() { + local_names.append(*i, names); + } + names.locals(&local_names); + + let mut table_names = wasm_encoder::NameMap::new(); + table_names.append(0, "sm_function_table"); + names.tables(&table_names); + + let mut memory_names = wasm_encoder::NameMap::new(); + memory_names.append(SM_MEMORY, "sm_mem"); + memory_names.append(GLUE_MEMORY, "glue_mem"); + names.memories(&memory_names); + + module + .section(&aliases) + .section(&funcs) + .section(&mems) + .section(&globals) + .section(&self.exports) + .section(&elems) + .section(&code) + .section(&data) + .section(&names); + + let wasm = module.finish(); + + let js_file_stem = self.js_name.file_stem().unwrap_or_else(|| { + panic!( + "input JavaScript file path does not have a file stem: {}", + self.js_name.display() + ) + }); + let js_file_stem = js_file_stem.to_str().unwrap_or_else(|| { + panic!( + "input JavaScript file path is not UTF-8 representable: {}", + self.js_name.display() + ) + }); + let wasm_name = format!("{}.wasm", js_file_stem); + + files.push(&wasm_name, &wasm); + } +} + +trait InstructionSink<'a> { + fn instruction(&mut self, inst: wasm_encoder::Instruction<'a>); +} + +impl<'a> InstructionSink<'a> for wasm_encoder::Function { + fn instruction(&mut self, inst: wasm_encoder::Instruction<'a>) { + wasm_encoder::Function::instruction(self, &inst); + } +} + +impl<'a> InstructionSink<'a> for Vec> { + fn instruction(&mut self, inst: wasm_encoder::Instruction<'a>) { + self.push(inst); + } +} + +const RET_PTR_GLOBAL: u32 = 0; + +const SM_MEMORY: u32 = 0; +const GLUE_MEMORY: u32 = 1; + +fn convert_ty(ty: WasmType) -> wasm_encoder::ValType { + match ty { + WasmType::I32 => wasm_encoder::ValType::I32, + WasmType::I64 => wasm_encoder::ValType::I64, + WasmType::F32 => wasm_encoder::ValType::F32, + WasmType::F64 => wasm_encoder::ValType::F64, + } +} + +struct Bindgen<'a, 'b> { + gen: &'a mut SpiderMonkeyWasm<'b>, + sig: &'a WasmSignature, + lift_lower: abi::LiftLower, + locals: Vec, + js_count: u32, + + blocks: Vec>>, + block_results: Vec>, + + /// The `i`th JS operand that is our current iteration element, if any. + iter_elem: Vec, + + /// The Wasm local for our current iteration's base pointer, if any. + iter_base_pointer: Vec, + + /// Allocations to free after the call. + /// + /// `(local holding pointer, local holding length, alignment)` + to_free: Vec<(u32, u32, u32)>, +} + +impl<'a, 'b> Bindgen<'a, 'b> { + fn new( + gen: &'a mut SpiderMonkeyWasm<'b>, + sig: &'a WasmSignature, + func: &'a Function, + lift_lower: abi::LiftLower, + ) -> Self { + let js_count = match lift_lower { + abi::LiftLower::LiftArgsLowerResults => 0, + abi::LiftLower::LowerArgsLiftResults => u32::try_from(func.params.len()).unwrap(), + }; + + let mut insts = vec![]; + if lift_lower == abi::LiftLower::LowerArgsLiftResults && !func.params.is_empty() { + // Initialize `bindgen.cpp`'s JS value operands vector with the + // arguments given to this function + // + // [] + insts.push(Instruction::LocalGet(1)); + // [i32] + insts.push(Instruction::LocalGet(2)); + // [i32 i32] + insts.push(Instruction::Call( + gen.spidermonkey_import("SMW_fill_operands"), + )); + // [] + } + + Bindgen { + gen, + sig, + lift_lower, + locals: vec![], + js_count, + blocks: vec![insts], + block_results: vec![], + iter_elem: vec![], + iter_base_pointer: vec![], + to_free: vec![], + } + } + + fn inst(&mut self, inst: Instruction<'a>) { + self.current_block().push(inst); + } + + fn current_block(&mut self) -> &mut Vec> { + self.blocks.last_mut().unwrap() + } + + fn pop_block(&mut self) -> (Vec>, Vec) { + ( + self.blocks.pop().unwrap(), + self.block_results.pop().unwrap(), + ) + } + + /// Create a new Wasm local for this function and return its index. + fn new_local(&mut self, ty: wasm_encoder::ValType) -> u32 { + let offset = match self.lift_lower { + abi::LiftLower::LiftArgsLowerResults => self.sig.params.len(), + // `JSNative` functions take three `i32` arguments: cx, argc, and + // vp. + abi::LiftLower::LowerArgsLiftResults => 3, + }; + let idx = u32::try_from(self.locals.len() + offset).unwrap(); + self.locals.push(ty); + idx + } + + /// Get the next JS value operand. + fn next_js(&mut self) -> Operand { + let js = self.js_count; + self.js_count += 1; + Operand::Js(js) + } + + /// Finish generating these bindings and return the encoded Wasm function. + fn finish(self) -> wasm_encoder::Function { + // TODO: Coalesce contiguous locals of the same type here into the + // compact encoding, like `[(i32, 3)]` rather than `[(i32, 1), (i32, 1), + // (i32, 1)]`. + let mut f = wasm_encoder::Function::new(self.locals.into_iter().map(|l| (1, l))); + + // By the time we get here, we should have finished all nested blocks. + assert_eq!(self.blocks.len(), 1); + + for inst in &self.blocks[0] { + f.instruction(inst); + } + f.instruction(&Instruction::End); + f + } +} + +/// Operands are locals that either hold the value directly or refer to an index +/// in `bindgen.cpp`'s JS operand vector depending on if we're dealing with a JS +/// or Wasm value: +/// +/// * When we are _importing_ a function, we are lifting arguments and lowering +/// results, so `operands` always refer to the `n`th local and `results` refer +/// to the `n`th value in `bindgen.cpp`'s JS value vector. +/// +/// * When we are _exporting_ a function, we are lowering arguments and lifting +/// results, so `operands` always refer to the `n`th value in `bindgen.cpp`'s +/// JS value vector and `results` always refer to the `n`th local. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +enum Operand { + /// The `n`th JS value in `bindgen.cpp`'s operand vector. + Js(u32), + /// The `n`th Wasm local. + Wasm(u32), + /// A "unit" void type + Unit, +} + +impl Operand { + fn unwrap_js(&self) -> u32 { + match *self { + Operand::Js(js) => js, + Operand::Wasm(_) => panic!("Operand::unwrap_js on a Wasm operand"), + Operand::Unit => panic!("Operand::unwrap_js on a Unit operand"), + } + } + fn unwrap_wasm(&self) -> u32 { + match *self { + Operand::Wasm(w) => w, + Operand::Js(_) => panic!("Operand::unwrap_wasm on a JS operand"), + Operand::Unit => panic!("Operand::unwrap_wasm on a Unit operand"), + } + } + fn unwrap_unit(&self) { + match *self { + Operand::Unit => {} + Operand::Wasm(_) => panic!("Operand::unwrap_unit on a Wasm operand"), + Operand::Js(_) => panic!("Operand::unwrap_unit on a JS operand"), + } + } +} + +fn pop_wasm(operands: &mut Vec) -> u32 { + match operands.pop() { + Some(op) => op.unwrap_wasm(), + None => panic!("`pop_wasm` with an empty stack"), + } +} + +fn pop_js(operands: &mut Vec) -> u32 { + match operands.pop() { + Some(op) => op.unwrap_js(), + None => panic!("`pop_js` with an empty stack"), + } +} + +fn sm_mem_arg(offset: u32) -> wasm_encoder::MemArg { + wasm_encoder::MemArg { + offset: offset as u64, + align: 0, + memory_index: SM_MEMORY, + } +} + +impl abi::Bindgen for Bindgen<'_, '_> { + type Operand = Operand; + + fn emit( + &mut self, + _iface: &Interface, + inst: &abi::Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + match inst { + abi::Instruction::GetArg { nth } => { + let nth = u32::try_from(*nth).unwrap(); + results.push(match self.lift_lower { + abi::LiftLower::LiftArgsLowerResults => Operand::Wasm(nth), + abi::LiftLower::LowerArgsLiftResults => Operand::Js(nth), + }); + } + abi::Instruction::I32Const { val: _ } => todo!(), + abi::Instruction::Bitcasts { casts: _ } => todo!(), + abi::Instruction::ConstZero { tys: _ } => todo!(), + abi::Instruction::I32Load { offset } => { + let addr = pop_wasm(operands); + let local = self.new_local(wasm_encoder::ValType::I32); + + // [] + self.inst(Instruction::LocalGet(addr)); + // [i32] + self.inst(Instruction::I32Load(sm_mem_arg((*offset as u32).into()))); + // [i32] + self.inst(Instruction::LocalSet(local)); + // [] + + results.push(Operand::Wasm(local)); + } + abi::Instruction::I32Load8U { offset: _ } => todo!(), + abi::Instruction::I32Load8S { offset: _ } => todo!(), + abi::Instruction::I32Load16U { offset: _ } => todo!(), + abi::Instruction::I32Load16S { offset: _ } => todo!(), + abi::Instruction::I64Load { offset: _ } => todo!(), + abi::Instruction::F32Load { offset: _ } => todo!(), + abi::Instruction::F64Load { offset: _ } => todo!(), + abi::Instruction::I32Store { offset } => { + let addr = pop_wasm(operands); + let val = pop_wasm(operands); + + // [] + self.inst(Instruction::LocalGet(addr)); + // [i32] + self.inst(Instruction::LocalGet(val)); + // [i32 i32] + self.inst(Instruction::I32Store(sm_mem_arg((*offset as u32).into()))); + // [] + } + abi::Instruction::I32Store8 { offset: _ } => todo!(), + abi::Instruction::I32Store16 { offset: _ } => todo!(), + abi::Instruction::I64Store { offset: _ } => todo!(), + abi::Instruction::F32Store { offset: _ } => todo!(), + abi::Instruction::F64Store { offset: _ } => todo!(), + abi::Instruction::I32FromChar => todo!(), + abi::Instruction::I64FromU64 => todo!(), + abi::Instruction::I64FromS64 => todo!(), + abi::Instruction::I32FromU32 => { + let js = pop_js(operands); + let local = self.new_local(wasm_encoder::ValType::I32); + + // [] + self.inst(Instruction::I32Const(js as i32)); + // [i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_i32_from_u32"), + )); + // [i32] + self.inst(Instruction::LocalSet(local)); + // [] + + results.push(Operand::Wasm(local)); + } + abi::Instruction::I32FromS32 => todo!(), + abi::Instruction::I32FromU16 => todo!(), + abi::Instruction::I32FromS16 => todo!(), + abi::Instruction::I32FromU8 => todo!(), + abi::Instruction::I32FromS8 => todo!(), + abi::Instruction::F32FromFloat32 => todo!(), + abi::Instruction::F64FromFloat64 => todo!(), + abi::Instruction::S8FromI32 => todo!(), + abi::Instruction::U8FromI32 => todo!(), + abi::Instruction::S16FromI32 => todo!(), + abi::Instruction::U16FromI32 => todo!(), + abi::Instruction::S32FromI32 => todo!(), + abi::Instruction::U32FromI32 => { + let local = pop_wasm(operands); + let result = self.next_js(); + + // [] + self.inst(Instruction::LocalGet(local)); + // [i32] + self.inst(Instruction::I32Const(result.unwrap_js() as i32)); + // [i32 i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_u32_from_i32"), + )); + // [] + + results.push(result); + } + abi::Instruction::S64FromI64 => todo!(), + abi::Instruction::U64FromI64 => todo!(), + abi::Instruction::CharFromI32 => todo!(), + abi::Instruction::Float32FromF32 => todo!(), + abi::Instruction::Float64FromF64 => todo!(), + abi::Instruction::I32FromBorrowedHandle { ty: _ } => todo!(), + abi::Instruction::I32FromOwnedHandle { ty: _ } => todo!(), + abi::Instruction::HandleOwnedFromI32 { ty: _ } => todo!(), + abi::Instruction::HandleBorrowedFromI32 { ty: _ } => todo!(), + abi::Instruction::ListCanonLower { .. } => todo!(), + abi::Instruction::StringLower { realloc } => { + let js = pop_js(operands); + let ptr = self.new_local(wasm_encoder::ValType::I32); + let len = self.new_local(wasm_encoder::ValType::I32); + + // Make sure our return pointer area can hold at least two + // `u32`s, since we will use it that way with + // `SMW_{list,string}_canon_lower`. + self.gen.return_pointer_area_size = self.gen.return_pointer_area_size.max(1); + self.gen.return_pointer_area_align = self.gen.return_pointer_area_align.max(4); + + // [] + self.inst(Instruction::GlobalGet(RET_PTR_GLOBAL)); + // [i32] + self.inst(Instruction::I32Const(js as _)); + // [i32 i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_string_canon_lower"), + )); + // [] + + // Read the pointer from the return pointer area. + // + // [] + self.inst(Instruction::GlobalGet(RET_PTR_GLOBAL)); + // [i32] + self.inst(Instruction::I32Load(sm_mem_arg(0))); + // [i32] + self.inst(Instruction::LocalSet(ptr)); + // [] + + // Read the length from the return pointer area. + // + // [] + self.inst(Instruction::GlobalGet(RET_PTR_GLOBAL)); + // [i32] + self.inst(Instruction::I32Load(sm_mem_arg(4))); + // [i32] + self.inst(Instruction::LocalSet(len)); + // [] + + // If `realloc` is `None`, then we are responsible for freeing + // this pointer after the call. + if realloc.is_none() { + self.to_free.push((ptr, len, 1)); + } + + results.push(Operand::Wasm(ptr)); + results.push(Operand::Wasm(len)); + } + abi::Instruction::ListLower { element, realloc } => { + let iterable = pop_js(operands); + let (block, block_results) = self.pop_block(); + assert!(block_results.is_empty()); + let iter_elem = self.iter_elem.pop().unwrap(); + let iter_base_pointer = self.iter_base_pointer.pop().unwrap(); + + let length = self.new_local(wasm_encoder::ValType::I32); + let index = self.new_local(wasm_encoder::ValType::I32); + let ptr = self.new_local(wasm_encoder::ValType::I32); + + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + + // [] + self.inst(Instruction::I32Const(iterable as i32)); + // [i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_spread_into_array"), + )); + // [i32] + self.inst(Instruction::LocalSet(length)); + // [] + + // `malloc` space for the result. + self.gen + .malloc_dynamic_size(self.blocks.last_mut().unwrap(), length, ptr); + + // Create a new block and loop. The block is so we can branch to + // it to exit out of the loop. + // + // Also re-zero the index since the current block itself might + // be reused multiple times if it is part of a loop body. + // + // [] + self.inst(Instruction::I32Const(0)); + // [i32] + self.inst(Instruction::LocalSet(index)); + // [] + self.inst(Instruction::Block(wasm_encoder::BlockType::Empty)); + // [] + self.inst(Instruction::Loop(wasm_encoder::BlockType::Empty)); + // [] + + // Check the loop's exit condition: `index >= length`. + // + // [] + self.inst(Instruction::LocalGet(index)); + // [i32] + self.inst(Instruction::LocalGet(length)); + // [i32 i32] + self.inst(Instruction::I32GeU); + // [i32] + self.inst(Instruction::BrIf(1)); + // [] + + // Update the element for this iteration. + // + // [] + self.inst(Instruction::I32Const(iterable as i32)); + // [i32] + self.inst(Instruction::LocalGet(index)); + // [i32 32] + self.inst(Instruction::I32Const(iter_elem as i32)); + // [i32 i32 i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_get_array_element"), + )); + // [] + + // Update the base pointer for this iteration. + // + // [] + self.inst(Instruction::LocalGet(index)); + // [i32] + self.inst(Instruction::I32Const(u32::try_from(size).unwrap() as _)); + // [i32 i32] + self.inst(Instruction::I32Mul); + // [i32] + self.inst(Instruction::LocalGet(ptr)); + // [i32 i32] + self.inst(Instruction::I32Add); + // [i32] + self.inst(Instruction::LocalSet(iter_base_pointer)); + // [] + + // Now do include the snippet that lowers a single list element! + self.current_block().extend(block); + + // Increment our index counter. + // + // [] + self.inst(Instruction::LocalGet(index)); + // [i32] + self.inst(Instruction::I32Const(1)); + // [i32 i32] + self.inst(Instruction::I32Add); + // [i32] + self.inst(Instruction::LocalSet(index)); + // [] + + // Unconditionally jump back to the loop head, and close out our blocks. + // + // [] + self.inst(Instruction::Br(0)); + // [] + self.inst(Instruction::End); + // [] + self.inst(Instruction::End); + // [] + + // If `realloc` is `None`, then we are responsible for freeing + // this pointer after the call. + if realloc.is_none() { + self.to_free + .push((ptr, length, u32::try_from(align).unwrap())); + } + + results.push(Operand::Wasm(ptr)); + results.push(Operand::Wasm(length)); + } + abi::Instruction::ListCanonLift { .. } => todo!(), + abi::Instruction::StringLift { free } => { + let len = pop_wasm(operands); + let ptr = pop_wasm(operands); + let result = self.next_js(); + + // [] + self.inst(Instruction::LocalGet(ptr)); + // [i32] + self.inst(Instruction::LocalGet(len)); + // [i32 i32] + self.inst(Instruction::I32Const(result.unwrap_js() as i32)); + // [i32 i32 i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_string_canon_lift"), + )); + // [] + + if let Some(free) = free { + // [] + self.inst(Instruction::LocalGet(ptr)); + // [i32] + self.inst(Instruction::LocalGet(len)); + // [i32 i32] + self.inst(Instruction::I32Const(1)); + // [i32 i32 i32] + self.inst(Instruction::Call(self.gen.spidermonkey_import(free))); + // [] + } + + results.push(result); + } + abi::Instruction::ListLift { + element, + free, + ty: _, + } => { + let len = pop_wasm(operands); + let ptr = pop_wasm(operands); + let (block, block_results) = self.pop_block(); + assert_eq!(block_results.len(), 1); + let iter_base_pointer = self.iter_base_pointer.pop().unwrap(); + + let index = self.new_local(wasm_encoder::ValType::I32); + + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + + let result = self.next_js(); + + // Create a new JS array object that will be the result of this + // lifting. + // + // [] + self.inst(Instruction::I32Const(result.unwrap_js() as i32)); + // [i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_new_array"), + )); + // [] + + // Create a block and a loop. The block is for branching to when + // we need to exit the loop. + // + // Also re-zero the loop index because it might be reused across + // multiple loops if the current block itself is also a loop + // body. + // + // [] + self.inst(Instruction::Block(wasm_encoder::BlockType::Empty)); + // [] + self.inst(Instruction::I32Const(0)); + // [i32] + self.inst(Instruction::LocalSet(index)); + // [] + self.inst(Instruction::Loop(wasm_encoder::BlockType::Empty)); + // [] + + // Check for our loop's exit condition: `index >= len`. + // + // [] + self.inst(Instruction::LocalGet(index)); + // [i32] + self.inst(Instruction::LocalGet(len)); + // [i32 i32] + self.inst(Instruction::I32GeU); + // [i32] + self.inst(Instruction::BrIf(1)); + // [] + + // Update the base pointer for this iteration. + // + // [] + self.inst(Instruction::LocalGet(index)); + // [i32] + self.inst(Instruction::I32Const(u32::try_from(size).unwrap() as _)); + // [i32 i32] + self.inst(Instruction::I32Mul); + // [i32] + self.inst(Instruction::LocalGet(ptr)); + // [i32 i32] + self.inst(Instruction::I32Add); + // [i32] + self.inst(Instruction::LocalSet(iter_base_pointer)); + // [] + + self.current_block().extend(block); + + // Append the result of this iteration's lifting to our JS array. + // + // [] + self.inst(Instruction::I32Const(result.unwrap_js() as i32)); + // [i32] + self.inst(Instruction::I32Const(block_results[0].unwrap_js() as i32)); + // [i32 i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_array_push"), + )); + // [] + + // Increment the index variable, unconditionally jump back to + // the head of the loop, and close out our blocks. + // + // [] + self.inst(Instruction::I32Const(1)); + // [i32] + self.inst(Instruction::LocalGet(index)); + // [i32 i32] + self.inst(Instruction::I32Add); + // [i32] + self.inst(Instruction::LocalSet(index)); + // [] + self.inst(Instruction::Br(0)); + // [] + self.inst(Instruction::End); + // [] + self.inst(Instruction::End); + // [] + + if let Some(free) = free { + // [] + self.inst(Instruction::LocalGet(ptr)); + // [i32] + self.inst(Instruction::LocalGet(len)); + // [i32 i32] + self.inst(Instruction::I32Const(u32::try_from(align).unwrap() as _)); + // [i32 i32 i32] + self.inst(Instruction::Call(self.gen.spidermonkey_import(free))); + // [] + } + + results.push(result); + } + abi::Instruction::IterElem { element: _ } => { + let iter_elem = self.next_js(); + self.iter_elem.push(iter_elem.unwrap_js()); + results.push(iter_elem); + } + abi::Instruction::IterBasePointer => { + let iter_base_pointer = self.new_local(wasm_encoder::ValType::I32); + self.iter_base_pointer.push(iter_base_pointer); + results.push(Operand::Wasm(iter_base_pointer)); + } + + abi::Instruction::RecordLower { + record: _, + name: _, + ty: _, + } => todo!(), + abi::Instruction::RecordLift { + record: _, + name: _, + ty: _, + } => todo!(), + abi::Instruction::TupleLower { tuple: _, ty: _ } => todo!(), + abi::Instruction::TupleLift { tuple: _, ty: _ } => todo!(), + abi::Instruction::FlagsLower { + flags: _, + name: _, + ty: _, + } => todo!(), + abi::Instruction::FlagsLift { + flags: _, + name: _, + ty: _, + } => todo!(), + abi::Instruction::VariantPayloadName => todo!(), + abi::Instruction::VariantLower { + variant: _, + name: _, + ty: _, + results: _, + } => todo!(), + abi::Instruction::VariantLift { + variant: _, + name: _, + ty: _, + } => todo!(), + abi::Instruction::OptionLower { .. } => todo!(), + abi::Instruction::OptionLift { .. } => todo!(), + abi::Instruction::ExpectedLower { .. } => todo!(), + abi::Instruction::ExpectedLift { .. } => todo!(), + abi::Instruction::UnionLower { .. } => todo!(), + abi::Instruction::UnionLift { .. } => todo!(), + abi::Instruction::EnumLower { + enum_: _, + name: _, + ty: _, + } => todo!(), + abi::Instruction::EnumLift { + enum_: _, + name: _, + ty: _, + } => todo!(), + abi::Instruction::CallWasm { iface, name, sig } => { + // Push the Wasm arguments. + // + // [] + let locals: Vec<_> = sig.params.iter().map(|_| pop_wasm(operands)).collect(); + for local in locals.into_iter().rev() { + self.inst(Instruction::LocalGet(local)); + } + // [A...] + + let func_index = self + .gen + .import_fn_name_to_index + .get(&iface.name) + .unwrap() + .get(*name) + .unwrap() + .0; + self.inst(Instruction::Call(func_index)); + // [R...] + + // Allocate locals for the results and pop the return values off + // the Wasm stack, saving each of them to the associated local. + let locals: Vec<_> = sig + .results + .iter() + .map(|ty| self.new_local(convert_ty(*ty))) + .collect(); + // [R...] + for l in locals.iter().rev() { + self.inst(Instruction::LocalSet(*l)); + } + // [] + + results.extend(locals.into_iter().map(Operand::Wasm)); + + for (ptr, len, alignment) in mem::replace(&mut self.to_free, vec![]) { + // [] + self.inst(Instruction::LocalGet(ptr)); + // [i32] + self.inst(Instruction::LocalGet(len)); + // [i32 i32] + self.inst(Instruction::I32Const(alignment as _)); + // [i32 i32 i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("canonical_abi_free"), + )); + // [] + } + } + abi::Instruction::CallInterface { module: _, func } => { + // TODO: Rather than always dynamically pushing all of our JS + // arguments, make `SMW_call_{0,1,...,n}` up to the largest + // common `n` so we can directly pass the arguments for most + // function calls. + + // Push the JS arguments. + let js_args: Vec<_> = func.params.iter().map(|_| pop_js(operands)).collect(); + for js in js_args.into_iter().rev() { + // [] + self.inst(Instruction::I32Const(js as _)); + // [i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_push_arg"), + )); + // [] + } + + // TODO: Rather than `malloc`ing the name for each call, we + // should pre-`malloc` them in the `wizer.initialize` function, + // add a global to the glue module that points to the + // pre-`malloc`ed name for each exported function, and then + // reuse those that pre-`malloc`ed name on each call. + + // Malloc space for the function name. + let func_name_local = self.new_local(wasm_encoder::ValType::I32); + self.gen.malloc_static_size( + self.blocks.last_mut().unwrap(), + u32::try_from(func.name.len()).unwrap() + 1, + func_name_local, + ); + + // Copy the function name from the glue Wasm module's memory + // into the `malloc`ed space in the `spidermonkey.wasm`'s + // memory. + let func_name_offset = self + .gen + .data_segments + .add(func.name.to_snake_case().as_bytes().iter().copied()); + self.gen.copy_to_smw( + self.blocks.last_mut().unwrap(), + func_name_offset, + func_name_local, + u32::try_from(func.name.len()).unwrap(), + ); + + let (first_result, num_results) = match &func.result { + // If there aren't any function results, then this argument + // to `SMW_call` is going to be ignored. Use a highly + // visible placeholder so that if this is ever accidentally + // used it is easier to debug. + Type::Unit => { + results.push(Operand::Unit); + (0xffffffff, 0) + } + _ => { + let js = self.next_js(); + results.push(js); + (js.unwrap_js(), 1) + } + }; + + // Make the call. + // + // [] + self.inst(Instruction::LocalGet(func_name_local)); + // [i32] + self.inst(Instruction::I32Const( + i32::try_from(func.name.len()).unwrap(), + )); + // [i32 i32] + self.inst(Instruction::I32Const(num_results)); + // [i32 i32 i32] + self.inst(Instruction::I32Const(first_result as i32)); + // [i32 i32 i32 i32] + self.inst(Instruction::Call(self.gen.spidermonkey_import("SMW_call"))); + // [] + } + + abi::Instruction::CallWasmAsyncExport { .. } => todo!(), + abi::Instruction::CallWasmAsyncImport { .. } => todo!(), + + abi::Instruction::Return { func, amt } => { + match self.lift_lower { + abi::LiftLower::LowerArgsLiftResults => { + match &func.result { + Type::Unit => { + operands[0].unwrap_unit(); + } + _ => { + // Attach the return values to the `JS::CallArgs`: + // build up the return values via a series of + // `SMW_push_return_value` calls, followed by a + // single `SMW_finish_returns` call. + // + // TODO: introduce fast path intrinsics for common + // small numbers of return values so that we don't + // have to do multiple intrinsic calls here, and can + // instead do a single `SMW_return_{1,2,...,n}` + // call. + let val = pop_js(operands); + // [] + self.inst(Instruction::I32Const(val as _)); + // [i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_push_return_value"), + )); + // [] + self.inst(Instruction::LocalGet(1)); + // [i32] + self.inst(Instruction::LocalGet(2)); + // [i32 i32] + self.inst(Instruction::Call( + self.gen.spidermonkey_import("SMW_finish_returns"), + )); + // [] + } + } + + // NB: only clear the JS operands after we've attached + // the return value(s) to the `JS::CallArgs`. + self.gen.clear_js_operands(self.blocks.last_mut().unwrap()); + + // Return `true`, meaning that a JS exception was not thrown. + // + // [] + self.inst(Instruction::I32Const(1)); + // [i32] + self.inst(Instruction::Return); + // [] + } + abi::LiftLower::LiftArgsLowerResults => { + self.gen.clear_js_operands(self.blocks.last_mut().unwrap()); + + // Get the return values out of their locals and push + // them onto the Wasm stack. + // + // [] + for _ in 0..*amt { + let local = pop_wasm(operands); + self.inst(Instruction::LocalGet(local)); + } + // [R...] + self.inst(Instruction::Return); + // [] + } + } + } + + abi::Instruction::UnitLower { .. } => { + operands[0].unwrap_unit(); + } + abi::Instruction::UnitLift { .. } => { + results.push(Operand::Unit); + } + + abi::Instruction::I32FromBool { .. } => todo!(), + abi::Instruction::BoolFromI32 { .. } => todo!(), + + abi::Instruction::ReturnAsyncExport { .. } => todo!(), + abi::Instruction::ReturnAsyncImport { .. } => todo!(), + + abi::Instruction::Malloc { .. } => todo!(), + abi::Instruction::Free { .. } => todo!(), + } + } + + fn return_pointer(&mut self, _iface: &Interface, size: usize, align: usize) -> Self::Operand { + self.gen.return_pointer_area_size = self.gen.return_pointer_area_size.max(size); + self.gen.return_pointer_area_align = self.gen.return_pointer_area_align.max(align); + let local = self.new_local(wasm_encoder::ValType::I32); + + // [] + self.inst(Instruction::GlobalGet(RET_PTR_GLOBAL)); + // [i32] + self.inst(Instruction::LocalSet(local)); + // [] + + Operand::Wasm(local) + } + + fn push_block(&mut self) { + self.blocks.push(vec![]); + } + + fn finish_block(&mut self, results: &mut Vec) { + self.block_results.push(results.to_vec()); + } + + fn sizes(&self) -> &SizeAlign { + &self.gen.sizes + } + + fn is_list_canonical(&self, _iface: &Interface, _ty: &Type) -> bool { + // TODO: we will want to support canonical lists for the various typed + // arrays. + false + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/tests/codegen-smw.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/tests/codegen-smw.rs new file mode 100644 index 0000000..562c340 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-spidermonkey/tests/codegen-smw.rs @@ -0,0 +1,31 @@ +use std::path::Path; + +mod imports { + test_helpers::codegen_spidermonkey_import!( + // TODO: should support more of the `*.wit` test suite + "strings.wit" + "simple-lists.wit" + "simple-functions.wit" + ); +} + +mod exports { + test_helpers::codegen_spidermonkey_export!( + // TODO: should support more of the `*.wit` test suite + "strings.wit" + "simple-lists.wit" + "simple-functions.wit" + ); +} + +fn verify(dir: &str, _name: &str) { + let wasm = std::fs::read(Path::new(dir).join("foo.wasm")).unwrap(); + let mut validator = wasmparser::Validator::new(); + validator.wasm_features(wasmparser::WasmFeatures { + bulk_memory: true, + module_linking: true, + multi_memory: true, + ..wasmparser::WasmFeatures::default() + }); + validator.validate_all(&wasm).expect("wasm isn't valid"); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/Cargo.toml new file mode 100644 index 0000000..cae9a81 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "wit-bindgen-gen-wasmtime-py" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' } +heck = "0.3" +structopt = { version = "0.3", default-features = false, optional = true } + +[dev-dependencies] +test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-wasmtime-py'] } diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/build.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/build.rs new file mode 100644 index 0000000..d7c7fc2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + // this build script is currently only here so OUT_DIR is set for testing. +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/mypy.ini b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/mypy.ini new file mode 100644 index 0000000..49f970a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/mypy.ini @@ -0,0 +1,13 @@ +[mypy] + +disallow_any_unimported = True + +disallow_untyped_calls = True +disallow_untyped_defs = True +disallow_incomplete_defs = True +check_untyped_defs = True +disallow_untyped_decorators = True +strict_optional = True + +warn_return_any = True +warn_unused_configs = True diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/dependencies.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/dependencies.rs new file mode 100644 index 0000000..478594c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/dependencies.rs @@ -0,0 +1,368 @@ +use crate::Source; +use std::collections::{BTreeMap, BTreeSet}; + +/// Tracks all of the import and intrinsics that a given codegen +/// requires and how to generate them when needed. +#[derive(Default)] +pub struct Dependencies { + pub needs_clamp: bool, + pub needs_store: bool, + pub needs_load: bool, + pub needs_validate_guest_char: bool, + pub needs_expected: bool, + pub needs_i32_to_f32: bool, + pub needs_f32_to_i32: bool, + pub needs_i64_to_f64: bool, + pub needs_f64_to_i64: bool, + pub needs_decode_utf8: bool, + pub needs_encode_utf8: bool, + pub needs_list_canon_lift: bool, + pub needs_list_canon_lower: bool, + pub needs_t_typevar: bool, + pub needs_resources: bool, + pub pyimports: BTreeMap>>, +} + +impl Dependencies { + /// Record that a Python import is required + /// + /// Examples + /// ``` + /// # use wit_bindgen_gen_wasmtime_py::dependencies::Dependencies; + /// # let mut deps = Dependencies::default(); + /// // Import a specific item from a module + /// deps.pyimport("typing", "NamedTuple"); + /// // Import an entire module + /// deps.pyimport("collections", None); + /// ``` + pub fn pyimport<'a>(&mut self, module: &str, name: impl Into>) { + let name = name.into(); + let list = self + .pyimports + .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()), + } + } + + /// Create a `Source` containing all of the intrinsics + /// required according to this `Dependencies` struct. + pub fn intrinsics(&mut self) -> Source { + let mut src = Source::default(); + + if self.needs_clamp { + src.push_str( + " + def _clamp(i: int, min: int, max: int) -> int: + if i < min or i > max: + raise OverflowError(f'must be between {min} and {max}') + return i + ", + ); + } + if self.needs_store { + // TODO: this uses native endianness + self.pyimport("ctypes", None); + src.push_str( + " + def _store(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 + ", + ); + } + if self.needs_load { + // TODO: this uses native endianness + self.pyimport("ctypes", None); + src.push_str( + " + def _load(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] + ", + ); + } + if self.needs_validate_guest_char { + src.push_str( + " + def _validate_guest_char(i: int) -> str: + if i > 0x10ffff or (i >= 0xd800 and i <= 0xdfff): + raise TypeError('not a valid char') + return chr(i) + ", + ); + } + if self.needs_expected { + self.pyimport("dataclasses", "dataclass"); + self.pyimport("typing", "TypeVar"); + self.pyimport("typing", "Generic"); + self.pyimport("typing", "Union"); + self.needs_t_typevar = true; + src.push_str( + " + @dataclass + class Ok(Generic[T]): + value: T + E = TypeVar('E') + @dataclass + class Err(Generic[E]): + value: E + + Expected = Union[Ok[T], Err[E]] + ", + ); + } + if self.needs_i32_to_f32 || self.needs_f32_to_i32 { + self.pyimport("ctypes", None); + src.push_str("_i32_to_f32_i32 = ctypes.pointer(ctypes.c_int32(0))\n"); + src.push_str( + "_i32_to_f32_f32 = ctypes.cast(_i32_to_f32_i32, ctypes.POINTER(ctypes.c_float))\n", + ); + if self.needs_i32_to_f32 { + src.push_str( + " + def _i32_to_f32(i: int) -> float: + _i32_to_f32_i32[0] = i # type: ignore + return _i32_to_f32_f32[0] # type: ignore + ", + ); + } + if self.needs_f32_to_i32 { + src.push_str( + " + def _f32_to_i32(i: float) -> int: + _i32_to_f32_f32[0] = i # type: ignore + return _i32_to_f32_i32[0] # type: ignore + ", + ); + } + } + if self.needs_i64_to_f64 || self.needs_f64_to_i64 { + self.pyimport("ctypes", None); + src.push_str("_i64_to_f64_i64 = ctypes.pointer(ctypes.c_int64(0))\n"); + src.push_str( + "_i64_to_f64_f64 = ctypes.cast(_i64_to_f64_i64, ctypes.POINTER(ctypes.c_double))\n", + ); + if self.needs_i64_to_f64 { + src.push_str( + " + def _i64_to_f64(i: int) -> float: + _i64_to_f64_i64[0] = i # type: ignore + return _i64_to_f64_f64[0] # type: ignore + ", + ); + } + if self.needs_f64_to_i64 { + src.push_str( + " + def _f64_to_i64(i: float) -> int: + _i64_to_f64_f64[0] = i # type: ignore + return _i64_to_f64_i64[0] # type: ignore + ", + ); + } + } + if self.needs_decode_utf8 { + self.pyimport("ctypes", None); + src.push_str( + " + def _decode_utf8(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') + ", + ); + } + if self.needs_encode_utf8 { + self.pyimport("ctypes", None); + self.pyimport("typing", "Tuple"); + src.push_str( + " + def _encode_utf8(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)) + ", + ); + } + if self.needs_list_canon_lift { + self.pyimport("ctypes", None); + self.pyimport("typing", "List"); + // TODO: this is doing a native-endian read, not a little-endian + // read + src.push_str( + " + def _list_canon_lift(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] + ", + ); + } + if self.needs_list_canon_lower { + self.pyimport("ctypes", None); + self.pyimport("typing", "List"); + self.pyimport("typing", "Tuple"); + // 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 + src.push_str( + " + def _list_canon_lower(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)) + ", + ); + } + + if self.needs_resources { + self.pyimport("typing", "TypeVar"); + self.pyimport("typing", "Generic"); + self.pyimport("typing", "List"); + self.pyimport("typing", "Optional"); + self.pyimport("dataclasses", "dataclass"); + self.needs_t_typevar = true; + src.push_str( + " + @dataclass + class SlabEntry(Generic[T]): + next: int + val: Optional[T] + + class Slab(Generic[T]): + head: int + list: List[SlabEntry[T]] + + def __init__(self) -> None: + self.list = [] + self.head = 0 + + def insert(self, val: T) -> int: + if self.head >= len(self.list): + self.list.append(SlabEntry(next = len(self.list) + 1, val = None)) + ret = self.head + slot = self.list[ret] + self.head = slot.next + slot.next = -1 + slot.val = val + return ret + + def get(self, idx: int) -> T: + if idx >= len(self.list): + raise IndexError('handle index not valid') + slot = self.list[idx] + if slot.next == -1: + assert(slot.val is not None) + return slot.val + raise IndexError('handle index not valid') + + def remove(self, idx: int) -> T: + ret = self.get(idx) + slot = self.list[idx] + slot.val = None + slot.next = self.head + self.head = idx + return ret + ", + ); + } + src + } +} + +#[cfg(test)] +mod test { + use std::collections::{BTreeMap, BTreeSet}; + + use super::Dependencies; + + #[test] + fn test_pyimport_only_contents() { + let mut deps = Dependencies::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 = Dependencies::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 = Dependencies::default(); + deps.pyimport("typing", "NamedTuple"); + deps.pyimport("typing", None); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/lib.rs new file mode 100644 index 0000000..051dfa1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/lib.rs @@ -0,0 +1,1876 @@ +use heck::*; +use std::collections::{BTreeMap, BTreeSet, HashMap}; +use std::mem; +use wit_bindgen_gen_core::wit_parser::abi::{ + AbiVariant, Bindgen, Bitcast, Instruction, LiftLower, WasmType, +}; +use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator, Ns}; + +pub mod dependencies; +pub mod source; + +use dependencies::Dependencies; +use source::Source; + +#[derive(Default)] +pub struct WasmtimePy { + src: Source, + in_import: bool, + opts: Opts, + guest_imports: HashMap, + guest_exports: HashMap, + sizes: SizeAlign, + /// Tracks the intrinsics and Python imports needed + deps: Dependencies, + /// Whether the Python Union being emited will wrap its cases with dataclasses + union_representation: HashMap, +} + +#[derive(Debug, Clone, Copy)] +enum PyUnionRepresentation { + /// A union whose inner types are used directly + Raw, + /// A union whose inner types have been wrapped in dataclasses + Wrapped, +} + +#[derive(Default)] +struct Imports { + freestanding_funcs: Vec, + resource_funcs: BTreeMap>, +} + +struct Import { + name: String, + src: Source, + wasm_ty: String, + pysig: String, +} + +#[derive(Default)] +struct Exports { + freestanding_funcs: Vec, + resource_funcs: BTreeMap>, + fields: BTreeMap, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + #[cfg_attr(feature = "structopt", structopt(long = "no-typescript"))] + pub no_typescript: bool, +} + +impl Opts { + pub fn build(self) -> WasmtimePy { + let mut r = WasmtimePy::new(); + r.opts = self; + r + } +} + +impl WasmtimePy { + pub fn new() -> WasmtimePy { + WasmtimePy::default() + } + + fn abi_variant(dir: Direction) -> AbiVariant { + // This generator uses a reversed mapping! In the Wasmtime-py host-side + // bindings, we don't use any extra adapter layer between guest wasm + // modules and the host. When the guest imports functions using the + // `GuestImport` ABI, the host directly implements the `GuestImport` + // ABI, even though the host is *exporting* functions. Similarly, when + // the guest exports functions using the `GuestExport` ABI, the host + // directly imports them with the `GuestExport` ABI, even though the + // host is *importing* functions. + match dir { + Direction::Import => AbiVariant::GuestExport, + Direction::Export => AbiVariant::GuestImport, + } + } + + /// Creates a `Source` with all of the required intrinsics + fn intrinsics(&mut self, iface: &Interface) -> Source { + if iface.resources.len() > 0 { + self.deps.needs_resources = true; + self.deps.pyimport("typing", "runtime_checkable"); + } + self.deps.intrinsics() + } +} + +fn array_ty(iface: &Interface, ty: &Type) -> Option<&'static str> { + match ty { + Type::Unit | 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::Float32 => Some("c_float"), + Type::Float64 => Some("c_double"), + Type::Char => None, + Type::Handle(_) => None, + Type::String => None, + Type::Id(id) => match &iface.types[*id].kind { + TypeDefKind::Type(t) => array_ty(iface, t), + _ => None, + }, + } +} + +impl Generator for WasmtimePy { + fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + let variant = Self::abi_variant(dir); + self.sizes.fill(iface); + self.in_import = variant == AbiVariant::GuestImport; + } + + fn type_record( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ) { + let mut builder = self.src.builder(&mut self.deps, iface); + builder.pyimport("dataclasses", "dataclass"); + builder.push_str("@dataclass\n"); + builder.push_str(&format!("class {}:\n", name.to_camel_case())); + builder.indent(); + builder.docstring(docs); + for field in record.fields.iter() { + builder.comment(&field.docs); + let field_name = field.name.to_snake_case(); + builder.push_str(&format!("{field_name}: ")); + builder.print_ty(&field.ty, true); + builder.push_str("\n"); + } + if record.fields.is_empty() { + builder.push_str("pass\n"); + } + builder.dedent(); + builder.push_str("\n"); + } + + fn type_tuple( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + tuple: &Tuple, + docs: &Docs, + ) { + let mut builder = self.src.builder(&mut self.deps, iface); + builder.comment(docs); + builder.push_str(&format!("{} = ", name.to_camel_case())); + builder.print_tuple(tuple); + builder.push_str("\n"); + } + + fn type_flags( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + flags: &Flags, + docs: &Docs, + ) { + let mut builder = self.src.builder(&mut self.deps, iface); + builder.pyimport("enum", "Flag"); + builder.pyimport("enum", "auto"); + builder.push_str(&format!("class {}(Flag):\n", name.to_camel_case())); + builder.indent(); + builder.docstring(docs); + for flag in flags.flags.iter() { + let flag_name = flag.name.to_shouty_snake_case(); + builder.comment(&flag.docs); + builder.push_str(&format!("{flag_name} = auto()\n")); + } + if flags.flags.is_empty() { + builder.push_str("pass\n"); + } + builder.dedent(); + builder.push_str("\n"); + } + + fn type_variant( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + variant: &Variant, + docs: &Docs, + ) { + let mut builder = self.src.builder(&mut self.deps, iface); + builder.pyimport("dataclasses", "dataclass"); + let mut cases = Vec::new(); + for case in variant.cases.iter() { + builder.docstring(&case.docs); + builder.push_str("@dataclass\n"); + let case_name = format!("{}{}", name.to_camel_case(), case.name.to_camel_case()); + builder.push_str(&format!("class {case_name}:\n")); + builder.indent(); + builder.push_str("value: "); + builder.print_ty(&case.ty, true); + builder.push_str("\n"); + builder.dedent(); + builder.push_str("\n"); + cases.push(case_name); + } + + builder.deps.pyimport("typing", "Union"); + builder.comment(docs); + builder.push_str(&format!( + "{} = Union[{}]\n", + name.to_camel_case(), + cases.join(", "), + )); + builder.push_str("\n"); + } + + /// Appends a Python definition for the provided Union to the current `Source`. + /// e.g. `MyUnion = Union[float, str, int]` + fn type_union( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + union: &Union, + docs: &Docs, + ) { + let mut py_type_classes = BTreeSet::new(); + for case in union.cases.iter() { + py_type_classes.insert(py_type_class_of(&case.ty)); + } + + let mut builder = self.src.builder(&mut self.deps, iface); + if py_type_classes.len() != union.cases.len() { + // Some of the cases are not distinguishable + self.union_representation + .insert(name.to_string(), PyUnionRepresentation::Wrapped); + builder.print_union_wrapped(name, union, docs); + } else { + // All of the cases are distinguishable + self.union_representation + .insert(name.to_string(), PyUnionRepresentation::Raw); + builder.print_union_raw(name, union, docs); + } + } + + fn type_option( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + payload: &Type, + docs: &Docs, + ) { + let mut builder = self.src.builder(&mut self.deps, iface); + builder.pyimport("typing", "Optional"); + builder.comment(docs); + builder.push_str(&name.to_camel_case()); + builder.push_str(" = Optional["); + builder.print_ty(payload, true); + builder.push_str("]\n\n"); + } + + fn type_expected( + &mut self, + iface: &Interface, + _id: TypeId, + name: &str, + expected: &Expected, + docs: &Docs, + ) { + self.deps.needs_expected = true; + + let mut builder = self.src.builder(&mut self.deps, iface); + builder.comment(docs); + builder.push_str(&format!("{} = Expected[", name.to_camel_case())); + builder.print_ty(&expected.ok, true); + builder.push_str(", "); + builder.print_ty(&expected.err, true); + builder.push_str("]\n\n"); + } + + fn type_enum(&mut self, iface: &Interface, _id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + let mut builder = self.src.builder(&mut self.deps, iface); + builder.pyimport("enum", "Enum"); + builder.push_str(&format!("class {}(Enum):\n", name.to_camel_case())); + builder.indent(); + builder.docstring(docs); + for (i, case) in enum_.cases.iter().enumerate() { + builder.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); + } + builder.push_str(&format!("{} = {}\n", name, i)); + } + builder.dedent(); + builder.push_str("\n"); + } + + fn type_resource(&mut self, _iface: &Interface, _ty: ResourceId) { + // if !self.in_import { + // self.exported_resources.insert(ty); + // } + } + + fn type_alias(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let mut builder = self.src.builder(&mut self.deps, iface); + builder.comment(docs); + builder.push_str(&format!("{} = ", name.to_camel_case())); + builder.print_ty(ty, false); + builder.push_str("\n"); + } + + fn type_list(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + let mut builder = self.src.builder(&mut self.deps, iface); + builder.comment(docs); + builder.push_str(&format!("{} = ", name.to_camel_case())); + builder.print_list(ty); + builder.push_str("\n"); + } + + fn type_builtin(&mut self, iface: &Interface, id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.type_alias(iface, id, name, ty, docs); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "export" uses the "guest import" ABI variant on the inside of + // this `Generator` implementation. + fn export(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let mut pysig = Source::default(); + let mut builder = pysig.builder(&mut self.deps, iface); + builder.print_sig(func, self.in_import); + let pysig = pysig.to_string(); + + let mut func_body = Source::default(); + let mut builder = func_body.builder(&mut self.deps, iface); + + let sig = iface.wasm_signature(AbiVariant::GuestImport, func); + builder.push_str(&format!( + "def {}(caller: wasmtime.Caller", + func.name.to_snake_case(), + )); + let mut params = Vec::new(); + for (i, param) in sig.params.iter().enumerate() { + builder.push_str(", "); + let name = format!("arg{}", i); + builder.push_str(&name); + builder.push_str(": "); + builder.push_str(wasm_ty_typing(*param)); + params.push(name); + } + builder.push_str(") -> "); + match sig.results.len() { + 0 => builder.push_str("None"), + 1 => builder.push_str(wasm_ty_typing(sig.results[0])), + _ => unimplemented!(), + } + builder.push_str(":\n"); + builder.indent(); + drop(builder); + + let mut f = FunctionBindgen::new(self, params); + iface.call( + AbiVariant::GuestImport, + LiftLower::LiftArgsLowerResults, + func, + &mut f, + ); + + let FunctionBindgen { + src, + needs_memory, + needs_realloc, + needs_free, + mut locals, + .. + } = f; + + let mut builder = func_body.builder(&mut self.deps, iface); + if needs_memory { + // TODO: hardcoding "memory" + builder.push_str("m = caller[\"memory\"]\n"); + builder.push_str("assert(isinstance(m, wasmtime.Memory))\n"); + builder.deps.pyimport("typing", "cast"); + builder.push_str("memory = cast(wasmtime.Memory, m)\n"); + locals.insert("memory").unwrap(); + } + + if let Some(name) = needs_realloc { + builder.push_str(&format!("realloc = caller[\"{}\"]\n", name)); + builder.push_str("assert(isinstance(realloc, wasmtime.Func))\n"); + locals.insert("realloc").unwrap(); + } + + if let Some(name) = needs_free { + builder.push_str(&format!("free = caller[\"{}\"]\n", name)); + builder.push_str("assert(isinstance(free, wasmtime.Func))\n"); + locals.insert("free").unwrap(); + } + builder.push_str(&src); + builder.dedent(); + + let mut wasm_ty = String::from("wasmtime.FuncType(["); + wasm_ty.push_str( + &sig.params + .iter() + .map(|t| wasm_ty_ctor(*t)) + .collect::>() + .join(", "), + ); + wasm_ty.push_str("], ["); + wasm_ty.push_str( + &sig.results + .iter() + .map(|t| wasm_ty_ctor(*t)) + .collect::>() + .join(", "), + ); + wasm_ty.push_str("])"); + let import = Import { + name: func.name.clone(), + src: func_body, + wasm_ty, + pysig, + }; + let imports = self + .guest_imports + .entry(iface.name.to_string()) + .or_insert(Imports::default()); + let dst = match &func.kind { + FunctionKind::Freestanding | FunctionKind::Static { .. } => { + &mut imports.freestanding_funcs + } + FunctionKind::Method { resource, .. } => imports + .resource_funcs + .entry(*resource) + .or_insert(Vec::new()), + }; + dst.push(import); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "import" uses the "export" ABI variant on the inside of + // this `Generator` implementation. + fn import(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let mut func_body = Source::default(); + let mut builder = func_body.builder(&mut self.deps, iface); + + // Print the function signature + let params = builder.print_sig(func, self.in_import); + builder.push_str(":\n"); + builder.indent(); + drop(builder); + + // Use FunctionBindgen call + let src_object = match &func.kind { + FunctionKind::Freestanding => "self".to_string(), + FunctionKind::Static { .. } => "obj".to_string(), + FunctionKind::Method { .. } => "self._obj".to_string(), + }; + let mut f = FunctionBindgen::new(self, params); + f.src_object = src_object; + iface.call( + AbiVariant::GuestExport, + LiftLower::LowerArgsLiftResults, + func, + &mut f, + ); + let FunctionBindgen { + src, + needs_memory, + needs_realloc, + needs_free, + src_object, + .. + } = f; + let mut builder = func_body.builder(&mut self.deps, iface); + if needs_memory { + // TODO: hardcoding "memory" + builder.push_str(&format!("memory = {}._memory;\n", src_object)); + } + + if let Some(name) = &needs_realloc { + builder.push_str(&format!( + "realloc = {}._{}\n", + src_object, + name.to_snake_case(), + )); + } + + if let Some(name) = &needs_free { + builder.push_str(&format!( + "free = {}._{}\n", + src_object, + name.to_snake_case(), + )); + } + builder.push_str(&src); + builder.dedent(); + + let exports = self + .guest_exports + .entry(iface.name.to_string()) + .or_insert_with(Exports::default); + if needs_memory { + exports + .fields + .insert("memory".to_string(), "wasmtime.Memory"); + } + if let Some(name) = &needs_realloc { + exports.fields.insert(name.clone(), "wasmtime.Func"); + } + if let Some(name) = &needs_free { + exports.fields.insert(name.clone(), "wasmtime.Func"); + } + exports.fields.insert(func.name.clone(), "wasmtime.Func"); + + let dst = match &func.kind { + FunctionKind::Freestanding => &mut exports.freestanding_funcs, + FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => { + exports + .resource_funcs + .entry(*resource) + .or_insert(Vec::new()) + } + }; + dst.push(func_body); + } + + fn finish_one(&mut self, iface: &Interface, files: &mut Files) { + self.deps.pyimport("typing", "Any"); + self.deps.pyimport("abc", "abstractmethod"); + + let types = mem::take(&mut self.src); + let intrinsics = self.intrinsics(iface); + + for (k, v) in self.deps.pyimports.iter() { + match v { + Some(list) => { + let list = list.iter().cloned().collect::>().join(", "); + self.src.push_str(&format!("from {} import {}\n", k, list)); + } + None => { + self.src.push_str(&format!("import {}\n", k)); + } + } + } + self.src.push_str("import wasmtime\n"); + self.src.push_str( + " + try: + from typing import Protocol + except ImportError: + class Protocol: # type: ignore + pass + ", + ); + self.src.push_str("\n"); + + if self.deps.needs_t_typevar { + self.src.push_str("T = TypeVar('T')\n"); + } + + self.src.push_str(&intrinsics); + for (id, r) in iface.resources.iter() { + let name = r.name.to_camel_case(); + if self.in_import { + self.src.push_str("@runtime_checkable\n"); + self.src.push_str(&format!("class {}(Protocol):\n", name)); + self.src.indent(); + self.src.push_str("def drop(self) -> None:\n"); + self.src.indent(); + self.src.push_str("pass\n"); + self.src.dedent(); + + for (_, funcs) in self.guest_imports.iter() { + if let Some(funcs) = funcs.resource_funcs.get(&id) { + for func in funcs { + self.src.push_str("@abstractmethod\n"); + self.src.push_str(&func.pysig); + self.src.push_str(":\n"); + self.src.indent(); + self.src.push_str("raise NotImplementedError\n"); + self.src.dedent(); + } + } + } + self.src.dedent(); + } else { + self.src.push_str(&format!("class {}:\n", name)); + self.src.indent(); + self.src.push_str(&format!( + " + _wasm_val: int + _refcnt: int + _obj: '{iface}' + _destroyed: bool + + def __init__(self, val: int, obj: '{iface}') -> None: + self._wasm_val = val + self._refcnt = 1 + self._obj = obj + self._destroyed = False + + def clone(self) -> '{name}': + self._refcnt += 1 + return self + + def drop(self, store: wasmtime.Storelike) -> None: + self._refcnt -= 1; + if self._refcnt != 0: + return + assert(not self._destroyed) + self._destroyed = True + self._obj._canonical_abi_drop_{drop}(store, self._wasm_val) + + def __del__(self) -> None: + if not self._destroyed: + raise RuntimeError('wasm object not dropped') + ", + name = name, + iface = iface.name.to_camel_case(), + drop = name.to_snake_case(), + )); + + for (_, exports) in self.guest_exports.iter() { + if let Some(funcs) = exports.resource_funcs.get(&id) { + for func in funcs { + self.src.push_str(func); + } + } + } + + self.src.dedent(); + } + } + self.src.push_str(&types); + + for (module, funcs) in mem::take(&mut self.guest_imports) { + self.src + .push_str(&format!("class {}(Protocol):\n", module.to_camel_case())); + self.src.indent(); + for func in funcs.freestanding_funcs.iter() { + self.src.push_str("@abstractmethod\n"); + self.src.push_str(&func.pysig); + self.src.push_str(":\n"); + self.src.indent(); + self.src.push_str("raise NotImplementedError\n"); + self.src.dedent(); + } + self.src.dedent(); + self.src.push_str("\n"); + + self.src.push_str(&format!( + "def add_{}_to_linker(linker: wasmtime.Linker, store: wasmtime.Store, host: {}) -> None:\n", + module.to_snake_case(), + module.to_camel_case(), + )); + self.src.indent(); + + for (id, r) in iface.resources.iter() { + self.src.push_str(&format!( + "_resources{}: Slab[{}] = Slab()\n", + id.index(), + r.name.to_camel_case() + )); + } + + for func in funcs + .freestanding_funcs + .iter() + .chain(funcs.resource_funcs.values().flat_map(|v| v)) + { + self.src.push_str(&format!("ty = {}\n", func.wasm_ty)); + self.src.push_str(&func.src); + self.src.push_str(&format!( + "linker.define('{}', '{}', wasmtime.Func(store, ty, {}, access_caller = True))\n", + iface.name, + func.name, + func.name.to_snake_case(), + )); + } + + for (id, resource) in iface.resources.iter() { + let snake = resource.name.to_snake_case(); + + self.src.push_str(&format!( + "def resource_drop_{}(i: int) -> None:\n _resources{}.remove(i).drop()\n", + snake, + id.index(), + )); + self.src + .push_str("ty = wasmtime.FuncType([wasmtime.ValType.i32()], [])\n"); + self.src.push_str(&format!( + "linker.define(\ + 'canonical_abi', \ + 'resource_drop_{}', \ + wasmtime.Func(store, ty, resource_drop_{})\ + )\n", + resource.name, snake, + )); + } + self.src.dedent(); + } + + // This is exculsively here to get mypy to not complain about empty + // modules, this probably won't really get triggered much in practice + if !self.in_import && self.guest_exports.is_empty() { + self.src + .push_str(&format!("class {}:\n", iface.name.to_camel_case())); + self.src.indent(); + if iface.resources.len() == 0 { + self.src.push_str("pass\n"); + } else { + for (_, r) in iface.resources.iter() { + self.src.push_str(&format!( + "_canonical_abi_drop_{}: wasmtime.Func\n", + r.name.to_snake_case(), + )); + } + } + self.src.dedent(); + } + + for (module, exports) in mem::take(&mut self.guest_exports) { + let module = module.to_camel_case(); + self.src.push_str(&format!("class {}:\n", module)); + self.src.indent(); + + self.src.push_str("instance: wasmtime.Instance\n"); + for (name, ty) in exports.fields.iter() { + self.src + .push_str(&format!("_{}: {}\n", name.to_snake_case(), ty)); + } + for (id, r) in iface.resources.iter() { + self.src.push_str(&format!( + "_resource{}_slab: Slab[{}]\n", + id.index(), + r.name.to_camel_case(), + )); + self.src.push_str(&format!( + "_canonical_abi_drop_{}: wasmtime.Func\n", + r.name.to_snake_case(), + )); + } + + self.src.push_str("def __init__(self, store: wasmtime.Store, linker: wasmtime.Linker, module: wasmtime.Module):\n"); + self.src.indent(); + for (id, r) in iface.resources.iter() { + self.src.push_str(&format!( + " + ty1 = wasmtime.FuncType([wasmtime.ValType.i32()], []) + ty2 = wasmtime.FuncType([wasmtime.ValType.i32()], [wasmtime.ValType.i32()]) + def drop_{snake}(caller: wasmtime.Caller, idx: int) -> None: + self._resource{idx}_slab.remove(idx).drop(caller); + linker.define('canonical_abi', 'resource_drop_{name}', wasmtime.Func(store, ty1, drop_{snake}, access_caller = True)) + + def clone_{snake}(idx: int) -> int: + obj = self._resource{idx}_slab.get(idx) + return self._resource{idx}_slab.insert(obj.clone()) + linker.define('canonical_abi', 'resource_clone_{name}', wasmtime.Func(store, ty2, clone_{snake})) + + def get_{snake}(idx: int) -> int: + obj = self._resource{idx}_slab.get(idx) + return obj._wasm_val + linker.define('canonical_abi', 'resource_get_{name}', wasmtime.Func(store, ty2, get_{snake})) + + def new_{snake}(val: int) -> int: + return self._resource{idx}_slab.insert({camel}(val, self)) + linker.define('canonical_abi', 'resource_new_{name}', wasmtime.Func(store, ty2, new_{snake})) + ", + name = r.name, + camel = r.name.to_camel_case(), + snake = r.name.to_snake_case(), + idx = id.index(), + )); + } + self.src + .push_str("self.instance = linker.instantiate(store, module)\n"); + self.src + .push_str("exports = self.instance.exports(store)\n"); + for (name, ty) in exports.fields.iter() { + self.src.push_str(&format!( + " + {snake} = exports['{name}'] + assert(isinstance({snake}, {ty})) + self._{snake} = {snake} + ", + name = name, + snake = name.to_snake_case(), + ty = ty, + )); + } + for (id, r) in iface.resources.iter() { + self.src.push_str(&format!( + " + self._resource{idx}_slab = Slab() + canon_drop_{snake} = exports['canonical_abi_drop_{name}'] + assert(isinstance(canon_drop_{snake}, wasmtime.Func)) + self._canonical_abi_drop_{snake} = canon_drop_{snake} + ", + idx = id.index(), + name = r.name, + snake = r.name.to_snake_case(), + )); + } + self.src.dedent(); + + for func in exports.freestanding_funcs.iter() { + self.src.push_str(&func); + } + + self.src.dedent(); + } + + files.push("bindings.py", self.src.as_bytes()); + } +} + +struct FunctionBindgen<'a> { + gen: &'a mut WasmtimePy, + locals: Ns, + src: Source, + block_storage: Vec, + blocks: Vec<(String, Vec)>, + needs_memory: bool, + needs_realloc: Option, + needs_free: Option, + params: Vec, + payloads: Vec, + src_object: String, +} + +impl FunctionBindgen<'_> { + fn new(gen: &mut WasmtimePy, params: Vec) -> FunctionBindgen<'_> { + 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 + for param in params.iter() { + locals.insert(param).unwrap(); + } + FunctionBindgen { + gen, + locals, + src: Source::default(), + block_storage: Vec::new(), + blocks: Vec::new(), + needs_memory: false, + needs_realloc: None, + needs_free: None, + params, + payloads: Vec::new(), + src_object: "self".to_string(), + } + } + + fn clamp(&mut self, results: &mut Vec, operands: &[String], min: T, max: T) + where + T: std::fmt::Display, + { + self.gen.deps.needs_clamp = true; + results.push(format!("_clamp({}, {}, {})", operands[0], min, max)); + } + + fn load(&mut self, ty: &str, offset: i32, operands: &[String], results: &mut Vec) { + self.needs_memory = true; + self.gen.deps.needs_load = true; + let tmp = self.locals.tmp("load"); + self.src.push_str(&format!( + "{} = _load(ctypes.{}, memory, caller, {}, {})\n", + tmp, ty, operands[0], offset, + )); + results.push(tmp); + } + + fn store(&mut self, ty: &str, offset: i32, operands: &[String]) { + self.needs_memory = true; + self.gen.deps.needs_store = true; + self.src.push_str(&format!( + "_store(ctypes.{}, memory, caller, {}, {}, {})\n", + ty, operands[1], offset, operands[0] + )); + } +} + +impl Bindgen for FunctionBindgen<'_> { + type Operand = String; + + fn sizes(&self) -> &SizeAlign { + &self.gen.sizes + } + + fn push_block(&mut self) { + let prev = mem::take(&mut self.src); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().unwrap(); + let src = mem::replace(&mut self.src, to_restore); + self.blocks.push((src.into(), mem::take(operands))); + } + + fn return_pointer(&mut self, _iface: &Interface, _size: usize, _align: usize) -> String { + unimplemented!() + } + + fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { + array_ty(iface, ty).is_some() + } + + fn emit( + &mut self, + iface: &Interface, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + let mut builder = self.src.builder(&mut self.gen.deps, iface); + 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 => 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::Float32FromF32 + | Instruction::Float64FromF64 + | Instruction::F32FromFloat32 + | Instruction::F64FromFloat64 => results.push(operands.pop().unwrap()), + + // Validate that i32 values coming from wasm are indeed valid code + // points. + Instruction::CharFromI32 => { + builder.deps.needs_validate_guest_char = true; + results.push(format!("_validate_guest_char({})", operands[0])); + } + + Instruction::I32FromChar => { + results.push(format!("ord({})", operands[0])); + } + + Instruction::Bitcasts { casts } => { + for (cast, op) in casts.iter().zip(operands) { + match cast { + Bitcast::I32ToF32 => { + builder.deps.needs_i32_to_f32 = true; + results.push(format!("_i32_to_f32({})", op)); + } + Bitcast::F32ToI32 => { + builder.deps.needs_f32_to_i32 = true; + results.push(format!("_f32_to_i32({})", op)); + } + Bitcast::I64ToF64 => { + builder.deps.needs_i64_to_f64 = true; + results.push(format!("_i64_to_f64({})", op)); + } + Bitcast::F64ToI64 => { + builder.deps.needs_f64_to_i64 = true; + results.push(format!("_f64_to_i64({})", op)); + } + Bitcast::I64ToF32 => { + builder.deps.needs_i32_to_f32 = true; + results.push(format!("_i32_to_f32(({}) & 0xffffffff)", op)); + } + Bitcast::F32ToI64 => { + builder.deps.needs_f32_to_i32 = true; + results.push(format!("_f32_to_i32({})", op)); + } + Bitcast::I32ToI64 | Bitcast::I64ToI32 | Bitcast::None => { + results.push(op.clone()) + } + } + } + } + + Instruction::UnitLower => {} + Instruction::UnitLift => { + results.push("None".to_string()); + } + Instruction::BoolFromI32 => { + let op = self.locals.tmp("operand"); + let ret = self.locals.tmp("boolean"); + builder.push_str(&format!( + " + {op} = {} + if {op} == 0: + {ret} = False + elif {op} == 1: + {ret} = True + else: + raise TypeError(\"invalid variant discriminant for bool\") + ", + operands[0] + )); + results.push(ret); + } + Instruction::I32FromBool => { + results.push(format!("int({})", operands[0])); + } + + // These instructions are used with handles when we're implementing + // imports. This means we interact with the `resources` slabs to + // translate the wasm-provided index into a Python value. + Instruction::I32FromOwnedHandle { ty } => { + results.push(format!("_resources{}.insert({})", ty.index(), operands[0])); + } + Instruction::HandleBorrowedFromI32 { ty } => { + results.push(format!("_resources{}.get({})", ty.index(), operands[0])); + } + + // These instructions are used for handles to objects owned in wasm. + // This means that they're interacting with a wrapper class defined + // in Python. + Instruction::I32FromBorrowedHandle { ty } => { + let obj = self.locals.tmp("obj"); + builder.push_str(&format!("{} = {}\n", obj, operands[0])); + + results.push(format!( + "{}._resource{}_slab.insert({}.clone())", + self.src_object, + ty.index(), + obj, + )); + } + Instruction::HandleOwnedFromI32 { ty } => { + results.push(format!( + "{}._resource{}_slab.remove({})", + self.src_object, + ty.index(), + operands[0], + )); + } + Instruction::RecordLower { record, .. } => { + if record.fields.is_empty() { + return; + } + let tmp = self.locals.tmp("record"); + builder.push_str(&format!("{} = {}\n", tmp, operands[0])); + for field in record.fields.iter() { + let name = self.locals.tmp("field"); + builder.push_str(&format!( + "{} = {}.{}\n", + name, + tmp, + field.name.to_snake_case(), + )); + results.push(name); + } + } + + Instruction::RecordLift { name, .. } => { + results.push(format!("{}({})", name.to_camel_case(), operands.join(", "))); + } + Instruction::TupleLower { tuple, .. } => { + if tuple.types.is_empty() { + return; + } + builder.push_str("("); + for _ in 0..tuple.types.len() { + let name = self.locals.tmp("tuplei"); + builder.push_str(&name); + builder.push_str(","); + results.push(name); + } + builder.push_str(") = "); + builder.push_str(&operands[0]); + builder.push_str("\n"); + } + 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("flags"); + builder.push_str(&format!("{tmp} = 0\n")); + for (i, op) in operands.iter().enumerate() { + let i = 32 * i; + builder.push_str(&format!("{tmp} |= {op} << {i}\n")); + } + tmp + } + }; + results.push(format!("{}({})", name.to_camel_case(), operand)); + } + Instruction::FlagsLower { flags, .. } => match flags.repr().count() { + 1 => results.push(format!("({}).value", operands[0])), + n => { + let tmp = self.locals.tmp("flags"); + self.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, + .. + } => { + 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 { + builder.push_str("if "); + } else { + builder.push_str("elif "); + } + + builder.push_str(&format!( + "isinstance({}, {}{}):\n", + operands[0], + name.to_camel_case(), + case.name.to_camel_case() + )); + builder.indent(); + builder.push_str(&format!("{} = {}.value\n", payload, operands[0])); + builder.push_str(&block); + + for (i, result) in block_results.iter().enumerate() { + builder.push_str(&format!("{} = {}\n", results[i], result)); + } + builder.dedent(); + } + let variant_name = name.to_camel_case(); + builder.push_str("else:\n"); + builder.indent(); + builder.push_str(&format!( + "raise TypeError(\"invalid variant specified for {}\")\n", + variant_name + )); + builder.dedent(); + } + + Instruction::VariantLift { + variant, name, ty, .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + + let result = self.locals.tmp("variant"); + builder.print_var_declaration(&result, &Type::Id(*ty)); + for (i, (case, (block, block_results))) in + variant.cases.iter().zip(blocks).enumerate() + { + if i == 0 { + builder.push_str("if "); + } else { + builder.push_str("elif "); + } + builder.push_str(&format!("{} == {}:\n", operands[0], i)); + builder.indent(); + builder.push_str(&block); + + builder.push_str(&format!( + "{} = {}{}(", + result, + name.to_camel_case(), + case.name.to_camel_case() + )); + assert!(block_results.len() == 1); + builder.push_str(&block_results[0]); + builder.push_str(")\n"); + builder.dedent(); + } + builder.push_str("else:\n"); + builder.indent(); + let variant_name = name.to_camel_case(); + builder.push_str(&format!( + "raise TypeError(\"invalid variant discriminant for {}\")\n", + variant_name + )); + builder.dedent(); + results.push(result); + } + + Instruction::UnionLower { + union, + results: result_types, + name, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + let payloads = self + .payloads + .drain(self.payloads.len() - union.cases.len()..) + .collect::>(); + + for _ in 0..result_types.len() { + results.push(self.locals.tmp("variant")); + } + + // Assumes that type_union has been called for this union + let union_representation = *self + .gen + .union_representation + .get(&name.to_string()) + .unwrap(); + let name = name.to_camel_case(); + let op0 = &operands[0]; + for (i, ((case, (block, block_results)), payload)) in + union.cases.iter().zip(blocks).zip(payloads).enumerate() + { + builder.push_str(if i == 0 { "if " } else { "elif " }); + builder.push_str(&format!("isinstance({op0}, ")); + match union_representation { + // Prints the Python type for this union case + PyUnionRepresentation::Raw => { + builder.print_ty(&case.ty, false); + } + // Prints the name of this union cases dataclass + PyUnionRepresentation::Wrapped => { + builder.push_str(&format!("{name}{i}")); + } + } + builder.push_str(&format!("):\n")); + builder.indent(); + match union_representation { + // Uses the value directly + PyUnionRepresentation::Raw => { + builder.push_str(&format!("{payload} = {op0}\n")) + } + // Uses this union case dataclass's inner value + PyUnionRepresentation::Wrapped => { + builder.push_str(&format!("{payload} = {op0}.value\n")) + } + } + builder.push_str(&block); + for (i, result) in block_results.iter().enumerate() { + builder.push_str(&format!("{} = {result}\n", results[i])); + } + builder.dedent(); + } + builder.push_str("else:\n"); + builder.indent(); + builder.push_str(&format!( + "raise TypeError(\"invalid variant specified for {name}\")\n" + )); + builder.dedent(); + } + + Instruction::UnionLift { + union, name, ty, .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + + let result = self.locals.tmp("variant"); + builder.print_var_declaration(&result, &Type::Id(*ty)); + // Assumes that type_union has been called for this union + let union_representation = *self + .gen + .union_representation + .get(&name.to_string()) + .unwrap(); + let name = name.to_camel_case(); + let op0 = &operands[0]; + for (i, (_case, (block, block_results))) in + union.cases.iter().zip(blocks).enumerate() + { + builder.push_str(if i == 0 { "if " } else { "elif " }); + builder.push_str(&format!("{op0} == {i}:\n")); + builder.indent(); + builder.push_str(&block); + assert!(block_results.len() == 1); + let block_result = &block_results[0]; + builder.push_str(&format!("{result} = ")); + match union_representation { + // Uses the passed value directly + PyUnionRepresentation::Raw => builder.push_str(block_result), + // Constructs an instance of the union cases dataclass + PyUnionRepresentation::Wrapped => { + builder.push_str(&format!("{name}{i}({block_result})")) + } + } + builder.newline(); + builder.dedent(); + } + builder.push_str("else:\n"); + builder.indent(); + builder.push_str(&format!( + "raise TypeError(\"invalid variant discriminant for {name}\")\n", + )); + builder.dedent(); + results.push(result); + } + + Instruction::OptionLower { + results: result_types, + .. + } => { + 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]; + builder.push_str(&format!("if {op0} is None:\n")); + + builder.indent(); + builder.push_str(&none); + for (dst, result) in results.iter().zip(&none_results) { + builder.push_str(&format!("{dst} = {result}\n")); + } + builder.dedent(); + builder.push_str("else:\n"); + builder.indent(); + builder.push_str(&format!("{some_payload} = {op0}\n")); + builder.push_str(&some); + for (dst, result) in results.iter().zip(&some_results) { + builder.push_str(&format!("{dst} = {result}\n")); + } + builder.dedent(); + } + + Instruction::OptionLift { ty, .. } => { + let (some, some_results) = self.blocks.pop().unwrap(); + let (none, none_results) = self.blocks.pop().unwrap(); + assert!(none_results.len() == 1); + assert!(some_results.len() == 1); + let some_result = &some_results[0]; + assert_eq!(none_results[0], "None"); + + let result = self.locals.tmp("option"); + builder.print_var_declaration(&result, &Type::Id(*ty)); + + let op0 = &operands[0]; + builder.push_str(&format!("if {op0} == 0:\n")); + builder.indent(); + builder.push_str(&none); + builder.push_str(&format!("{result} = None\n")); + builder.dedent(); + builder.push_str(&format!("elif {op0} == 1:\n")); + builder.indent(); + builder.push_str(&some); + builder.push_str(&format!("{result} = {some_result}\n")); + builder.dedent(); + + builder.push_str("else:\n"); + builder.indent(); + builder.push_str("raise TypeError(\"invalid variant discriminant for option\")\n"); + builder.dedent(); + + results.push(result); + } + + Instruction::ExpectedLower { + 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(); + + for _ in 0..result_types.len() { + results.push(self.locals.tmp("variant")); + } + + let op0 = &operands[0]; + builder.push_str(&format!("if isinstance({op0}, Ok):\n")); + + builder.indent(); + builder.push_str(&format!("{ok_payload} = {op0}.value\n")); + builder.push_str(&ok); + for (dst, result) in results.iter().zip(&ok_results) { + builder.push_str(&format!("{dst} = {result}\n")); + } + builder.dedent(); + builder.push_str(&format!("elif isinstance({op0}, Err):\n")); + builder.indent(); + builder.push_str(&format!("{err_payload} = {op0}.value\n")); + builder.push_str(&err); + for (dst, result) in results.iter().zip(&err_results) { + builder.push_str(&format!("{dst} = {result}\n")); + } + builder.dedent(); + builder.push_str("else:\n"); + builder.indent(); + builder.push_str(&format!( + "raise TypeError(\"invalid variant specified for expected\")\n", + )); + builder.dedent(); + } + + Instruction::ExpectedLift { ty, .. } => { + let (err, err_results) = self.blocks.pop().unwrap(); + let (ok, ok_results) = self.blocks.pop().unwrap(); + assert!(err_results.len() == 1); + let err_result = &err_results[0]; + assert!(ok_results.len() == 1); + let ok_result = &ok_results[0]; + + let result = self.locals.tmp("expected"); + builder.print_var_declaration(&result, &Type::Id(*ty)); + + let op0 = &operands[0]; + builder.push_str(&format!("if {op0} == 0:\n")); + builder.indent(); + builder.push_str(&ok); + builder.push_str(&format!("{result} = Ok({ok_result})\n")); + builder.dedent(); + builder.push_str(&format!("elif {op0} == 1:\n")); + builder.indent(); + builder.push_str(&err); + builder.push_str(&format!("{result} = Err({err_result})\n")); + builder.dedent(); + + builder.push_str("else:\n"); + builder.indent(); + builder + .push_str("raise TypeError(\"invalid variant discriminant for expected\")\n"); + builder.dedent(); + + results.push(result); + } + + Instruction::EnumLower { .. } => results.push(format!("({}).value", operands[0])), + + Instruction::EnumLift { name, .. } => { + results.push(format!("{}({})", name.to_camel_case(), operands[0])); + } + + Instruction::ListCanonLower { element, realloc } => { + // Lowering only happens when we're passing lists into wasm, + // which forces us to always allocate, so this should always be + // `Some`. + let realloc = realloc.unwrap(); + self.needs_memory = true; + self.needs_realloc = Some(realloc.to_string()); + + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + let array_ty = array_ty(iface, element).unwrap(); + builder.deps.needs_list_canon_lower = true; + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + builder.push_str(&format!( + "{}, {} = _list_canon_lower({}, ctypes.{}, {}, {}, realloc, memory, caller)\n", + ptr, len, operands[0], array_ty, size, align, + )); + results.push(ptr); + results.push(len); + } + Instruction::ListCanonLift { element, free, .. } => { + self.needs_memory = true; + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + builder.push_str(&format!("{} = {}\n", ptr, operands[0])); + builder.push_str(&format!("{} = {}\n", len, operands[1])); + let array_ty = array_ty(iface, element).unwrap(); + builder.deps.needs_list_canon_lift = true; + let lift = format!( + "_list_canon_lift({}, {}, {}, ctypes.{}, memory, caller)", + ptr, + len, + self.gen.sizes.size(element), + array_ty, + ); + builder.deps.pyimport("typing", "cast"); + let align = self.gen.sizes.align(element); + match free { + Some(free) => { + self.needs_free = Some(free.to_string()); + let list = self.locals.tmp("list"); + builder.push_str(&list); + builder.push_str(" = cast("); + builder.print_list(element); + builder.push_str(", "); + builder.push_str(&lift); + builder.push_str(")\n"); + builder.push_str(&format!("free(caller, {}, {}, {})\n", ptr, len, align)); + results.push(list); + } + None => { + let mut result_src = Source::default(); + drop(builder); + let mut builder = result_src.builder(&mut self.gen.deps, iface); + builder.push_str("cast("); + builder.print_list(element); + builder.push_str(", "); + builder.push_str(&lift); + builder.push_str(")"); + results.push(result_src.to_string()); + } + } + } + Instruction::StringLower { realloc } => { + // Lowering only happens when we're passing strings into wasm, + // which forces us to always allocate, so this should always be + // `Some`. + let realloc = realloc.unwrap(); + self.needs_memory = true; + self.needs_realloc = Some(realloc.to_string()); + + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + builder.deps.needs_encode_utf8 = true; + builder.push_str(&format!( + "{}, {} = _encode_utf8({}, realloc, memory, caller)\n", + ptr, len, operands[0], + )); + results.push(ptr); + results.push(len); + } + Instruction::StringLift { free, .. } => { + self.needs_memory = true; + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + builder.push_str(&format!("{} = {}\n", ptr, operands[0])); + builder.push_str(&format!("{} = {}\n", len, operands[1])); + builder.deps.needs_decode_utf8 = true; + let result = format!("_decode_utf8(memory, caller, {}, {})", ptr, len); + match free { + Some(free) => { + self.needs_free = Some(free.to_string()); + let list = self.locals.tmp("list"); + builder.push_str(&format!("{} = {}\n", list, result)); + self.src + .push_str(&format!("free(caller, {}, {}, 1)\n", ptr, len)); + results.push(list); + } + None => results.push(result), + } + } + + Instruction::ListLower { element, realloc } => { + let base = self.payloads.pop().unwrap(); + let e = self.payloads.pop().unwrap(); + let realloc = realloc.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"); + self.needs_realloc = Some(realloc.to_string()); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + + // first store our vec-to-lower in a temporary since we'll + // reference it multiple times. + builder.push_str(&format!("{} = {}\n", vec, operands[0])); + builder.push_str(&format!("{} = len({})\n", len, vec)); + + // ... then realloc space for the result in the guest module + builder.push_str(&format!( + "{} = realloc(caller, 0, 0, {}, {} * {})\n", + result, align, len, size, + )); + builder.push_str(&format!("assert(isinstance({}, int))\n", result)); + + // ... then consume the vector and use the block to lower the + // result. + let i = self.locals.tmp("i"); + builder.push_str(&format!("for {} in range(0, {}):\n", i, len)); + builder.indent(); + builder.push_str(&format!("{} = {}[{}]\n", e, vec, i)); + builder.push_str(&format!("{} = {} + {} * {}\n", base, result, i, size)); + builder.push_str(&body); + builder.dedent(); + + results.push(result); + results.push(len); + } + + Instruction::ListLift { element, free, .. } => { + let (body, body_results) = self.blocks.pop().unwrap(); + let base = self.payloads.pop().unwrap(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + let ptr = self.locals.tmp("ptr"); + let len = self.locals.tmp("len"); + builder.push_str(&format!("{} = {}\n", ptr, operands[0])); + builder.push_str(&format!("{} = {}\n", len, operands[1])); + let result = self.locals.tmp("result"); + builder.push_str(&format!("{}: List[", result)); + builder.print_ty(element, true); + builder.push_str("] = []\n"); + + let i = self.locals.tmp("i"); + builder.push_str(&format!("for {} in range(0, {}):\n", i, len)); + builder.indent(); + builder.push_str(&format!("{} = {} + {} * {}\n", base, ptr, i, size)); + builder.push_str(&body); + assert_eq!(body_results.len(), 1); + builder.push_str(&format!("{}.append({})\n", result, body_results[0])); + builder.dedent(); + + if let Some(free) = free { + self.needs_free = Some(free.to_string()); + builder.push_str(&format!( + "free(caller, {}, {} * {}, {})\n", + ptr, len, size, align, + )); + } + 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 { + iface: _, + name, + sig, + } => { + if sig.results.len() > 0 { + for i in 0..sig.results.len() { + if i > 0 { + builder.push_str(", "); + } + let ret = self.locals.tmp("ret"); + builder.push_str(&ret); + results.push(ret); + } + builder.push_str(" = "); + } + builder.push_str(&self.src_object); + builder.push_str("._"); + builder.push_str(&name.to_snake_case()); + builder.push_str("(caller"); + if operands.len() > 0 { + builder.push_str(", "); + } + builder.push_str(&operands.join(", ")); + builder.push_str(")\n"); + for (ty, name) in sig.results.iter().zip(results.iter()) { + let ty = match ty { + WasmType::I32 | WasmType::I64 => "int", + WasmType::F32 | WasmType::F64 => "float", + }; + self.src + .push_str(&format!("assert(isinstance({}, {}))\n", name, ty)); + } + } + Instruction::CallInterface { module: _, func } => { + match &func.result { + Type::Unit => { + results.push("".to_string()); + } + _ => { + let result = self.locals.tmp("ret"); + builder.push_str(&result); + results.push(result); + builder.push_str(" = "); + } + } + match &func.kind { + FunctionKind::Freestanding | FunctionKind::Static { .. } => { + builder.push_str(&format!( + "host.{}({})", + func.name.to_snake_case(), + operands.join(", "), + )); + } + FunctionKind::Method { name, .. } => { + builder.push_str(&format!( + "{}.{}({})", + operands[0], + name.to_snake_case(), + operands[1..].join(", "), + )); + } + } + builder.push_str("\n"); + } + + Instruction::Return { amt, .. } => match amt { + 0 => {} + 1 => builder.push_str(&format!("return {}\n", operands[0])), + _ => { + self.src + .push_str(&format!("return ({})\n", operands.join(", "))); + } + }, + + Instruction::I32Load { 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 } => 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 { + realloc, + size, + align, + } => { + self.needs_realloc = Some(realloc.to_string()); + let ptr = self.locals.tmp("ptr"); + builder.push_str(&format!( + " + {ptr} = realloc(caller, 0, 0, {align}, {size}) + assert(isinstance({ptr}, int)) + ", + )); + results.push(ptr); + } + + i => unimplemented!("{:?}", i), + } + } +} + +fn py_type_class_of(ty: &Type) -> PyTypeClass { + match ty { + Type::Unit => PyTypeClass::None, + Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::U64 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::S64 => PyTypeClass::Int, + Type::Float32 | Type::Float64 => PyTypeClass::Float, + Type::Char | Type::String => PyTypeClass::Str, + Type::Handle(_) | Type::Id(_) => PyTypeClass::Custom, + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)] +enum PyTypeClass { + None, + Int, + Str, + Float, + Custom, +} + +fn wasm_ty_ctor(ty: WasmType) -> &'static str { + match ty { + WasmType::I32 => "wasmtime.ValType.i32()", + WasmType::I64 => "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 => "int", + WasmType::I64 => "int", + WasmType::F32 => "float", + WasmType::F64 => "float", + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/source.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/source.rs new file mode 100644 index 0000000..3d067cb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/src/source.rs @@ -0,0 +1,501 @@ +use heck::*; +use wit_bindgen_gen_core::wit_parser::*; + +use crate::dependencies::Dependencies; + +/// A [Source] represents some unit of Python code +/// and keeps track of its indent. +#[derive(Default)] +pub struct Source { + s: 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.s.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.indent += 4; + self.s.push_str(" "); + } + + /// Unindent, or in Python terms "dedent", + /// the source one level. + pub fn dedent(&mut self) { + self.indent -= 4; + assert!(self.s.ends_with(" ")); + self.s.pop(); + self.s.pop(); + self.s.pop(); + self.s.pop(); + } + + /// Go to the next line and apply any indent. + pub fn newline(&mut self) { + self.s.push_str("\n"); + for _ in 0..self.indent { + self.s.push_str(" "); + } + } +} + +impl std::ops::Deref for Source { + type Target = str; + fn deref(&self) -> &str { + &self.s + } +} + +impl From for String { + fn from(s: Source) -> String { + s.s + } +} + +/// [SourceBuilder] combines together a [Source] +/// with other contextual information and state. +/// +/// This allows you to generate code for the Source using +/// high-level tools that take care of updating dependencies +/// and retrieving interface details. +/// +/// You can create a [SourceBuilder] easily using a [Source] +/// ``` +/// # use wit_bindgen_gen_wasmtime_py::dependencies::Dependencies; +/// # use wit_bindgen_gen_core::wit_parser::{Interface, Type}; +/// # use wit_bindgen_gen_wasmtime_py::source::Source; +/// # let mut deps = Dependencies::default(); +/// # let mut interface = Interface::default(); +/// # let iface = &interface; +/// let mut source = Source::default(); +/// let mut builder = source.builder(&mut deps, iface); +/// builder.print_ty(&Type::Bool, false); +/// ``` +pub struct SourceBuilder<'s, 'd, 'i> { + source: &'s mut Source, + pub deps: &'d mut Dependencies, + iface: &'i Interface, +} + +impl<'s, 'd, 'i> Source { + /// Create a [SourceBuilder] for the current source. + pub fn builder( + &'s mut self, + deps: &'d mut Dependencies, + iface: &'i Interface, + ) -> SourceBuilder<'s, 'd, 'i> { + SourceBuilder { + source: self, + deps, + iface, + } + } +} + +impl<'s, 'd, 'i> SourceBuilder<'s, 'd, 'i> { + /// See [Dependencies::pyimport]. + pub fn pyimport<'a>(&mut self, module: &str, name: impl Into>) { + self.deps.pyimport(module, name) + } + + /// Appends a type's Python representation to this `Source`. + /// Records any required intrinsics and imports in the `deps`. + /// Uses Python forward reference syntax (e.g. 'Foo') + /// on the root type only if `forward_ref` is true. + pub fn print_ty(&mut self, ty: &Type, forward_ref: bool) { + match ty { + Type::Unit => self.push_str("None"), + Type::Bool => self.push_str("bool"), + Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 => self.push_str("int"), + Type::Float32 | Type::Float64 => self.push_str("float"), + Type::Char => self.push_str("str"), + Type::String => self.push_str("str"), + Type::Handle(id) => { + if forward_ref { + self.push_str("'"); + } + let handle_name = &self.iface.resources[*id].name.to_camel_case(); + self.source.push_str(handle_name); + if forward_ref { + self.push_str("'"); + } + } + Type::Id(id) => { + let ty = &self.iface.types[*id]; + if let Some(name) = &ty.name { + self.push_str(&name.to_camel_case()); + 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(_) + | TypeDefKind::Union(_) => { + unreachable!() + } + TypeDefKind::Option(t) => { + self.deps.pyimport("typing", "Optional"); + self.push_str("Optional["); + self.print_ty(t, true); + self.push_str("]"); + } + TypeDefKind::Expected(e) => { + self.deps.needs_expected = true; + self.push_str("Expected["); + self.print_ty(&e.ok, true); + self.push_str(", "); + self.print_ty(&e.err, true); + self.push_str("]"); + } + TypeDefKind::List(t) => self.print_list(t), + TypeDefKind::Future(t) => { + self.push_str("Future["); + self.print_ty(t, true); + self.push_str("]"); + } + TypeDefKind::Stream(s) => { + self.push_str("Stream["); + self.print_ty(&s.element, true); + self.push_str(", "); + self.print_ty(&s.end, true); + self.push_str("]"); + } + } + } + } + } + + /// Appends a tuple type's Python representation to this `Source`. + /// Records any required intrinsics and imports in the `deps`. + /// Uses Python forward reference syntax (e.g. 'Foo') for named type parameters. + pub fn print_tuple(&mut self, tuple: &Tuple) { + if tuple.types.is_empty() { + return self.push_str("None"); + } + self.deps.pyimport("typing", "Tuple"); + self.push_str("Tuple["); + for (i, t) in tuple.types.iter().enumerate() { + if i > 0 { + self.push_str(", "); + } + self.print_ty(t, true); + } + self.push_str("]"); + } + + /// Appends a Python type representing a sequence of the `element` type to this `Source`. + /// If the element type is `Type::U8`, the result type is `bytes` otherwise it is a `List[T]` + /// Records any required intrinsics and imports in the `deps`. + /// Uses Python forward reference syntax (e.g. 'Foo') for named type parameters. + pub fn print_list(&mut self, element: &Type) { + match element { + Type::U8 => self.push_str("bytes"), + t => { + self.deps.pyimport("typing", "List"); + self.push_str("List["); + self.print_ty(t, true); + self.push_str("]"); + } + } + } + + /// Print variable declaration. + /// Brings name into scope and binds type to it. + pub fn print_var_declaration<'a>(&mut self, name: &'a str, ty: &Type) { + self.push_str(name); + self.push_str(": "); + self.print_ty(ty, true); + self.push_str("\n"); + } + + pub fn print_sig(&mut self, func: &Function, in_import: bool) -> Vec { + if !in_import { + if let FunctionKind::Static { .. } = func.kind { + self.push_str("@classmethod\n"); + } + } + self.source.push_str("def "); + match &func.kind { + FunctionKind::Method { .. } => self.source.push_str(&func.item_name().to_snake_case()), + FunctionKind::Static { .. } if !in_import => { + self.source.push_str(&func.item_name().to_snake_case()) + } + _ => self.source.push_str(&func.name.to_snake_case()), + } + if in_import { + self.source.push_str("(self"); + } else if let FunctionKind::Static { .. } = func.kind { + self.source.push_str("(cls, caller: wasmtime.Store, obj: '"); + self.source.push_str(&self.iface.name.to_camel_case()); + self.source.push_str("'"); + } else { + self.source.push_str("(self, caller: wasmtime.Store"); + } + let mut params = Vec::new(); + for (i, (param, ty)) in func.params.iter().enumerate() { + if i == 0 { + if let FunctionKind::Method { .. } = func.kind { + params.push("self".to_string()); + continue; + } + } + self.source.push_str(", "); + self.source.push_str(¶m.to_snake_case()); + params.push(param.to_snake_case()); + self.source.push_str(": "); + self.print_ty(ty, true); + } + self.source.push_str(") -> "); + self.print_ty(&func.result, true); + params + } + + /// Print a wrapped union definition. + /// e.g. + /// ```py + /// @dataclass + /// class Foo0: + /// value: int + /// + /// @dataclass + /// class Foo1: + /// value: int + /// + /// Foo = Union[Foo0, Foo1] + /// ``` + pub fn print_union_wrapped(&mut self, name: &str, union: &Union, docs: &Docs) { + self.deps.pyimport("dataclasses", "dataclass"); + let mut cases = Vec::new(); + let name = name.to_camel_case(); + for (i, case) in union.cases.iter().enumerate() { + self.source.push_str("@dataclass\n"); + let name = format!("{name}{i}"); + self.source.push_str(&format!("class {name}:\n")); + self.source.indent(); + self.source.docstring(&case.docs); + self.source.push_str("value: "); + self.print_ty(&case.ty, true); + self.source.newline(); + self.source.dedent(); + self.source.newline(); + cases.push(name); + } + + self.deps.pyimport("typing", "Union"); + self.source.comment(docs); + self.source + .push_str(&format!("{name} = Union[{}]\n", cases.join(", "))); + self.source.newline(); + } + + pub fn print_union_raw(&mut self, name: &str, union: &Union, docs: &Docs) { + self.deps.pyimport("typing", "Union"); + self.source.comment(docs); + for case in union.cases.iter() { + self.source.comment(&case.docs); + } + self.source.push_str(&name.to_camel_case()); + self.source.push_str(" = Union["); + let mut first = true; + for case in union.cases.iter() { + if !first { + self.source.push_str(","); + } + self.print_ty(&case.ty, true); + first = false; + } + self.source.push_str("]\n\n"); + } +} + +impl<'s, 'd, 'i> std::ops::Deref for SourceBuilder<'s, 'd, 'i> { + type Target = Source; + fn deref(&self) -> &Source { + &self.source + } +} + +impl<'s, 'd, 'i> std::ops::DerefMut for SourceBuilder<'s, 'd, 'i> { + fn deref_mut(&mut self) -> &mut Source { + &mut self.source + } +} + +#[cfg(test)] +mod tests { + use std::collections::{BTreeMap, BTreeSet}; + + use super::*; + + #[test] + fn simple_append() { + let mut s = Source::default(); + s.push_str("x"); + assert_eq!(s.s, "x"); + s.push_str("y"); + assert_eq!(s.s, "xy"); + s.push_str("z "); + assert_eq!(s.s, "xyz "); + s.push_str(" a "); + assert_eq!(s.s, "xyz a "); + s.push_str("\na"); + assert_eq!(s.s, "xyz a \na"); + } + + #[test] + fn trim_ws() { + let mut s = Source::default(); + s.push_str("def foo():\n return 1\n"); + assert_eq!(s.s, "def foo():\n return 1\n"); + } + + #[test] + fn print_ty_forward_ref() { + let mut deps = Dependencies::default(); + let mut iface = Interface::default(); + // Set up a Resource type to refer to + let resource_id = iface.resources.alloc(Resource { + docs: Docs::default(), + name: "foo".into(), + supertype: None, + foreign_module: None, + }); + iface.resource_lookup.insert("foo".into(), resource_id); + let handle_ty = Type::Handle(resource_id); + // ForwardRef usage can be controlled by an argument to print_ty + let mut s1 = Source::default(); + let mut builder = s1.builder(&mut deps, &iface); + builder.print_ty(&handle_ty, true); + drop(builder); + assert_eq!(s1.s, "'Foo'"); + + let mut s2 = Source::default(); + let mut builder = s2.builder(&mut deps, &iface); + builder.print_ty(&handle_ty, false); + drop(builder); + assert_eq!(s2.s, "Foo"); + + // ForwardRef is used for any types within other types + // Even if the outer type is itself not allowed to be one + let option_id = iface.types.alloc(TypeDef { + docs: Docs::default(), + kind: TypeDefKind::Option(handle_ty), + name: None, + foreign_module: None, + }); + let option_ty = Type::Id(option_id); + let mut s3 = Source::default(); + let mut builder = s3.builder(&mut deps, &iface); + builder.print_ty(&option_ty, false); + drop(builder); + assert_eq!(s3.s, "Optional['Foo']"); + } + + #[test] + fn print_list_bytes() { + // If the element type is u8, it is interpreted as `bytes` + let mut deps = Dependencies::default(); + let iface = Interface::default(); + let mut source = Source::default(); + let mut builder = source.builder(&mut deps, &iface); + builder.print_list(&Type::U8); + drop(builder); + assert_eq!(source.s, "bytes"); + assert_eq!(deps.pyimports, BTreeMap::default()); + } + + #[test] + fn print_list_non_bytes() { + // If the element type is u8, it is interpreted as `bytes` + let mut deps = Dependencies::default(); + let iface = Interface::default(); + let mut source = Source::default(); + let mut builder = source.builder(&mut deps, &iface); + builder.print_list(&Type::Float32); + drop(builder); + assert_eq!(source.s, "List[float]"); + assert_eq!( + deps.pyimports, + BTreeMap::from([("typing".into(), Some(BTreeSet::from(["List".into()])))]) + ); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/tests/codegen.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/tests/codegen.rs new file mode 100644 index 0000000..285eb4a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/tests/codegen.rs @@ -0,0 +1,51 @@ +use std::path::Path; +use std::process::Command; + +mod exports { + test_helpers::codegen_py_export!( + "*.wit" + + // TODO: implement async support + "!async-functions.wit" + ); +} + +mod imports { + test_helpers::codegen_py_import!( + "*.wit" + + // TODO: implement async support + "!async-functions.wit" + + // This uses buffers, which we don't support in imports just yet + // TODO: should support this + "!wasi-next.wit" + "!host.wit" + ); +} + +fn verify(dir: &str, _name: &str) { + let output = Command::new("mypy") + .arg(Path::new(dir).join("bindings.py")) + .arg("--config-file") + .arg("mypy.ini") + .output() + .expect("failed to run `mypy`; do you have it installed?"); + if output.status.success() { + return; + } + panic!( + "mypy failed + +status: {status} + +stdout --- +{stdout} + +stderr --- +{stderr}", + status = output.status, + stdout = String::from_utf8_lossy(&output.stdout).replace("\n", "\n\t"), + stderr = String::from_utf8_lossy(&output.stderr).replace("\n", "\n\t"), + ); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/tests/runtime.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/tests/runtime.rs new file mode 100644 index 0000000..14114b9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime-py/tests/runtime.rs @@ -0,0 +1,75 @@ +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::Command; +use wit_bindgen_gen_core::Generator; + +test_helpers::runtime_tests!("py"); + +fn execute(name: &str, wasm: &Path, py: &Path, imports: &Path, exports: &Path) { + let out_dir = PathBuf::from(env!("OUT_DIR")); + let dir = out_dir.join(name); + drop(fs::remove_dir_all(&dir)); + fs::create_dir_all(&dir).unwrap(); + fs::create_dir_all(&dir.join("imports")).unwrap(); + fs::create_dir_all(&dir.join("exports")).unwrap(); + + println!("OUT_DIR = {:?}", dir); + println!("Generating bindings..."); + // We call `generate_all` with exports from the imports.wit file, and + // imports from the exports.wit wit file. It's reversed because we're + // implementing the host side of these APIs. + let iface = wit_bindgen_gen_core::wit_parser::Interface::parse_file(imports).unwrap(); + let mut files = Default::default(); + wit_bindgen_gen_wasmtime_py::Opts::default() + .build() + .generate_all(&[], &[iface], &mut files); + for (file, contents) in files.iter() { + fs::write(dir.join("imports").join(file), contents).unwrap(); + } + fs::write(dir.join("imports").join("__init__.py"), "").unwrap(); + + let iface = wit_bindgen_gen_core::wit_parser::Interface::parse_file(exports).unwrap(); + let mut files = Default::default(); + wit_bindgen_gen_wasmtime_py::Opts::default() + .build() + .generate_all(&[iface], &[], &mut files); + for (file, contents) in files.iter() { + fs::write(dir.join("exports").join(file), contents).unwrap(); + } + fs::write(dir.join("exports").join("__init__.py"), "").unwrap(); + + println!("Running mypy..."); + exec( + Command::new("mypy") + .env("MYPYPATH", &dir) + .arg(py) + .arg("--cache-dir") + .arg(out_dir.join("mypycache").join(name)), + ); + + exec( + Command::new("python3") + .env("PYTHONPATH", &dir) + .arg(py) + .arg(wasm), + ); +} + +fn exec(cmd: &mut Command) { + println!("{:?}", cmd); + let output = cmd.output().unwrap(); + if output.status.success() { + return; + } + println!("status: {}", output.status); + println!( + "stdout ---\n {}", + String::from_utf8_lossy(&output.stdout).replace("\n", "\n ") + ); + println!( + "stderr ---\n {}", + String::from_utf8_lossy(&output.stderr).replace("\n", "\n ") + ); + panic!("no success"); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/Cargo.toml new file mode 100644 index 0000000..683d19e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "wit-bindgen-gen-wasmtime" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +test = false +doctest = false + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core', version = '0.1.0' } +wit-bindgen-gen-rust = { path = '../gen-rust', version = '0.1.0' } +heck = "0.3" +structopt = { version = "0.3", default-features = false, optional = true } + +[dev-dependencies] +anyhow = "1.0" +test-helpers = { path = '../test-helpers', features = ['wit-bindgen-gen-wasmtime'] } +wasmtime = "0.38.0" +wasmtime-wasi = "0.38.0" +wit-bindgen-wasmtime = { path = '../wasmtime', features = ['tracing', 'async'] } diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/build.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/build.rs new file mode 100644 index 0000000..d7c7fc2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/build.rs @@ -0,0 +1,4 @@ +fn main() { + println!("cargo:rerun-if-changed=build.rs"); + // this build script is currently only here so OUT_DIR is set for testing. +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/src/lib.rs new file mode 100644 index 0000000..84016a1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/src/lib.rs @@ -0,0 +1,2217 @@ +use heck::*; +use std::collections::{BTreeMap, BTreeSet, HashMap, HashSet}; +use std::io::{Read, Write}; +use std::mem; +use std::process::{Command, Stdio}; +use std::str::FromStr; +use wit_bindgen_gen_core::wit_parser::abi::{ + AbiVariant, Bindgen, Instruction, LiftLower, WasmType, +}; +use wit_bindgen_gen_core::{wit_parser::*, Direction, Files, Generator, Source, TypeInfo, Types}; +use wit_bindgen_gen_rust::{ + to_rust_ident, wasm_type, FnSig, RustFlagsRepr, RustFunctionGenerator, RustGenerator, TypeMode, +}; + +#[derive(Default)] +pub struct Wasmtime { + src: Source, + opts: Opts, + needs_get_memory: bool, + needs_get_func: bool, + needs_char_from_i32: bool, + needs_invalid_variant: bool, + needs_validate_flags: bool, + needs_raw_mem: bool, + needs_bad_int: bool, + needs_copy_slice: bool, + needs_buffer_glue: bool, + needs_le: bool, + needs_custom_error_to_trap: bool, + needs_custom_error_to_types: BTreeSet, + all_needed_handles: BTreeSet, + exported_resources: BTreeSet, + types: Types, + guest_imports: HashMap>, + guest_exports: HashMap, + in_import: bool, + in_trait: bool, + trait_name: String, + sizes: SizeAlign, +} + +enum NeededFunction { + Realloc, + Free, +} + +struct Import { + is_async: bool, + name: String, + trait_signature: String, + num_wasm_params: usize, + closure: String, +} + +#[derive(Default)] +struct Exports { + fields: BTreeMap, + funcs: Vec, +} + +#[derive(Default, Debug, Clone)] +#[cfg_attr(feature = "structopt", derive(structopt::StructOpt))] +pub struct Opts { + /// Whether or not `rustfmt` is executed to format generated code. + #[cfg_attr(feature = "structopt", structopt(long))] + pub rustfmt: bool, + + /// Whether or not to emit `tracing` macro calls on function entry/exit. + #[cfg_attr(feature = "structopt", structopt(long))] + pub tracing: bool, + + /// Indicates which functions should be `async`: `all`, `none`, or a + /// comma-separated list. + #[cfg_attr( + feature = "structopt", + structopt(long = "async", default_value = "none") + )] + pub async_: Async, + + /// A flag to indicate that all trait methods in imports should return a + /// custom trait-defined error. Applicable for import bindings. + #[cfg_attr(feature = "structopt", structopt(long))] + pub custom_error: bool, +} + +#[derive(Debug, Clone)] +pub enum Async { + None, + All, + Only(HashSet), +} + +impl Async { + fn includes(&self, name: &str) -> bool { + match self { + Async::None => false, + Async::All => true, + Async::Only(list) => list.contains(name), + } + } + + fn is_none(&self) -> bool { + match self { + Async::None => true, + _ => false, + } + } +} + +impl Default for Async { + fn default() -> Async { + Async::None + } +} + +impl FromStr for Async { + type Err = String; + fn from_str(s: &str) -> Result { + Ok(if s == "all" { + Async::All + } else if s == "none" { + Async::None + } else { + Async::Only(s.split(',').map(|s| s.trim().to_string()).collect()) + }) + } +} + +impl Opts { + pub fn build(self) -> Wasmtime { + let mut r = Wasmtime::new(); + r.opts = self; + r + } +} + +enum FunctionRet { + /// The function return is normal and needs to extra handling. + Normal, + /// The function return was wrapped in a `Result` in Rust. The `Ok` variant + /// is the actual value that will be lowered, and the `Err`, if present, + /// means that a trap has occurred. + CustomToTrap, + /// The function returns a `Result` in both wasm and in Rust, but the + /// Rust error type is a custom error and must be converted to `err`. The + /// `ok` variant payload is provided here too. + CustomToError { ok: Type, err: String }, +} + +impl Wasmtime { + pub fn new() -> Wasmtime { + Wasmtime::default() + } + + fn abi_variant(dir: Direction) -> AbiVariant { + // This generator uses a reversed mapping! In the Wasmtime host-side + // bindings, we don't use any extra adapter layer between guest wasm + // modules and the host. When the guest imports functions using the + // `GuestImport` ABI, the host directly implements the `GuestImport` + // ABI, even though the host is *exporting* functions. Similarly, when + // the guest exports functions using the `GuestExport` ABI, the host + // directly imports them with the `GuestExport` ABI, even though the + // host is *importing* functions. + match dir { + Direction::Import => AbiVariant::GuestExport, + Direction::Export => AbiVariant::GuestImport, + } + } + + fn print_intrinsics(&mut self) { + if self.needs_raw_mem { + self.push_str("use wit_bindgen_wasmtime::rt::RawMem;\n"); + } + if self.needs_char_from_i32 { + self.push_str("use wit_bindgen_wasmtime::rt::char_from_i32;\n"); + } + if self.needs_invalid_variant { + self.push_str("use wit_bindgen_wasmtime::rt::invalid_variant;\n"); + } + if self.needs_bad_int { + self.push_str("use core::convert::TryFrom;\n"); + self.push_str("use wit_bindgen_wasmtime::rt::bad_int;\n"); + } + if self.needs_validate_flags { + self.push_str("use wit_bindgen_wasmtime::rt::validate_flags;\n"); + } + if self.needs_le { + self.push_str("use wit_bindgen_wasmtime::Le;\n"); + } + if self.needs_copy_slice { + self.push_str("use wit_bindgen_wasmtime::rt::copy_slice;\n"); + } + } + + /// Classifies the return value of a function to see if it needs handling + /// with respect to the `custom_error` configuration option. + fn classify_fn_ret(&mut self, iface: &Interface, f: &Function) -> FunctionRet { + if !self.opts.custom_error { + return FunctionRet::Normal; + } + + if let Type::Id(id) = &f.result { + if let TypeDefKind::Expected(e) = &iface.types[*id].kind { + if let Type::Id(err) = e.err { + if let Some(name) = &iface.types[err].name { + self.needs_custom_error_to_types.insert(name.clone()); + return FunctionRet::CustomToError { + ok: e.ok, + err: name.to_string(), + }; + } + } + } + } + + self.needs_custom_error_to_trap = true; + FunctionRet::CustomToTrap + } +} + +impl RustGenerator for Wasmtime { + fn default_param_mode(&self) -> TypeMode { + if self.in_import { + // The default here is that only leaf values can be borrowed because + // otherwise lists and such need to be copied into our own memory. + TypeMode::LeafBorrowed("'a") + } else { + // When we're calling wasm exports, however, there's no need to take + // any ownership of anything from the host so everything is borrowed + // in the parameter position. + TypeMode::AllBorrowed("'a") + } + } + + fn handle_projection(&self) -> Option<(&'static str, String)> { + if self.in_import { + if self.in_trait { + Some(("Self", self.trait_name.clone())) + } else { + Some(("T", self.trait_name.clone())) + } + } else { + None + } + } + + fn handle_wrapper(&self) -> Option<&'static str> { + None + } + + fn push_str(&mut self, s: &str) { + self.src.push_str(s); + } + + fn info(&self, ty: TypeId) -> TypeInfo { + self.types.get(ty) + } + + fn types_mut(&mut self) -> &mut Types { + &mut self.types + } + + fn print_borrowed_slice( + &mut self, + iface: &Interface, + mutbl: bool, + ty: &Type, + lifetime: &'static str, + ) { + if self.sizes.align(ty) > 1 && self.in_import { + // If we're generating bindings for an import we ideally want to + // hand out raw pointers into memory. We can't guarantee anything + // about alignment in memory, though, so if the alignment + // requirement is bigger than one then we have to use slices where + // the type has a `Le<...>` wrapper. + // + // For exports we're generating functions that take values from + // Rust, so we can assume alignment and use raw slices. For types + // with an align of 1, then raw pointers are fine since Rust will + // have the same alignment requirement. + self.needs_le = true; + self.push_str("&"); + if lifetime != "'_" { + self.push_str(lifetime); + self.push_str(" "); + } + if mutbl { + self.push_str(" mut "); + } + self.push_str("[Le<"); + self.print_ty(iface, ty, TypeMode::AllBorrowed(lifetime)); + self.push_str(">]"); + } else { + self.print_rust_slice(iface, mutbl, ty, lifetime); + } + } + + fn print_borrowed_str(&mut self, lifetime: &'static str) { + self.push_str("&"); + if lifetime != "'_" { + self.push_str(lifetime); + self.push_str(" "); + } + self.push_str(" str"); + } +} + +impl Generator for Wasmtime { + fn preprocess_one(&mut self, iface: &Interface, dir: Direction) { + let variant = Self::abi_variant(dir); + self.types.analyze(iface); + self.in_import = variant == AbiVariant::GuestImport; + self.trait_name = iface.name.to_camel_case(); + self.src.push_str(&format!( + "#[allow(clippy::all)]\npub mod {} {{\n", + iface.name.to_snake_case(), + )); + self.src + .push_str("#[allow(unused_imports)]\nuse wit_bindgen_wasmtime::{wasmtime, anyhow};\n"); + self.sizes.fill(iface); + } + + fn type_record( + &mut self, + iface: &Interface, + id: TypeId, + name: &str, + record: &Record, + docs: &Docs, + ) { + self.print_typedef_record(iface, id, record, docs); + + // If this record might be used as a slice type in various places then + // we synthesize an `Endian` implementation for it so `&[Le]` + // is usable. + if self.modes_of(iface, id).len() > 0 + && record.fields.iter().all(|f| iface.all_bits_valid(&f.ty)) + { + self.src.push_str("impl wit_bindgen_wasmtime::Endian for "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str(" {\n"); + + self.src.push_str("fn into_le(self) -> Self {\n"); + self.src.push_str("Self {\n"); + for field in record.fields.iter() { + self.src.push_str(&field.name.to_snake_case()); + self.src.push_str(": self."); + self.src.push_str(&field.name.to_snake_case()); + self.src.push_str(".into_le(),\n"); + } + self.src.push_str("}\n"); + self.src.push_str("}\n"); + + self.src.push_str("fn from_le(self) -> Self {\n"); + self.src.push_str("Self {\n"); + for field in record.fields.iter() { + self.src.push_str(&field.name.to_snake_case()); + self.src.push_str(": self."); + self.src.push_str(&field.name.to_snake_case()); + self.src.push_str(".from_le(),\n"); + } + self.src.push_str("}\n"); + self.src.push_str("}\n"); + + self.src.push_str("}\n"); + + // Also add an `AllBytesValid` valid impl since this structure's + // byte representations are valid (guarded by the `all_bits_valid` + // predicate). + self.src + .push_str("unsafe impl wit_bindgen_wasmtime::AllBytesValid for "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str(" {}\n"); + } + } + + fn type_tuple( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + tuple: &Tuple, + docs: &Docs, + ) { + self.print_typedef_tuple(iface, id, tuple, docs); + } + + fn type_flags( + &mut self, + _iface: &Interface, + _id: TypeId, + name: &str, + flags: &Flags, + docs: &Docs, + ) { + self.src + .push_str("wit_bindgen_wasmtime::bitflags::bitflags! {\n"); + self.rustdoc(docs); + let repr = RustFlagsRepr::new(flags); + self.src + .push_str(&format!("pub struct {}: {repr} {{\n", name.to_camel_case())); + for (i, flag) in flags.flags.iter().enumerate() { + self.rustdoc(&flag.docs); + self.src.push_str(&format!( + "const {} = 1 << {};\n", + flag.name.to_shouty_snake_case(), + i, + )); + } + self.src.push_str("}\n"); + self.src.push_str("}\n\n"); + + self.src.push_str("impl core::fmt::Display for "); + self.src.push_str(&name.to_camel_case()); + self.src.push_str( + "{\nfn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {\n", + ); + + self.src.push_str("f.write_str(\""); + self.src.push_str(&name.to_camel_case()); + self.src.push_str("(\")?;\n"); + self.src.push_str("core::fmt::Debug::fmt(self, f)?;\n"); + self.src.push_str("f.write_str(\" (0x\")?;\n"); + self.src + .push_str("core::fmt::LowerHex::fmt(&self.bits, f)?;\n"); + self.src.push_str("f.write_str(\"))\")?;\n"); + self.src.push_str("Ok(())"); + + self.src.push_str("}\n"); + self.src.push_str("}\n\n"); + } + + fn type_variant( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + variant: &Variant, + docs: &Docs, + ) { + self.print_typedef_variant(iface, id, variant, docs); + } + + fn type_union( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + union: &Union, + docs: &Docs, + ) { + self.print_typedef_union(iface, id, union, docs); + } + + fn type_option( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + payload: &Type, + docs: &Docs, + ) { + self.print_typedef_option(iface, id, payload, docs); + } + + fn type_expected( + &mut self, + iface: &Interface, + id: TypeId, + _name: &str, + expected: &Expected, + docs: &Docs, + ) { + self.print_typedef_expected(iface, id, expected, docs); + } + + fn type_enum(&mut self, _iface: &Interface, id: TypeId, name: &str, enum_: &Enum, docs: &Docs) { + self.print_typedef_enum(id, name, enum_, docs); + } + + fn type_resource(&mut self, iface: &Interface, ty: ResourceId) { + let name = &iface.resources[ty].name; + self.all_needed_handles.insert(name.to_string()); + + // If we're binding imports then all handles are associated types so + // there's nothing that we need to do about that. + if self.in_import { + return; + } + + self.exported_resources.insert(ty); + + // ... otherwise for exports we generate a newtype wrapper around an + // `i32` to manage the resultt. + let tyname = name.to_camel_case(); + self.rustdoc(&iface.resources[ty].docs); + self.src.push_str("#[derive(Debug)]\n"); + self.src.push_str(&format!( + "pub struct {}(wit_bindgen_wasmtime::rt::ResourceIndex);\n", + tyname + )); + } + + fn type_alias(&mut self, iface: &Interface, id: TypeId, _name: &str, ty: &Type, docs: &Docs) { + self.print_typedef_alias(iface, id, ty, docs); + } + + fn type_list(&mut self, iface: &Interface, id: TypeId, _name: &str, ty: &Type, docs: &Docs) { + self.print_type_list(iface, id, ty, docs); + } + + fn type_builtin(&mut self, iface: &Interface, _id: TypeId, name: &str, ty: &Type, docs: &Docs) { + self.rustdoc(docs); + self.src + .push_str(&format!("pub type {}", name.to_camel_case())); + self.src.push_str(" = "); + self.print_ty(iface, ty, TypeMode::Owned); + self.src.push_str(";\n"); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "export" uses the "guest import" ABI variant on the inside of + // this `Generator` implementation. + fn export(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let prev = mem::take(&mut self.src); + + // Generate the closure that's passed to a `Linker`, the final piece of + // codegen here. + let sig = iface.wasm_signature(AbiVariant::GuestImport, func); + let params = (0..sig.params.len()) + .map(|i| format!("arg{}", i)) + .collect::>(); + let mut f = FunctionBindgen::new(self, params); + iface.call( + AbiVariant::GuestImport, + LiftLower::LiftArgsLowerResults, + func, + &mut f, + ); + let FunctionBindgen { + src, + cleanup, + needs_borrow_checker, + needs_memory, + needs_buffer_transaction, + needs_functions, + closures, + async_intrinsic_called, + .. + } = f; + assert!(cleanup.is_none()); + assert!(!needs_buffer_transaction); + + // Generate the signature this function will have in the final trait + let self_arg = "&mut self".to_string(); + self.in_trait = true; + + let mut fnsig = FnSig::default(); + fnsig.private = true; + fnsig.async_ = self.opts.async_.includes(&func.name); + fnsig.self_arg = Some(self_arg); + self.print_docs_and_params(iface, func, TypeMode::LeafBorrowed("'_"), &fnsig); + // The Rust return type may differ from the wasm return type based on + // the `custom_error` configuration of this code generator. + match self.classify_fn_ret(iface, func) { + FunctionRet::Normal => { + self.push_str(" -> "); + self.print_ty(iface, &func.result, TypeMode::Owned); + } + FunctionRet::CustomToTrap => { + self.push_str(" -> Result<"); + self.print_ty(iface, &func.result, TypeMode::Owned); + self.push_str(", Self::Error>"); + } + FunctionRet::CustomToError { ok, .. } => { + self.push_str(" -> Result<"); + self.print_ty(iface, &ok, TypeMode::Owned); + self.push_str(", Self::Error>"); + } + } + self.in_trait = false; + let trait_signature = mem::take(&mut self.src).into(); + + // Generate the closure that's passed to a `Linker`, the final piece of + // codegen here. + self.src + .push_str("move |mut caller: wasmtime::Caller<'_, T>"); + for (i, param) in sig.params.iter().enumerate() { + let arg = format!("arg{}", i); + self.src.push_str(","); + self.src.push_str(&arg); + self.src.push_str(":"); + self.wasm_type(*param); + } + self.src.push_str("| {\n"); + + // If an intrinsic was called asynchronously, which happens if anything + // in the module could be asynchronous, then we must wrap this host + // import with an async block. Otherwise if the function is itself + // explicitly async then we must also wrap it in an async block. + // + // If none of that happens, then this is fine to be sync because + // everything is sync. + let is_async = if async_intrinsic_called || self.opts.async_.includes(&func.name) { + self.src.push_str("Box::new(async move {\n"); + true + } else { + false + }; + + if self.opts.tracing { + self.src.push_str(&format!( + " + let span = wit_bindgen_wasmtime::tracing::span!( + wit_bindgen_wasmtime::tracing::Level::TRACE, + \"wit-bindgen abi\", + module = \"{}\", + function = \"{}\", + ); + let _enter = span.enter(); + ", + iface.name, func.name, + )); + } + self.src.push_str(&closures); + + for (name, func) in needs_functions { + self.src.push_str(&format!( + " + let func = get_func(&mut caller, \"{name}\")?; + let func_{name} = func.typed::<{cvt}, _>(&caller)?; + ", + name = name, + cvt = func.cvt(), + )); + self.needs_get_func = true; + } + + if needs_memory || needs_borrow_checker { + self.src + .push_str("let memory = &get_memory(&mut caller, \"memory\")?;\n"); + self.needs_get_memory = true; + } + + if needs_borrow_checker { + self.src.push_str( + "let (mem, data) = memory.data_and_store_mut(&mut caller); + let mut _bc = wit_bindgen_wasmtime::BorrowChecker::new(mem); + let host = get(data);\n", + ); + } else { + self.src.push_str("let host = get(caller.data_mut());\n"); + } + + if self.all_needed_handles.len() > 0 { + self.src.push_str("let (host, _tables) = host;\n"); + } + + self.src.push_str(&String::from(src)); + + if is_async { + self.src.push_str("})\n"); + } + self.src.push_str("}"); + let closure = mem::replace(&mut self.src, prev).into(); + + self.guest_imports + .entry(iface.name.to_string()) + .or_insert(Vec::new()) + .push(Import { + is_async, + num_wasm_params: sig.params.len(), + name: func.name.to_string(), + closure, + trait_signature, + }); + } + + // As with `abi_variant` above, we're generating host-side bindings here + // so a user "import" uses the "export" ABI variant on the inside of + // this `Generator` implementation. + fn import(&mut self, iface: &Interface, func: &Function) { + assert!(!func.is_async, "async not supported yet"); + let prev = mem::take(&mut self.src); + + // If anything is asynchronous on exports then everything must be + // asynchronous, Wasmtime can't intermix async and sync calls because + // it's unknown whether the wasm module will make an async host call. + let is_async = !self.opts.async_.is_none(); + let mut sig = FnSig::default(); + sig.async_ = is_async; + sig.self_arg = Some("&self, mut caller: impl wasmtime::AsContextMut".to_string()); + self.print_docs_and_params(iface, func, TypeMode::AllBorrowed("'_"), &sig); + self.push_str("-> Result<"); + self.print_ty(iface, &func.result, TypeMode::Owned); + self.push_str(", wasmtime::Trap> {\n"); + + let params = func + .params + .iter() + .map(|(name, _)| to_rust_ident(name).to_string()) + .collect(); + let mut f = FunctionBindgen::new(self, params); + iface.call( + AbiVariant::GuestExport, + LiftLower::LowerArgsLiftResults, + func, + &mut f, + ); + let FunctionBindgen { + needs_memory, + src, + needs_borrow_checker, + needs_buffer_transaction, + closures, + needs_functions, + .. + } = f; + + let exports = self + .guest_exports + .entry(iface.name.to_string()) + .or_insert_with(Exports::default); + for (name, func) in needs_functions { + self.src + .push_str(&format!("let func_{0} = &self.{0};\n", name)); + let get = format!( + "instance.get_typed_func::<{}, _>(&mut store, \"{}\")?", + func.cvt(), + name + ); + exports.fields.insert(name, (func.ty(), get)); + } + + self.src.push_str(&closures); + + assert!(!needs_borrow_checker); + if needs_memory { + self.src.push_str("let memory = &self.memory;\n"); + exports.fields.insert( + "memory".to_string(), + ( + "wasmtime::Memory".to_string(), + "instance + .get_memory(&mut store, \"memory\") + .ok_or_else(|| { + anyhow::anyhow!(\"`memory` export not a memory\") + })? + " + .to_string(), + ), + ); + } + + if needs_buffer_transaction { + self.needs_buffer_glue = true; + self.src + .push_str("let mut buffer_transaction = self.buffer_glue.transaction();\n"); + } + + self.src.push_str(&String::from(src)); + self.src.push_str("}\n"); + let func_body = mem::replace(&mut self.src, prev); + exports.funcs.push(func_body.into()); + + // Create the code snippet which will define the type of this field in + // the struct that we're exporting and additionally extracts the + // function from an instantiated instance. + let sig = iface.wasm_signature(AbiVariant::GuestExport, func); + let mut cvt = "(".to_string(); + for param in sig.params.iter() { + cvt.push_str(wasm_type(*param)); + cvt.push_str(","); + } + cvt.push_str("), ("); + for result in sig.results.iter() { + cvt.push_str(wasm_type(*result)); + cvt.push_str(","); + } + cvt.push_str(")"); + exports.fields.insert( + to_rust_ident(&func.name), + ( + format!("wasmtime::TypedFunc<{}>", cvt), + format!( + "instance.get_typed_func::<{}, _>(&mut store, \"{}\")?", + cvt, func.name, + ), + ), + ); + } + + fn finish_one(&mut self, iface: &Interface, files: &mut Files) { + for (module, funcs) in sorted_iter(&self.guest_imports) { + let module_camel = module.to_camel_case(); + let is_async = !self.opts.async_.is_none(); + if is_async { + self.src.push_str("#[wit_bindgen_wasmtime::async_trait]\n"); + } + self.src.push_str("pub trait "); + self.src.push_str(&module_camel); + self.src.push_str(": Sized "); + if is_async { + self.src.push_str(" + Send"); + } + self.src.push_str("{\n"); + if self.all_needed_handles.len() > 0 { + for handle in self.all_needed_handles.iter() { + self.src.push_str("type "); + self.src.push_str(&handle.to_camel_case()); + self.src.push_str(": std::fmt::Debug"); + if is_async { + self.src.push_str(" + Send + Sync"); + } + self.src.push_str(";\n"); + } + } + if self.opts.custom_error { + self.src.push_str("type Error;\n"); + if self.needs_custom_error_to_trap { + self.src.push_str( + "fn error_to_trap(&mut self, err: Self::Error) -> wasmtime::Trap;\n", + ); + } + for ty in self.needs_custom_error_to_types.iter() { + self.src.push_str(&format!( + "fn error_to_{}(&mut self, err: Self::Error) -> Result<{}, wasmtime::Trap>;\n", + ty.to_snake_case(), + ty.to_camel_case(), + )); + } + } + for f in funcs { + self.src.push_str(&f.trait_signature); + self.src.push_str(";\n\n"); + } + for handle in self.all_needed_handles.iter() { + self.src.push_str(&format!( + "fn drop_{}(&mut self, state: Self::{}) {{ + drop(state); + }}\n", + handle.to_snake_case(), + handle.to_camel_case(), + )); + } + self.src.push_str("}\n"); + + if self.all_needed_handles.len() > 0 { + self.src.push_str("\npub struct "); + self.src.push_str(&module_camel); + self.src.push_str("Tables {\n"); + for handle in self.all_needed_handles.iter() { + self.src.push_str("pub(crate) "); + self.src.push_str(&handle.to_snake_case()); + self.src.push_str("_table: wit_bindgen_wasmtime::Table,\n"); + } + self.src.push_str("}\n"); + self.src.push_str("impl Default for "); + self.src.push_str(&module_camel); + self.src.push_str("Tables {\n"); + self.src.push_str("fn default() -> Self { Self {"); + for handle in self.all_needed_handles.iter() { + self.src.push_str(&handle.to_snake_case()); + self.src.push_str("_table: Default::default(),"); + } + self.src.push_str("}}}"); + } + } + + for (module, funcs) in mem::take(&mut self.guest_imports) { + let module_camel = module.to_camel_case(); + let is_async = !self.opts.async_.is_none(); + self.push_str("\npub fn add_to_linker(linker: &mut wasmtime::Linker"); + self.push_str(", get: impl Fn(&mut T) -> "); + if self.all_needed_handles.is_empty() { + self.push_str("&mut U"); + } else { + self.push_str(&format!("(&mut U, &mut {}Tables)", module_camel)); + } + self.push_str("+ Send + Sync + Copy + 'static) -> anyhow::Result<()> \n"); + self.push_str("where U: "); + self.push_str(&module_camel); + if is_async { + self.push_str(", T: Send,"); + } + self.push_str("\n{\n"); + if self.needs_get_memory { + self.push_str("use wit_bindgen_wasmtime::rt::get_memory;\n"); + } + if self.needs_get_func { + self.push_str("use wit_bindgen_wasmtime::rt::get_func;\n"); + } + for f in funcs { + let method = if f.is_async { + format!("func_wrap{}_async", f.num_wasm_params) + } else { + String::from("func_wrap") + }; + self.push_str(&format!( + "linker.{}(\"{}\", \"{}\", {})?;\n", + method, module, f.name, f.closure, + )); + } + for handle in self.all_needed_handles.iter() { + self.src.push_str(&format!( + "linker.func_wrap( + \"canonical_abi\", + \"resource_drop_{name}\", + move |mut caller: wasmtime::Caller<'_, T>, handle: u32| {{ + let (host, tables) = get(caller.data_mut()); + let handle = tables + .{snake}_table + .remove(handle) + .map_err(|e| {{ + wasmtime::Trap::new(format!(\"failed to remove handle: {{}}\", e)) + }})?; + host.drop_{snake}(handle); + Ok(()) + }} + )?;\n", + name = handle, + snake = handle.to_snake_case(), + )); + } + self.push_str("Ok(())\n}\n"); + } + + for (module, exports) in sorted_iter(&mem::take(&mut self.guest_exports)) { + let name = module.to_camel_case(); + + // Generate a struct that is the "state" of this exported module + // which is required to be included in the host state `T` of the + // store. + self.push_str( + " + /// Auxiliary data associated with the wasm exports. + /// + /// This is required to be stored within the data of a + /// `Store` itself so lifting/lowering state can be managed + /// when translating between the host and wasm. + ", + ); + self.push_str("#[derive(Default)]\n"); + self.push_str("pub struct "); + self.push_str(&name); + self.push_str("Data {\n"); + for r in self.exported_resources.iter() { + self.src.push_str(&format!( + " + index_slab{}: wit_bindgen_wasmtime::rt::IndexSlab, + resource_slab{0}: wit_bindgen_wasmtime::rt::ResourceSlab, + dtor{0}: Option>, + ", + r.index() + )); + } + self.push_str("}\n"); + + self.push_str("pub struct "); + self.push_str(&name); + self.push_str(" {\n"); + self.push_str(&format!( + "get_state: Box &mut {}Data + Send + Sync>,\n", + name + )); + for (name, (ty, _)) in exports.fields.iter() { + self.push_str(name); + self.push_str(": "); + self.push_str(ty); + self.push_str(",\n"); + } + self.push_str("}\n"); + let bound = if self.opts.async_.is_none() { + "" + } else { + ": Send" + }; + self.push_str(&format!("impl {} {{\n", bound, name)); + + if self.exported_resources.len() == 0 { + self.push_str("#[allow(unused_variables)]\n"); + } + self.push_str(&format!( + " + /// Adds any intrinsics, if necessary for this exported wasm + /// functionality to the `linker` provided. + /// + /// The `get_state` closure is required to access the + /// auxiliary data necessary for these wasm exports from + /// the general store's state. + pub fn add_to_linker( + linker: &mut wasmtime::Linker, + get_state: impl Fn(&mut T) -> &mut {}Data + Send + Sync + Copy + 'static, + ) -> anyhow::Result<()> {{ + ", + name, + )); + for r in self.exported_resources.iter() { + let (func_wrap, call, wait, prefix, suffix) = if self.opts.async_.is_none() { + ("func_wrap", "call", "", "", "") + } else { + ( + "func_wrap1_async", + "call_async", + ".await", + "Box::new(async move {", + "})", + ) + }; + self.src.push_str(&format!( + " + linker.{func_wrap}( + \"canonical_abi\", + \"resource_drop_{name}\", + move |mut caller: wasmtime::Caller<'_, T>, idx: u32| {prefix}{{ + let state = get_state(caller.data_mut()); + let resource_idx = state.index_slab{idx}.remove(idx)?; + let wasm = match state.resource_slab{idx}.drop(resource_idx) {{ + Some(wasm) => wasm, + None => return Ok(()), + }}; + let dtor = state.dtor{idx}.expect(\"destructor not set yet\"); + dtor.{call}(&mut caller, wasm){wait}?; + Ok(()) + }}{suffix}, + )?; + linker.func_wrap( + \"canonical_abi\", + \"resource_clone_{name}\", + move |mut caller: wasmtime::Caller<'_, T>, idx: u32| {{ + let state = get_state(caller.data_mut()); + let resource_idx = state.index_slab{idx}.get(idx)?; + state.resource_slab{idx}.clone(resource_idx)?; + Ok(state.index_slab{idx}.insert(resource_idx)) + }}, + )?; + linker.func_wrap( + \"canonical_abi\", + \"resource_get_{name}\", + move |mut caller: wasmtime::Caller<'_, T>, idx: u32| {{ + let state = get_state(caller.data_mut()); + let resource_idx = state.index_slab{idx}.get(idx)?; + Ok(state.resource_slab{idx}.get(resource_idx)) + }}, + )?; + linker.func_wrap( + \"canonical_abi\", + \"resource_new_{name}\", + move |mut caller: wasmtime::Caller<'_, T>, val: i32| {{ + let state = get_state(caller.data_mut()); + let resource_idx = state.resource_slab{idx}.insert(val); + Ok(state.index_slab{idx}.insert(resource_idx)) + }}, + )?; + ", + name = iface.resources[*r].name, + idx = r.index(), + func_wrap = func_wrap, + call = call, + wait = wait, + prefix = prefix, + suffix = suffix, + )); + } + self.push_str("Ok(())\n"); + self.push_str("}\n"); + + let (async_fn, instantiate, wait) = if self.opts.async_.is_none() { + ("", "", "") + } else { + ("async ", "_async", ".await") + }; + self.push_str(&format!( + " + /// Instantiates the provided `module` using the specified + /// parameters, wrapping up the result in a structure that + /// translates between wasm and the host. + /// + /// The `linker` provided will have intrinsics added to it + /// automatically, so it's not necessary to call + /// `add_to_linker` beforehand. This function will + /// instantiate the `module` otherwise using `linker`, and + /// both an instance of this structure and the underlying + /// `wasmtime::Instance` will be returned. + /// + /// The `get_state` parameter is used to access the + /// auxiliary state necessary for these wasm exports from + /// the general store state `T`. + pub {}fn instantiate( + mut store: impl wasmtime::AsContextMut, + module: &wasmtime::Module, + linker: &mut wasmtime::Linker, + get_state: impl Fn(&mut T) -> &mut {}Data + Send + Sync + Copy + 'static, + ) -> anyhow::Result<(Self, wasmtime::Instance)> {{ + Self::add_to_linker(linker, get_state)?; + let instance = linker.instantiate{}(&mut store, module){}?; + Ok((Self::new(store, &instance,get_state)?, instance)) + }} + ", + async_fn, name, instantiate, wait, + )); + + self.push_str(&format!( + " + /// Low-level creation wrapper for wrapping up the exports + /// of the `instance` provided in this structure of wasm + /// exports. + /// + /// This function will extract exports from the `instance` + /// defined within `store` and wrap them all up in the + /// returned structure which can be used to interact with + /// the wasm module. + pub fn new( + mut store: impl wasmtime::AsContextMut, + instance: &wasmtime::Instance, + get_state: impl Fn(&mut T) -> &mut {}Data + Send + Sync + Copy + 'static, + ) -> anyhow::Result {{ + ", + name, + )); + self.push_str("let mut store = store.as_context_mut();\n"); + assert!(!self.needs_get_func); + for (name, (_, get)) in exports.fields.iter() { + self.push_str("let "); + self.push_str(&name); + self.push_str("= "); + self.push_str(&get); + self.push_str(";\n"); + } + for r in self.exported_resources.iter() { + self.src.push_str(&format!( + " + get_state(store.data_mut()).dtor{} = \ + Some(instance.get_typed_func::(\ + &mut store, \ + \"canonical_abi_drop_{}\", \ + )?);\n + ", + r.index(), + iface.resources[*r].name, + )); + } + self.push_str("Ok("); + self.push_str(&name); + self.push_str("{\n"); + for (name, _) in exports.fields.iter() { + self.push_str(name); + self.push_str(",\n"); + } + self.push_str("get_state: Box::new(get_state),\n"); + self.push_str("\n})\n"); + self.push_str("}\n"); + + for func in exports.funcs.iter() { + self.push_str(func); + } + + for r in self.exported_resources.iter() { + let (async_fn, call, wait) = if self.opts.async_.is_none() { + ("", "call", "") + } else { + ("async ", "call_async", ".await") + }; + self.src.push_str(&format!( + " + /// Drops the host-owned handle to the resource + /// specified. + /// + /// Note that this may execute the WebAssembly-defined + /// destructor for this type. This also may not run + /// the destructor if there are still other references + /// to this type. + pub {async}fn drop_{name_snake}( + &self, + mut store: impl wasmtime::AsContextMut, + val: {name_camel}, + ) -> Result<(), wasmtime::Trap> {{ + let mut store = store.as_context_mut(); + let data = (self.get_state)(store.data_mut()); + let wasm = match data.resource_slab{idx}.drop(val.0) {{ + Some(val) => val, + None => return Ok(()), + }}; + data.dtor{idx}.unwrap().{call}(&mut store, wasm){wait}?; + Ok(()) + }} + ", + name_snake = iface.resources[*r].name.to_snake_case(), + name_camel = iface.resources[*r].name.to_camel_case(), + idx = r.index(), + async = async_fn, + call = call, + wait = wait, + )); + } + + self.push_str("}\n"); + } + self.print_intrinsics(); + + // Close the opening `mod`. + self.push_str("}\n"); + + let mut src = mem::take(&mut self.src); + if self.opts.rustfmt { + let mut child = Command::new("rustfmt") + .arg("--edition=2018") + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .spawn() + .expect("failed to spawn `rustfmt`"); + child + .stdin + .take() + .unwrap() + .write_all(src.as_bytes()) + .unwrap(); + src.as_mut_string().truncate(0); + child + .stdout + .take() + .unwrap() + .read_to_string(src.as_mut_string()) + .unwrap(); + let status = child.wait().unwrap(); + assert!(status.success()); + } + + files.push("bindings.rs", src.as_bytes()); + } +} + +struct FunctionBindgen<'a> { + gen: &'a mut Wasmtime, + + // Number used to assign unique names to temporary variables. + tmp: usize, + + // Destination where source code is pushed onto for this function + src: Source, + + // The named parameters that are available to this function + params: Vec, + + // Management of block scopes used by `Bindgen`. + block_storage: Vec, + blocks: Vec, + + // Whether or not the code generator is after the invocation of wasm or the + // host, used for knowing where to acquire memory from. + after_call: bool, + // Whether or not the `caller_memory` variable has been defined and is + // available for use. + caller_memory_available: bool, + // Whether or not a helper function was called in an async fashion. If so + // and this is an import, then the import must be defined asynchronously as + // well. + async_intrinsic_called: bool, + // Code that must be executed before a return, generated during instruction + // lowering. + cleanup: Option, + + // Rust clousures for buffers that must be placed at the front of the + // function. + closures: Source, + + // Various intrinsic properties this function's codegen required, must be + // satisfied in the function header if any are set. + needs_buffer_transaction: bool, + needs_borrow_checker: bool, + needs_memory: bool, + needs_functions: HashMap, +} + +impl FunctionBindgen<'_> { + fn new(gen: &mut Wasmtime, params: Vec) -> FunctionBindgen<'_> { + FunctionBindgen { + gen, + block_storage: Vec::new(), + blocks: Vec::new(), + src: Source::default(), + after_call: false, + caller_memory_available: false, + async_intrinsic_called: false, + tmp: 0, + cleanup: None, + closures: Source::default(), + needs_buffer_transaction: false, + needs_borrow_checker: false, + needs_memory: false, + needs_functions: HashMap::new(), + params, + } + } + + fn memory_src(&mut self) -> String { + if self.gen.in_import { + if !self.after_call { + // Before calls we use `_bc` which is a borrow checker used for + // getting long-lasting borrows into memory. + self.needs_borrow_checker = true; + return format!("_bc"); + } + + if !self.caller_memory_available { + self.needs_memory = true; + self.caller_memory_available = true; + // get separate borrows of `caller_memory` and `_tables` if we + // might need handle tables later. If we don't end up using + // `_tables` that's ok, it'll almost always be optimized away. + if self.gen.all_needed_handles.len() > 0 { + self.push_str( + "let (caller_memory, data) = memory.data_and_store_mut(&mut caller);\n", + ); + self.push_str("let (_, _tables) = get(data);\n"); + } else { + self.push_str("let caller_memory = memory.data_mut(&mut caller);\n"); + } + } + format!("caller_memory") + } else { + self.needs_memory = true; + format!("memory.data_mut(&mut caller)") + } + } + + fn call_intrinsic(&mut self, name: &str, args: String) { + let (method, suffix) = if self.gen.opts.async_.is_none() { + ("call", "") + } else { + self.async_intrinsic_called = true; + ("call_async", ".await") + }; + self.push_str(&format!( + "func_{}.{}(&mut caller, {}){}?;\n", + name, method, args, suffix + )); + self.caller_memory_available = false; // invalidated by call + } + + fn load(&mut self, offset: i32, ty: &str, operands: &[String]) -> String { + let mem = self.memory_src(); + self.gen.needs_raw_mem = true; + let tmp = self.tmp(); + self.push_str(&format!( + "let load{} = {}.load::<{}>({} + {})?;\n", + tmp, mem, ty, operands[0], offset + )); + format!("load{}", tmp) + } + + fn store(&mut self, offset: i32, method: &str, extra: &str, operands: &[String]) { + let mem = self.memory_src(); + self.gen.needs_raw_mem = true; + self.push_str(&format!( + "{}.store({} + {}, wit_bindgen_wasmtime::rt::{}({}){})?;\n", + mem, operands[1], offset, method, operands[0], extra + )); + } +} + +impl RustFunctionGenerator for FunctionBindgen<'_> { + fn push_str(&mut self, s: &str) { + self.src.push_str(s); + } + + fn tmp(&mut self) -> usize { + let ret = self.tmp; + self.tmp += 1; + ret + } + + fn rust_gen(&self) -> &dyn RustGenerator { + self.gen + } + + fn lift_lower(&self) -> LiftLower { + if self.gen.in_import { + LiftLower::LiftArgsLowerResults + } else { + LiftLower::LowerArgsLiftResults + } + } +} + +impl Bindgen for FunctionBindgen<'_> { + type Operand = String; + + fn sizes(&self) -> &SizeAlign { + &self.gen.sizes + } + + fn push_block(&mut self) { + let prev = mem::take(&mut self.src); + self.block_storage.push(prev); + } + + fn finish_block(&mut self, operands: &mut Vec) { + let to_restore = self.block_storage.pop().unwrap(); + let src = mem::replace(&mut self.src, to_restore); + let expr = match operands.len() { + 0 => "()".to_string(), + 1 => operands[0].clone(), + _ => format!("({})", operands.join(", ")), + }; + if src.is_empty() { + self.blocks.push(expr); + } else if operands.is_empty() { + self.blocks.push(format!("{{\n{}}}", &src[..])); + } else { + self.blocks.push(format!("{{\n{}{}\n}}", &src[..], expr)); + } + self.caller_memory_available = false; + } + + fn return_pointer(&mut self, _iface: &Interface, _size: usize, _align: usize) -> String { + unimplemented!() + } + + fn is_list_canonical(&self, iface: &Interface, ty: &Type) -> bool { + iface.all_bits_valid(ty) + } + + fn emit( + &mut self, + iface: &Interface, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ) { + let mut top_as = |cvt: &str| { + let mut s = operands.pop().unwrap(); + s.push_str(" as "); + s.push_str(cvt); + results.push(s); + }; + + let mut try_from = |cvt: &str, operands: &[String], results: &mut Vec| { + self.gen.needs_bad_int = true; + let result = format!("{}::try_from({}).map_err(bad_int)?", cvt, operands[0]); + results.push(result); + }; + + match inst { + Instruction::GetArg { nth } => results.push(self.params[*nth].clone()), + Instruction::I32Const { val } => results.push(format!("{}i32", val)), + Instruction::ConstZero { tys } => { + for ty in tys.iter() { + match ty { + WasmType::I32 => results.push("0i32".to_string()), + WasmType::I64 => results.push("0i64".to_string()), + WasmType::F32 => results.push("0.0f32".to_string()), + WasmType::F64 => results.push("0.0f64".to_string()), + } + } + } + + Instruction::I64FromU64 | Instruction::I64FromS64 => { + let s = operands.pop().unwrap(); + results.push(format!("wit_bindgen_wasmtime::rt::as_i64({})", s)); + } + Instruction::I32FromChar + | Instruction::I32FromU8 + | Instruction::I32FromS8 + | Instruction::I32FromU16 + | Instruction::I32FromS16 + | Instruction::I32FromU32 + | Instruction::I32FromS32 => { + let s = operands.pop().unwrap(); + results.push(format!("wit_bindgen_wasmtime::rt::as_i32({})", s)); + } + + Instruction::F32FromFloat32 + | Instruction::F64FromFloat64 + | Instruction::Float32FromF32 + | Instruction::Float64FromF64 + | Instruction::S32FromI32 + | Instruction::S64FromI64 => { + results.push(operands.pop().unwrap()); + } + + // Downcasts from `i32` into smaller integers are checked to ensure + // that they fit within the valid range. While not strictly + // necessary since we could chop bits off this should be more + // forward-compatible with any future changes. + Instruction::S8FromI32 => try_from("i8", operands, results), + Instruction::U8FromI32 => try_from("u8", operands, results), + Instruction::S16FromI32 => try_from("i16", operands, results), + Instruction::U16FromI32 => try_from("u16", operands, results), + + // Casts of the same bit width simply use `as` since we're just + // reinterpreting the bits already there. + Instruction::U32FromI32 => top_as("u32"), + Instruction::U64FromI64 => top_as("u64"), + + Instruction::CharFromI32 => { + self.gen.needs_char_from_i32 = true; + results.push(format!("char_from_i32({})?", operands[0])); + } + + Instruction::Bitcasts { casts } => { + wit_bindgen_gen_rust::bitcast(casts, operands, results) + } + + Instruction::UnitLower => { + self.push_str(&format!("let () = {};\n", operands[0])); + } + Instruction::UnitLift => { + results.push("()".to_string()); + } + + Instruction::I32FromBool => { + results.push(format!("match {} {{ true => 1, false => 0 }}", operands[0])); + } + Instruction::BoolFromI32 => { + self.gen.needs_invalid_variant = true; + results.push(format!( + "match {} {{ + 0 => false, + 1 => true, + _ => return Err(invalid_variant(\"bool\")), + }}", + operands[0], + )); + } + + Instruction::I32FromOwnedHandle { ty } => { + let name = &iface.resources[*ty].name; + results.push(format!( + "_tables.{}_table.insert({}) as i32", + name.to_snake_case(), + operands[0] + )); + } + Instruction::HandleBorrowedFromI32 { ty } => { + let name = &iface.resources[*ty].name; + results.push(format!( + "_tables.{}_table.get(({}) as u32).ok_or_else(|| {{ + wasmtime::Trap::new(\"invalid handle index\") + }})?", + name.to_snake_case(), + operands[0] + )); + } + Instruction::I32FromBorrowedHandle { ty } => { + let tmp = self.tmp(); + self.push_str(&format!( + " + let obj{tmp} = {op}; + (self.get_state)(caller.as_context_mut().data_mut()).resource_slab{idx}.clone(obj{tmp}.0)?; + let handle{tmp} = (self.get_state)(caller.as_context_mut().data_mut()).index_slab{idx}.insert(obj{tmp}.0); + ", + tmp = tmp, + idx = ty.index(), + op = operands[0], + )); + + results.push(format!("handle{} as i32", tmp,)); + } + Instruction::HandleOwnedFromI32 { ty } => { + let tmp = self.tmp(); + self.push_str(&format!( + "let handle{} = (self.get_state)(caller.as_context_mut().data_mut()).index_slab{}.remove({} as u32)?;\n", + tmp, + ty.index(), + operands[0], + )); + + let name = iface.resources[*ty].name.to_camel_case(); + results.push(format!("{}(handle{})", name, tmp)); + } + + Instruction::RecordLower { ty, record, .. } => { + self.record_lower(iface, *ty, record, &operands[0], results); + } + Instruction::RecordLift { ty, record, .. } => { + self.record_lift(iface, *ty, record, operands, results); + } + + Instruction::TupleLower { tuple, .. } => { + self.tuple_lower(tuple, &operands[0], results); + } + Instruction::TupleLift { .. } => { + self.tuple_lift(operands, results); + } + + Instruction::FlagsLower { flags, .. } => { + let tmp = self.tmp(); + self.push_str(&format!("let flags{} = {};\n", tmp, operands[0])); + for i in 0..flags.repr().count() { + results.push(format!("(flags{}.bits >> {}) as i32", tmp, i * 32)); + } + } + Instruction::FlagsLift { flags, name, .. } => { + self.gen.needs_validate_flags = true; + let repr = RustFlagsRepr::new(flags); + let mut flags = String::from("0"); + for (i, op) in operands.iter().enumerate() { + flags.push_str(&format!("| (({} as {repr}) << {})", op, i * 32)); + } + results.push(format!( + "validate_flags( + {}, + {name}::all().bits(), + \"{name}\", + |bits| {name} {{ bits }} + )?", + flags, + name = name.to_camel_case(), + )); + } + + Instruction::VariantPayloadName => results.push("e".to_string()), + + Instruction::VariantLower { + variant, + results: result_types, + ty, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + self.let_results(result_types.len(), results); + let op0 = &operands[0]; + self.push_str(&format!("match {op0} {{\n")); + let name = self.typename_lower(iface, *ty); + for (case, block) in variant.cases.iter().zip(blocks) { + let case_name = case.name.to_camel_case(); + self.push_str(&format!("{name}::{case_name}")); + if case.ty == Type::Unit { + self.push_str(&format!(" => {{\nlet e = ();\n{block}\n}}\n")); + } else { + self.push_str(&format!("(e) => {block},\n")); + } + } + self.push_str("};\n"); + } + + Instruction::VariantLift { variant, ty, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - variant.cases.len()..) + .collect::>(); + let op0 = &operands[0]; + let mut result = format!("match {op0} {{\n"); + let name = self.typename_lift(iface, *ty); + for (i, (case, block)) in variant.cases.iter().zip(blocks).enumerate() { + let block = if case.ty != Type::Unit { + format!("({block})") + } else { + String::new() + }; + let case = case.name.to_camel_case(); + result.push_str(&format!("{i} => {name}::{case}{block},\n")); + } + result.push_str(&format!("_ => return Err(invalid_variant(\"{name}\")),\n")); + result.push_str("}"); + results.push(result); + self.gen.needs_invalid_variant = true; + } + + Instruction::UnionLower { + union, + results: result_types, + ty, + .. + } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + self.let_results(result_types.len(), results); + let op0 = &operands[0]; + self.push_str(&format!("match {op0} {{\n")); + let name = self.typename_lower(iface, *ty); + for (case_name, block) in self + .gen + .union_case_names(iface, union) + .into_iter() + .zip(blocks) + { + self.push_str(&format!("{name}::{case_name}(e) => {block},\n")); + } + self.push_str("};\n"); + } + + Instruction::UnionLift { union, ty, .. } => { + let blocks = self + .blocks + .drain(self.blocks.len() - union.cases.len()..) + .collect::>(); + let op0 = &operands[0]; + let mut result = format!("match {op0} {{\n"); + let name = self.typename_lift(iface, *ty); + for (i, (case_name, block)) in self + .gen + .union_case_names(iface, union) + .into_iter() + .zip(blocks) + .enumerate() + { + result.push_str(&format!("{i} => {name}::{case_name}({block}),\n")); + } + result.push_str(&format!("_ => return Err(invalid_variant(\"{name}\")),\n")); + result.push_str("}"); + results.push(result); + } + + Instruction::OptionLower { + results: result_types, + .. + } => { + let some = self.blocks.pop().unwrap(); + let none = self.blocks.pop().unwrap(); + self.let_results(result_types.len(), results); + let operand = &operands[0]; + self.push_str(&format!( + "match {operand} {{ + Some(e) => {some}, + None => {{\nlet e = ();\n{none}\n}}, + }};" + )); + } + + Instruction::OptionLift { .. } => { + let some = self.blocks.pop().unwrap(); + let none = self.blocks.pop().unwrap(); + assert_eq!(none, "()"); + let operand = &operands[0]; + results.push(format!( + "match {operand} {{ + 0 => None, + 1 => Some({some}), + _ => return Err(invalid_variant(\"option\")), + }}" + )); + self.gen.needs_invalid_variant = true; + } + + Instruction::ExpectedLower { + results: result_types, + .. + } => { + let err = self.blocks.pop().unwrap(); + let ok = self.blocks.pop().unwrap(); + self.let_results(result_types.len(), results); + let operand = &operands[0]; + self.push_str(&format!( + "match {operand} {{ + Ok(e) => {{ {ok} }}, + Err(e) => {{ {err} }}, + }};" + )); + } + + Instruction::ExpectedLift { .. } => { + let err = self.blocks.pop().unwrap(); + let ok = self.blocks.pop().unwrap(); + let operand = &operands[0]; + results.push(format!( + "match {operand} {{ + 0 => Ok({ok}), + 1 => Err({err}), + _ => return Err(invalid_variant(\"expected\")), + }}" + )); + self.gen.needs_invalid_variant = true; + } + + Instruction::EnumLower { .. } => { + results.push(format!("{} as i32", operands[0])); + } + + Instruction::EnumLift { name, enum_, .. } => { + let op0 = &operands[0]; + let mut result = format!("match {op0} {{\n"); + let name = name.to_camel_case(); + for (i, case) in enum_.cases.iter().enumerate() { + let case = case.name.to_camel_case(); + result.push_str(&format!("{i} => {name}::{case},\n")); + } + result.push_str(&format!("_ => return Err(invalid_variant(\"{name}\")),\n")); + result.push_str("}"); + results.push(result); + self.gen.needs_invalid_variant = true; + } + + Instruction::ListCanonLower { element, realloc } => { + // Lowering only happens when we're passing lists into wasm, + // which forces us to always allocate, so this should always be + // `Some`. + let realloc = realloc.unwrap(); + self.needs_functions + .insert(realloc.to_string(), NeededFunction::Realloc); + let (size, align) = (self.gen.sizes.size(element), self.gen.sizes.align(element)); + + // Store the operand into a temporary... + let tmp = self.tmp(); + let val = format!("vec{}", tmp); + self.push_str(&format!("let {} = {};\n", val, operands[0])); + + // ... and then realloc space for the result in the guest module + let ptr = format!("ptr{}", tmp); + self.push_str(&format!("let {} = ", ptr)); + self.call_intrinsic( + realloc, + format!("(0, 0, {}, ({}.len() as i32) * {})", align, val, size), + ); + + // ... and then copy over the result. + let mem = self.memory_src(); + self.push_str(&format!("{}.store_many({}, &{})?;\n", mem, ptr, val)); + self.gen.needs_raw_mem = true; + self.needs_memory = true; + results.push(ptr); + results.push(format!("{}.len() as i32", val)); + } + + Instruction::ListCanonLift { element, free, .. } => match free { + Some(free) => { + self.needs_memory = true; + self.gen.needs_copy_slice = true; + self.needs_functions + .insert(free.to_string(), NeededFunction::Free); + let (align, el_size) = + (self.sizes().align(element), self.sizes().size(element)); + let tmp = self.tmp(); + self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); + self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); + self.push_str(&format!( + " + let data{tmp} = copy_slice( + &mut caller, + memory, + ptr{tmp}, len{tmp}, {} + )?; + ", + align, + tmp = tmp, + )); + self.call_intrinsic( + free, + // we use normal multiplication here as copy_slice has + // already verified that multiplied size fits i32 + format!("(ptr{tmp}, len{tmp} * {}, {})", el_size, align, tmp = tmp), + ); + results.push(format!("data{}", tmp)); + } + None => { + self.needs_borrow_checker = true; + let tmp = self.tmp(); + self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); + self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); + let slice = format!("_bc.slice(ptr{0}, len{0})?", tmp); + results.push(slice); + } + }, + + Instruction::StringLower { realloc } => { + // see above for this unwrap + let realloc = realloc.unwrap(); + self.needs_functions + .insert(realloc.to_string(), NeededFunction::Realloc); + + // Store the operand into a temporary... + let tmp = self.tmp(); + let val = format!("vec{}", tmp); + self.push_str(&format!("let {} = {};\n", val, operands[0])); + + // ... and then realloc space for the result in the guest module + let ptr = format!("ptr{}", tmp); + self.push_str(&format!("let {} = ", ptr)); + self.call_intrinsic(realloc, format!("(0, 0, 1, {}.len() as i32)", val)); + + // ... and then copy over the result. + let mem = self.memory_src(); + self.push_str(&format!( + "{}.store_many({}, {}.as_bytes())?;\n", + mem, ptr, val + )); + self.gen.needs_raw_mem = true; + self.needs_memory = true; + results.push(ptr); + results.push(format!("{}.len() as i32", val)); + } + + Instruction::StringLift { free } => match free { + Some(free) => { + self.needs_memory = true; + self.gen.needs_copy_slice = true; + self.needs_functions + .insert(free.to_string(), NeededFunction::Free); + let tmp = self.tmp(); + self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); + self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); + self.push_str(&format!( + " + let data{tmp} = copy_slice( + &mut caller, + memory, + ptr{tmp}, len{tmp}, 1, + )?; + ", + tmp = tmp, + )); + self.call_intrinsic( + free, + // we use normal multiplication here as copy_slice has + // already verified that multiplied size fits i32 + format!("(ptr{tmp}, len{tmp}, 1)", tmp = tmp), + ); + results.push(format!( + "String::from_utf8(data{}) + .map_err(|_| wasmtime::Trap::new(\"invalid utf-8\"))?", + tmp, + )); + } + None => { + self.needs_borrow_checker = true; + let tmp = self.tmp(); + self.push_str(&format!("let ptr{} = {};\n", tmp, operands[0])); + self.push_str(&format!("let len{} = {};\n", tmp, operands[1])); + let slice = format!("_bc.slice_str(ptr{0}, len{0})?", tmp); + results.push(slice); + } + }, + + Instruction::ListLower { element, realloc } => { + let realloc = realloc.unwrap(); + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let vec = format!("vec{}", tmp); + let result = format!("result{}", tmp); + let len = format!("len{}", tmp); + self.needs_functions + .insert(realloc.to_string(), NeededFunction::Realloc); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + + // first store our vec-to-lower in a temporary since we'll + // reference it multiple times. + self.push_str(&format!("let {} = {};\n", vec, operands[0])); + self.push_str(&format!("let {} = {}.len() as i32;\n", len, vec)); + + // ... then realloc space for the result in the guest module + self.push_str(&format!("let {} = ", result)); + self.call_intrinsic(realloc, format!("(0, 0, {}, {} * {})", align, len, size)); + + // ... then consume the vector and use the block to lower the + // result. + self.push_str(&format!( + "for (i, e) in {}.into_iter().enumerate() {{\n", + vec + )); + self.push_str(&format!("let base = {} + (i as i32) * {};\n", result, size)); + self.push_str(&body); + self.push_str("}"); + + results.push(result); + results.push(len); + } + + Instruction::ListLift { element, free, .. } => { + let body = self.blocks.pop().unwrap(); + let tmp = self.tmp(); + let size = self.gen.sizes.size(element); + let align = self.gen.sizes.align(element); + let len = format!("len{}", tmp); + self.push_str(&format!("let {} = {};\n", len, operands[1])); + let base = format!("base{}", tmp); + self.push_str(&format!("let {} = {};\n", base, operands[0])); + let result = format!("result{}", tmp); + self.push_str(&format!( + "let mut {} = Vec::with_capacity({} as usize);\n", + result, len, + )); + + self.push_str("for i in 0.."); + self.push_str(&len); + self.push_str(" {\n"); + self.push_str("let base = "); + self.push_str(&base); + self.push_str(" + i *"); + self.push_str(&size.to_string()); + self.push_str(";\n"); + self.push_str(&result); + self.push_str(".push("); + self.push_str(&body); + self.push_str(");\n"); + self.push_str("}\n"); + results.push(result); + + if let Some(free) = free { + self.call_intrinsic(free, format!("({}, {} * {}, {})", base, len, size, align)); + self.needs_functions + .insert(free.to_string(), NeededFunction::Free); + } + } + + Instruction::IterElem { .. } => { + self.caller_memory_available = false; // invalidated by for loop + results.push("e".to_string()) + } + + Instruction::IterBasePointer => results.push("base".to_string()), + + Instruction::CallWasm { + iface: _, + name, + sig, + } => { + if sig.results.len() > 0 { + let tmp = self.tmp(); + self.push_str("let ("); + for i in 0..sig.results.len() { + let arg = format!("result{}_{}", tmp, i); + self.push_str(&arg); + self.push_str(","); + results.push(arg); + } + self.push_str(") = "); + } + self.push_str("self."); + self.push_str(&to_rust_ident(name)); + if self.gen.opts.async_.includes(name) { + self.push_str(".call_async("); + } else { + self.push_str(".call("); + } + self.push_str("&mut caller, ("); + for operand in operands { + self.push_str(operand); + self.push_str(", "); + } + self.push_str("))"); + if self.gen.opts.async_.includes(name) { + self.push_str(".await"); + } + self.push_str("?;\n"); + self.after_call = true; + self.caller_memory_available = false; // invalidated by call + } + + Instruction::CallWasmAsyncImport { .. } => unimplemented!(), + Instruction::CallWasmAsyncExport { .. } => unimplemented!(), + + Instruction::CallInterface { module: _, func } => { + for (i, operand) in operands.iter().enumerate() { + self.push_str(&format!("let param{} = {};\n", i, operand)); + } + if self.gen.opts.tracing && func.params.len() > 0 { + self.push_str("wit_bindgen_wasmtime::tracing::event!(\n"); + self.push_str("wit_bindgen_wasmtime::tracing::Level::TRACE,\n"); + for (i, (name, _ty)) in func.params.iter().enumerate() { + self.push_str(&format!( + "{} = wit_bindgen_wasmtime::tracing::field::debug(¶m{}),\n", + to_rust_ident(name), + i + )); + } + self.push_str(");\n"); + } + + let mut call = format!("host.{}(", func.name.to_snake_case()); + for i in 0..operands.len() { + call.push_str(&format!("param{}, ", i)); + } + call.push_str(")"); + if self.gen.opts.async_.includes(&func.name) { + call.push_str(".await"); + } + + self.push_str("let result = "); + results.push("result".to_string()); + match self.gen.classify_fn_ret(iface, func) { + FunctionRet::Normal => self.push_str(&call), + // Unwrap the result, translating errors to unconditional + // traps + FunctionRet::CustomToTrap => { + self.push_str("match "); + self.push_str(&call); + self.push_str("{\n"); + self.push_str("Ok(val) => val,\n"); + self.push_str("Err(e) => return Err(host.error_to_trap(e)),\n"); + self.push_str("}"); + } + // Keep the `Result` as a `Result`, but convert the error + // to either the expected destination value or a trap, + // propagating a trap outwards. + FunctionRet::CustomToError { err, .. } => { + self.push_str("match "); + self.push_str(&call); + self.push_str("{\n"); + self.push_str("Ok(val) => Ok(val),\n"); + self.push_str(&format!( + "Err(e) => Err(host.error_to_{}(e)?),\n", + err.to_snake_case() + )); + self.push_str("}"); + } + } + self.push_str(";\n"); + self.after_call = true; + match &func.result { + Type::Unit => {} + _ if self.gen.opts.tracing => { + self.push_str("wit_bindgen_wasmtime::tracing::event!(\n"); + self.push_str("wit_bindgen_wasmtime::tracing::Level::TRACE,\n"); + self.push_str(&format!( + "{} = wit_bindgen_wasmtime::tracing::field::debug(&{0}),\n", + results[0], + )); + self.push_str(");\n"); + } + _ => {} + } + } + + Instruction::Return { amt, .. } => { + let result = match amt { + 0 => format!("Ok(())\n"), + 1 => format!("Ok({})\n", operands[0]), + _ => format!("Ok(({}))\n", operands.join(", ")), + }; + match self.cleanup.take() { + Some(cleanup) => { + self.push_str("let ret = "); + self.push_str(&result); + self.push_str(";\n"); + self.push_str(&cleanup); + self.push_str("ret"); + } + None => self.push_str(&result), + } + } + + Instruction::ReturnAsyncExport { .. } => unimplemented!(), + Instruction::ReturnAsyncImport { .. } => unimplemented!(), + + Instruction::I32Load { offset } => results.push(self.load(*offset, "i32", operands)), + Instruction::I32Load8U { offset } => { + results.push(format!("i32::from({})", self.load(*offset, "u8", operands))); + } + Instruction::I32Load8S { offset } => { + results.push(format!("i32::from({})", self.load(*offset, "i8", operands))); + } + Instruction::I32Load16U { offset } => { + results.push(format!( + "i32::from({})", + self.load(*offset, "u16", operands) + )); + } + Instruction::I32Load16S { offset } => { + results.push(format!( + "i32::from({})", + self.load(*offset, "i16", operands) + )); + } + Instruction::I64Load { offset } => results.push(self.load(*offset, "i64", operands)), + Instruction::F32Load { offset } => results.push(self.load(*offset, "f32", operands)), + Instruction::F64Load { offset } => results.push(self.load(*offset, "f64", operands)), + + Instruction::I32Store { offset } => self.store(*offset, "as_i32", "", operands), + Instruction::I64Store { offset } => self.store(*offset, "as_i64", "", operands), + Instruction::F32Store { offset } => self.store(*offset, "as_f32", "", operands), + Instruction::F64Store { offset } => self.store(*offset, "as_f64", "", operands), + Instruction::I32Store8 { offset } => self.store(*offset, "as_i32", " as u8", operands), + Instruction::I32Store16 { offset } => { + self.store(*offset, "as_i32", " as u16", operands) + } + + Instruction::Malloc { + realloc, + size, + align, + } => { + self.needs_functions + .insert(realloc.to_string(), NeededFunction::Realloc); + let tmp = self.tmp(); + let ptr = format!("ptr{}", tmp); + self.push_str(&format!("let {} = ", ptr)); + self.call_intrinsic(realloc, format!("(0, 0, {}, {})", align, size)); + results.push(ptr); + } + + Instruction::Free { .. } => unimplemented!(), + } + } +} + +impl NeededFunction { + fn cvt(&self) -> &'static str { + match self { + NeededFunction::Realloc => "(i32, i32, i32, i32), i32", + NeededFunction::Free => "(i32, i32, i32), ()", + } + } + + fn ty(&self) -> String { + format!("wasmtime::TypedFunc<{}>", self.cvt()) + } +} + +fn sorted_iter(map: &HashMap) -> impl Iterator { + let mut list = map.into_iter().collect::>(); + list.sort_by_key(|p| p.0); + list.into_iter() +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/tests/codegen.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/tests/codegen.rs new file mode 100644 index 0000000..573be4b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/tests/codegen.rs @@ -0,0 +1,108 @@ +#![allow(dead_code, type_alias_bounds)] + +fn main() { + println!("compiled successfully!") +} + +#[rustfmt::skip] +mod exports { + test_helpers::codegen_wasmtime_export!( + "*.wit" + + // TODO: implement async support + "!async-functions.wit" + + // If you want to exclude a specific test you can include it here with + // gitignore glob syntax: + // + // "!wasm.wit" + // "!host.wit" + // + // + // Similarly you can also just remove the `*.wit` glob and list tests + // individually if you're debugging. + ); +} + +mod imports { + test_helpers::codegen_wasmtime_import!( + "*.wit" + + // TODO: implement async support + "!async-functions.wit" + + // TODO: these use push/pull buffer which isn't implemented in the test + // generator just yet + "!wasi-next.wit" + "!host.wit" + ); +} + +mod async_tests { + mod not_async { + wit_bindgen_wasmtime::export!({ + src["x"]: "foo: func()", + async: ["bar"], + }); + + struct Me; + + impl x::X for Me { + fn foo(&mut self) {} + } + } + mod one_async { + wit_bindgen_wasmtime::export!({ + src["x"]: " + foo: func() -> list + bar: func() + ", + async: ["bar"], + }); + + struct Me; + + #[wit_bindgen_wasmtime::async_trait] + impl x::X for Me { + fn foo(&mut self) -> Vec { + Vec::new() + } + + async fn bar(&mut self) {} + } + } + mod one_async_export { + wit_bindgen_wasmtime::import!({ + src["x"]: " + foo: func(x: list) + bar: func() + ", + async: ["bar"], + }); + } + mod resource_with_none_async { + wit_bindgen_wasmtime::export!({ + src["x"]: " + resource y { + z: func() -> string + } + ", + async: [], + }); + } +} + +mod custom_errors { + wit_bindgen_wasmtime::export!({ + src["x"]: " + foo: func() + bar: func() -> expected + enum errno { + bad1, + bad2, + } + baz: func() -> expected + ", + custom_error: true, + }); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/tests/runtime.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/tests/runtime.rs new file mode 100644 index 0000000..2387ccb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/gen-wasmtime/tests/runtime.rs @@ -0,0 +1,110 @@ +use anyhow::{Context as _, Result}; +use wasmtime::{Config, Engine, Instance, Linker, Module, Store}; + +test_helpers::runtime_tests_wasmtime!(); + +fn default_config() -> Result { + // Create an engine with caching enabled to assist with iteration in this + // project. + let mut config = Config::new(); + config.cache_config_load_default()?; + config.wasm_backtrace_details(wasmtime::WasmBacktraceDetails::Enable); + Ok(config) +} + +fn default_wasi() -> wasmtime_wasi::WasiCtx { + wasmtime_wasi::sync::WasiCtxBuilder::new() + .inherit_stdio() + .build() +} + +struct Context { + wasi: wasmtime_wasi::WasiCtx, + imports: I, + exports: E, +} + +fn instantiate( + wasm: &str, + add_imports: impl FnOnce(&mut Linker>) -> Result<()>, + mk_exports: impl FnOnce( + &mut Store>, + &Module, + &mut Linker>, + ) -> Result<(T, Instance)>, +) -> Result<(T, Store>)> { + let engine = Engine::new(&default_config()?)?; + let module = Module::from_file(&engine, wasm)?; + + let mut linker = Linker::new(&engine); + add_imports(&mut linker)?; + wasmtime_wasi::add_to_linker(&mut linker, |cx| &mut cx.wasi)?; + + let mut store = Store::new( + &engine, + Context { + wasi: default_wasi(), + imports: I::default(), + exports: E::default(), + }, + ); + let (exports, _instance) = mk_exports(&mut store, &module, &mut linker)?; + Ok((exports, store)) +} + +// TODO: This function needs to be updated to use the component model once it's ready. See +// https://github.com/bytecodealliance/wit-bindgen/issues/259 for details. +// +// Also, rename the ignore_host.rs files under the tests/runtime/smw_{functions|lists|strings} to host.rs and +// remove the leading underscore from this function's name to re-enable the Spidermonkey tests. +fn _instantiate_smw( + wasm: &str, + add_imports: impl FnOnce(&mut Linker>) -> Result<()>, + mk_exports: impl FnOnce( + &mut Store>, + &Module, + &mut Linker>, + ) -> Result<(T, Instance)>, +) -> Result<(T, Store>)> { + let mut config = default_config()?; + config.wasm_multi_memory(true); + let engine = Engine::new(&config)?; + + println!("reading wasms..."); + let wasm = std::fs::read(wasm).context(format!("failed to read {}", wasm))?; + let smw = std::fs::read("../gen-spidermonkey/spidermonkey-wasm/spidermonkey.wasm") + .context("failed to read `spidermonkey.wasm`")?; + println!("compiling input wasm..."); + let module = Module::new(&engine, &wasm)?; + println!("compiling spidermonkey.wasm..."); + let smw = Module::new(&engine, &smw)?; + + let mut linker = Linker::new(&engine); + add_imports(&mut linker)?; + wasmtime_wasi::add_to_linker(&mut linker, |cx| &mut cx.wasi)?; + + let mut store = Store::new( + &engine, + Context { + wasi: default_wasi(), + imports: I::default(), + exports: E::default(), + }, + ); + + println!("instantiating spidermonkey.wasm..."); + let _smw_instance = linker + .instantiate(&mut store, &smw) + .context("failed to instantiate `spidermonkey.wasm`")?; + // TODO: replace this with a component model equivalent: + // linker.define_name("spidermonkey", smw_instance)?; + + println!("instantiating input wasm..."); + let (exports, instance) = mk_exports(&mut store, &module, &mut linker)?; + + println!("running wizer.initialize"); + let init = instance.get_typed_func::<(), (), _>(&mut store, "wizer.initialize")?; + init.call(&mut store, ()) + .context("failed to call wizer.initialize")?; + Ok((exports, store)) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/.gitignore b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/.gitignore new file mode 100644 index 0000000..a9d37c5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/.gitignore @@ -0,0 +1,2 @@ +target +Cargo.lock diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/Cargo.toml new file mode 100644 index 0000000..61e87dc --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "wit-parser" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[dependencies] +id-arena = "2" +anyhow = "1.0" +pulldown-cmark = { version = "0.8", default-features = false } +wast = { version = "33", default-features = false, optional = true } +unicode-xid = "0.2.2" +unicode-normalization = "0.1.19" + +[dev-dependencies] +rayon = "1" +serde_json = "1" +serde = { version = "1", features = ['derive'] } + +[[test]] +name = "all" +harness = false diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/abi.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/abi.rs new file mode 100644 index 0000000..ae2811b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/abi.rs @@ -0,0 +1,2251 @@ +use crate::sizealign::align_to; +use crate::{ + Enum, Expected, Flags, FlagsRepr, Function, Int, Interface, Record, ResourceId, Tuple, Type, + TypeDefKind, TypeId, Union, Variant, +}; +use std::mem; + +/// A raw WebAssembly signature with params and results. +#[derive(Clone, Debug, Hash, Eq, PartialEq, PartialOrd, Ord)] +pub struct WasmSignature { + /// The WebAssembly parameters of this function. + pub params: Vec, + + /// The WebAssembly results of this function. + pub results: Vec, + + /// Whether or not this signature is passing all of its parameters + /// indirectly through a pointer within `params`. + /// + /// Note that `params` still reflects the true wasm paramters of this + /// function, this is auxiliary information for code generators if + /// necessary. + pub indirect_params: bool, + + /// Whether or not this signature is using a return pointer to store the + /// result of the function, which is reflected either in `params` or + /// `results` depending on the context this function is used (e.g. an import + /// or an export). + pub retptr: bool, +} + +/// Enumerates wasm types used by interface types when lowering/lifting. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub enum WasmType { + I32, + I64, + F32, + F64, + // NOTE: we don't lower interface types to any other Wasm type, + // e.g. externref, so we don't need to define them here. +} + +fn join(a: WasmType, b: WasmType) -> WasmType { + use WasmType::*; + + match (a, b) { + (I32, I32) | (I64, I64) | (F32, F32) | (F64, F64) => a, + + (I32, F32) | (F32, I32) => I32, + + (_, I64 | F64) | (I64 | F64, _) => I64, + } +} + +impl From for WasmType { + fn from(i: Int) -> WasmType { + match i { + Int::U8 | Int::U16 | Int::U32 => WasmType::I32, + Int::U64 => WasmType::I64, + } + } +} + +// Helper macro for defining instructions without having to have tons of +// exhaustive `match` statements to update +macro_rules! def_instruction { + ( + $( #[$enum_attr:meta] )* + pub enum $name:ident<'a> { + $( + $( #[$attr:meta] )* + $variant:ident $( { + $($field:ident : $field_ty:ty $(,)* )* + } )? + : + [$num_popped:expr] => [$num_pushed:expr], + )* + } + ) => { + $( #[$enum_attr] )* + pub enum $name<'a> { + $( + $( #[$attr] )* + $variant $( { + $( + $field : $field_ty, + )* + } )? , + )* + } + + impl $name<'_> { + /// How many operands does this instruction pop from the stack? + #[allow(unused_variables)] + pub fn operands_len(&self) -> usize { + match self { + $( + Self::$variant $( { + $( + $field, + )* + } )? => $num_popped, + )* + } + } + + /// How many results does this instruction push onto the stack? + #[allow(unused_variables)] + pub fn results_len(&self) -> usize { + match self { + $( + Self::$variant $( { + $( + $field, + )* + } )? => $num_pushed, + )* + } + } + } + }; +} + +def_instruction! { + #[derive(Debug)] + pub enum Instruction<'a> { + /// Acquires the specified parameter and places it on the stack. + /// Depending on the context this may refer to wasm parameters or + /// interface types parameters. + GetArg { nth: usize } : [0] => [1], + + // Integer const/manipulation instructions + + /// Pushes the constant `val` onto the stack. + I32Const { val: i32 } : [0] => [1], + /// Casts the top N items on the stack using the `Bitcast` enum + /// provided. Consumes the same number of operands that this produces. + Bitcasts { casts: &'a [Bitcast] } : [casts.len()] => [casts.len()], + /// Pushes a number of constant zeros for each wasm type on the stack. + ConstZero { tys: &'a [WasmType] } : [0] => [tys.len()], + + // Memory load/store instructions + + /// Pops an `i32` from the stack and loads a little-endian `i32` from + /// it, using the specified constant offset. + I32Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i8` from + /// it, using the specified constant offset. The value loaded is the + /// zero-extended to 32-bits + I32Load8U { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i8` from + /// it, using the specified constant offset. The value loaded is the + /// sign-extended to 32-bits + I32Load8S { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i16` from + /// it, using the specified constant offset. The value loaded is the + /// zero-extended to 32-bits + I32Load16U { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i16` from + /// it, using the specified constant offset. The value loaded is the + /// sign-extended to 32-bits + I32Load16S { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `i64` from + /// it, using the specified constant offset. + I64Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `f32` from + /// it, using the specified constant offset. + F32Load { offset: i32 } : [1] => [1], + /// Pops an `i32` from the stack and loads a little-endian `f64` from + /// it, using the specified constant offset. + F64Load { offset: i32 } : [1] => [1], + + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + I32Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the low 8 bits of the value in little-endian at the pointer + /// specified plus the constant `offset`. + I32Store8 { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i32` value. + /// Stores the low 16 bits of the value in little-endian at the pointer + /// specified plus the constant `offset`. + I32Store16 { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `i64` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + I64Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `f32` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + F32Store { offset: i32 } : [2] => [0], + /// Pops an `i32` address from the stack and then an `f64` value. + /// Stores the value in little-endian at the pointer specified plus the + /// constant `offset`. + F64Store { offset: i32 } : [2] => [0], + + // Scalar lifting/lowering + + /// Converts an interface type `char` value to a 32-bit integer + /// representing the unicode scalar value. + I32FromChar : [1] => [1], + /// Converts an interface type `u64` value to a wasm `i64`. + I64FromU64 : [1] => [1], + /// Converts an interface type `s64` value to a wasm `i64`. + I64FromS64 : [1] => [1], + /// Converts an interface type `u32` value to a wasm `i32`. + I32FromU32 : [1] => [1], + /// Converts an interface type `s32` value to a wasm `i32`. + I32FromS32 : [1] => [1], + /// Converts an interface type `u16` value to a wasm `i32`. + I32FromU16 : [1] => [1], + /// Converts an interface type `s16` value to a wasm `i32`. + I32FromS16 : [1] => [1], + /// Converts an interface type `u8` value to a wasm `i32`. + I32FromU8 : [1] => [1], + /// Converts an interface type `s8` value to a wasm `i32`. + I32FromS8 : [1] => [1], + /// Conversion an interface type `f32` value to a wasm `f32`. + /// + /// This may be a noop for some implementations, but it's here in case the + /// native language representation of `f32` is different than the wasm + /// representation of `f32`. + F32FromFloat32 : [1] => [1], + /// Conversion an interface type `f64` value to a wasm `f64`. + /// + /// This may be a noop for some implementations, but it's here in case the + /// native language representation of `f64` is different than the wasm + /// representation of `f64`. + F64FromFloat64 : [1] => [1], + + /// Converts a native wasm `i32` to an interface type `s8`. + /// + /// This will truncate the upper bits of the `i32`. + S8FromI32 : [1] => [1], + /// Converts a native wasm `i32` to an interface type `u8`. + /// + /// This will truncate the upper bits of the `i32`. + U8FromI32 : [1] => [1], + /// Converts a native wasm `i32` to an interface type `s16`. + /// + /// This will truncate the upper bits of the `i32`. + S16FromI32 : [1] => [1], + /// Converts a native wasm `i32` to an interface type `u16`. + /// + /// This will truncate the upper bits of the `i32`. + U16FromI32 : [1] => [1], + /// Converts a native wasm `i32` to an interface type `s32`. + S32FromI32 : [1] => [1], + /// Converts a native wasm `i32` to an interface type `u32`. + U32FromI32 : [1] => [1], + /// Converts a native wasm `i64` to an interface type `s64`. + S64FromI64 : [1] => [1], + /// Converts a native wasm `i64` to an interface type `u64`. + U64FromI64 : [1] => [1], + /// Converts a native wasm `i32` to an interface type `char`. + /// + /// It's safe to assume that the `i32` is indeed a valid unicode code point. + CharFromI32 : [1] => [1], + /// Converts a native wasm `f32` to an interface type `f32`. + Float32FromF32 : [1] => [1], + /// Converts a native wasm `f64` to an interface type `f64`. + Float64FromF64 : [1] => [1], + + /// Creates a `bool` from an `i32` input, trapping if the `i32` isn't + /// zero or one. + BoolFromI32 : [1] => [1], + /// Creates an `i32` from a `bool` input, must return 0 or 1. + I32FromBool : [1] => [1], + + /// Creates a "unit" value from nothing. + UnitLift : [0] => [1], + /// Consumes a "unit" value and returns nothing. + UnitLower : [1] => [0], + + // Handles + + /// Converts a "borrowed" handle into a wasm `i32` value. + /// + /// > **Note**: this documentation is outdated and does not reflect the + /// > current implementation of the canonical ABI. This needs to be + /// > updated. + /// + /// A "borrowed" handle in this case means one where ownership is not + /// being relinquished. This is only used for lowering interface types + /// parameters. + /// + /// Situations that this is used are: + /// + /// * A wasm exported function receives, as a parameter, handles defined + /// by the wasm module itself. This is effectively proof of ownership + /// by an external caller (be it host or wasm module) and the + /// ownership of the handle still lies with the caller. The wasm + /// module is only receiving a reference to the resource. + /// + /// * A wasm module is calling an import with a handle defined by the + /// import's module. Sort of the converse of the previous case this + /// means that the wasm module is handing out a reference to a + /// resource that it owns. The type in the wasm module, for example, + /// needs to reflect this. + /// + /// This instruction is not used for return values in either + /// export/import positions. + I32FromBorrowedHandle { ty: ResourceId } : [1] => [1], + + /// Converts an "owned" handle into a wasm `i32` value. + /// + /// > **Note**: this documentation is outdated and does not reflect the + /// > current implementation of the canonical ABI. This needs to be + /// > updated. + /// + /// This conversion is used for handle values which are crossing a + /// module boundary for perhaps the first time. Some example cases of + /// when this conversion is used are: + /// + /// * When a host defines a function to be imported, returned handles + /// use this instruction. Handles being returned to wasm a granting a + /// capability, which means that this new capability is typically + /// wrapped up in a new integer descriptor. + /// + /// * When a wasm module calls an imported function with a type defined + /// by itself, then it's granting a capability to the callee. This + /// means that the wasm module's type is being granted for the first + /// time, possibly, so it needs to be an owned value that's consumed. + /// Note that this doesn't actually happen with `*.witx` today due to + /// the lack of handle type imports. + /// + /// * When a wasm module export returns a handle defined within the + /// module, then it's similar to calling an imported function with + /// that handle. The capability is being granted to the caller of the + /// export, so the owned value is wrapped up in an `i32`. + /// + /// * When a host is calling a wasm module with a capability defined by + /// the host, its' similar to the host import returning a capability. + /// This would be granting the wasm module with the capability so an + /// owned version with a fresh handle is passed to the wasm module. + /// Note that this doesn't happen today with `*.witx` due to the lack + /// of handle type imports. + /// + /// Basically this instruction is used for handle->wasm conversions + /// depending on the calling context and where the handle type in + /// question was defined. + I32FromOwnedHandle { ty: ResourceId } : [1] => [1], + + /// Converts a native wasm `i32` into an owned handle value. + /// + /// > **Note**: this documentation is outdated and does not reflect the + /// > current implementation of the canonical ABI. This needs to be + /// > updated. + /// + /// This is the converse of `I32FromOwnedHandle` and is used in similar + /// situations: + /// + /// * A host definition of an import receives a handle defined in the + /// module itself. + /// * A wasm module calling an import receives a handle defined by the + /// import. + /// * A wasm module's export receives a handle defined by an external + /// module. + /// * A host calling a wasm export receives a handle defined in the + /// module. + /// + /// Note that like `I32FromOwnedHandle` the first and third bullets + /// above don't happen today because witx can't express type imports + /// just yet. + HandleOwnedFromI32 { ty: ResourceId } : [1] => [1], + + /// Converts a native wasm `i32` into a borrowedhandle value. + /// + /// > **Note**: this documentation is outdated and does not reflect the + /// > current implementation of the canonical ABI. This needs to be + /// > updated. + /// + /// This is the converse of `I32FromBorrowedHandle` and is used in similar + /// situations: + /// + /// * An exported wasm function receives, as a parameter, a handle that + /// is defined by the wasm module. + /// * An host-defined imported function is receiving a handle, as a + /// parameter, that is defined by the host itself. + HandleBorrowedFromI32 { ty: ResourceId } : [1] => [1], + + // lists + + /// Lowers a list where the element's layout in the native language is + /// expected to match the canonical ABI definition of interface types. + /// + /// Pops a list value from the stack and pushes the pointer/length onto + /// the stack. If `realloc` is set to `Some` then this is expected to + /// *consume* the list which means that the data needs to be copied. An + /// allocation/copy is expected when: + /// + /// * A host is calling a wasm export with a list (it needs to copy the + /// list in to the callee's module, allocating space with `realloc`) + /// * A wasm export is returning a list (it's expected to use `realloc` + /// to give ownership of the list to the caller. + /// * A host is returning a list in a import definition, meaning that + /// space needs to be allocated in the caller with `realloc`). + /// + /// A copy does not happen (e.g. `realloc` is `None`) when: + /// + /// * A wasm module calls an import with the list. In this situation + /// it's expected the caller will know how to access this module's + /// memory (e.g. the host has raw access or wasm-to-wasm communication + /// would copy the list). + /// + /// If `realloc` is `Some` then the adapter is not responsible for + /// cleaning up this list because the other end is receiving the + /// allocation. If `realloc` is `None` then the adapter is responsible + /// for cleaning up any temporary allocation it created, if any. + ListCanonLower { + element: &'a Type, + realloc: Option<&'a str>, + } : [1] => [2], + + /// Same as `ListCanonLower`, but used for strings + StringLower { + realloc: Option<&'a str>, + } : [1] => [2], + + /// Lowers a list where the element's layout in the native language is + /// not expected to match the canonical ABI definition of interface + /// types. + /// + /// Pops a list value from the stack and pushes the pointer/length onto + /// the stack. This operation also pops a block from the block stack + /// which is used as the iteration body of writing each element of the + /// list consumed. + /// + /// The `realloc` field here behaves the same way as `ListCanonLower`. + /// It's only set to `None` when a wasm module calls a declared import. + /// Otherwise lowering in other contexts requires allocating memory for + /// the receiver to own. + ListLower { + element: &'a Type, + realloc: Option<&'a str>, + } : [1] => [2], + + /// Lifts a list which has a canonical representation into an interface + /// types value. + /// + /// The term "canonical" representation here means that the + /// representation of the interface types value in the native language + /// exactly matches the canonical ABI definition of the type. + /// + /// This will consume two `i32` values from the stack, a pointer and a + /// length, and then produces an interface value list. If the `free` + /// field is set to `Some` then the pointer/length should be considered + /// an owned allocation and need to be deallocated by the receiver. If + /// it is set to `None` then a view is provided but it does not need to + /// be deallocated. + /// + /// The `free` field is set to `Some` in similar situations as described + /// by `ListCanonLower`. If `free` is `Some` then the memory must be + /// deallocated after the lifted list is done being consumed. If it is + /// `None` then the receiver of the lifted list does not own the memory + /// and must leave the memory as-is. + ListCanonLift { + element: &'a Type, + free: Option<&'a str>, + ty: TypeId, + } : [2] => [1], + + /// Same as `ListCanonLift`, but used for strings + StringLift { + free: Option<&'a str>, + } : [2] => [1], + + /// Lifts a list which into an interface types value. + /// + /// This will consume two `i32` values from the stack, a pointer and a + /// length, and then produces an interface value list. Note that the + /// pointer/length popped are **owned** and need to be deallocated with + /// the wasm `free` function when the list is no longer needed. + /// + /// This will also pop a block from the block stack which is how to + /// read each individual element from the list. + ListLift { + element: &'a Type, + free: Option<&'a str>, + ty: TypeId, + } : [2] => [1], + + /// Pushes an operand onto the stack representing the list item from + /// each iteration of the list. + /// + /// This is only used inside of blocks related to lowering lists. + IterElem { element: &'a Type } : [0] => [1], + + /// Pushes an operand onto the stack representing the base pointer of + /// the next element in a list. + /// + /// This is used for both lifting and lowering lists. + IterBasePointer : [0] => [1], + + // records + + /// Pops a record value off the stack, decomposes the record to all of + /// its fields, and then pushes the fields onto the stack. + RecordLower { + record: &'a Record, + name: &'a str, + ty: TypeId, + } : [1] => [record.fields.len()], + + /// Pops all fields for a record off the stack and then composes them + /// into a record. + RecordLift { + record: &'a Record, + name: &'a str, + ty: TypeId, + } : [record.fields.len()] => [1], + + /// Pops a tuple value off the stack, decomposes the tuple to all of + /// its fields, and then pushes the fields onto the stack. + TupleLower { + tuple: &'a Tuple, + ty: TypeId, + } : [1] => [tuple.types.len()], + + /// Pops all fields for a tuple off the stack and then composes them + /// into a tuple. + TupleLift { + tuple: &'a Tuple, + ty: TypeId, + } : [tuple.types.len()] => [1], + + /// Converts a language-specific record-of-bools to a list of `i32`. + FlagsLower { + flags: &'a Flags, + name: &'a str, + ty: TypeId, + } : [1] => [flags.repr().count()], + /// Converts a list of native wasm `i32` to a language-specific + /// record-of-bools. + FlagsLift { + flags: &'a Flags, + name: &'a str, + ty: TypeId, + } : [flags.repr().count()] => [1], + + // variants + + /// This is a special instruction used for `VariantLower` + /// instruction to determine the name of the payload, if present, to use + /// within each block. + /// + /// Each sub-block will have this be the first instruction, and if it + /// lowers a payload it will expect something bound to this name. + VariantPayloadName : [0] => [1], + + /// Pops a variant off the stack as well as `ty.cases.len()` blocks + /// from the code generator. Uses each of those blocks and the value + /// from the stack to produce `nresults` of items. + VariantLower { + variant: &'a Variant, + name: &'a str, + ty: TypeId, + results: &'a [WasmType], + } : [1] => [results.len()], + + /// Pops an `i32` off the stack as well as `ty.cases.len()` blocks + /// from the code generator. Uses each of those blocks and the value + /// from the stack to produce a final variant. + VariantLift { + variant: &'a Variant, + name: &'a str, + ty: TypeId, + } : [1] => [1], + + /// Same as `VariantLower`, except used for unions. + UnionLower { + union: &'a Union, + name: &'a str, + ty: TypeId, + results: &'a [WasmType], + } : [1] => [results.len()], + + /// Same as `VariantLift`, except used for unions. + UnionLift { + union: &'a Union, + name: &'a str, + ty: TypeId, + } : [1] => [1], + + /// Pops an enum off the stack and pushes the `i32` representation. + EnumLower { + enum_: &'a Enum, + name: &'a str, + ty: TypeId, + } : [1] => [1], + + /// Pops an `i32` off the stack and lifts it into the `enum` specified. + EnumLift { + enum_: &'a Enum, + name: &'a str, + ty: TypeId, + } : [1] => [1], + + /// Specialization of `VariantLower` for specifically `option` types, + /// otherwise behaves the same as `VariantLower` (e.g. two blocks for + /// the two cases. + OptionLower { + payload: &'a Type, + ty: TypeId, + results: &'a [WasmType], + } : [1] => [results.len()], + + /// Specialization of `VariantLift` for specifically the `option` + /// type. Otherwise behaves the same as the `VariantLift` instruction + /// with two blocks for the lift. + OptionLift { + payload: &'a Type, + ty: TypeId, + } : [1] => [1], + + /// Specialization of `VariantLower` for specifically `expected` + /// types, otherwise behaves the same as `VariantLower` (e.g. two blocks + /// for the two cases. + ExpectedLower { + expected: &'a Expected, + ty: TypeId, + results: &'a [WasmType], + } : [1] => [results.len()], + + /// Specialization of `VariantLift` for specifically the `expected` type. Otherwise behaves the same as the `VariantLift` + /// instruction with two blocks for the lift. + ExpectedLift { + expected: &'a Expected, + ty: TypeId, + } : [1] => [1], + + // calling/control flow + + /// Represents a call to a raw WebAssembly API. The module/name are + /// provided inline as well as the types if necessary. + /// + /// Note that this instruction is not currently used for async + /// functions, instead `CallWasmAsyncImport` and `CallWasmAsyncExport` + /// are used. + CallWasm { + iface: &'a Interface, + name: &'a str, + sig: &'a WasmSignature, + } : [sig.params.len()] => [sig.results.len()], + + /// Represents a call to an asynchronous wasm import. + /// + /// This currently only happens when a compiled-to-wasm module calls as + /// async import. This instruction is used to indicate that the + /// specified import function should be called. The specified import + /// function has `params` as its types, but the final two parameters + /// must be synthesized by this instruction which are the + /// callback/callback state. The actual imported function does not + /// return anything but the callback will be called with the `i32` state + /// as the first parameter and `results` as the rest of the parameters. + /// The callback function should return nothing. + /// + /// It's up to the bindings generator to figure out how to make this + /// look synchronous despite it being callback-based in the middle. + CallWasmAsyncImport { + iface: &'a Interface, + name: &'a str, + params: &'a [WasmType], + results: &'a [WasmType], + } : [params.len() - 2] => [results.len()], + + /// Represents a call to an asynchronous wasm export. + /// + /// This currently only happens when a host module calls an async + /// function on a wasm module. The specified function will take `params` + /// as its argument plus one more argument of an `i32` state that the + /// host needs to synthesize. The function being called doesn't actually + /// return anything. Instead wasm will call an `async_export_done` + /// intrinsic in the `canonical_abi` module. This intrinsic receives a + /// context value and a pointer into linear memory. The context value + /// lines up with the final `i32` parameter of this function call (which + /// the bindings generator must synthesize) and the pointer into linear + /// memory contains the `results`, stored at 8-byte offsets in the same + /// manner that multiple results are transferred. + /// + /// It's up to the bindings generator to figure out how to make this + /// look synchronous despite it being callback-based in the middle. + CallWasmAsyncExport { + module: &'a str, + name: &'a str, + params: &'a [WasmType], + results: &'a [WasmType], + } : [params.len() - 1] => [results.len()], + + /// Same as `CallWasm`, except the dual where an interface is being + /// called rather than a raw wasm function. + /// + /// Note that this will be used for async functions. + CallInterface { + module: &'a str, + func: &'a Function, + } : [func.params.len()] => [1], + + /// Returns `amt` values on the stack. This is always the last + /// instruction. + /// + /// Note that this instruction is used for asynchronous functions where + /// the results are *lifted*, not when they're *lowered*, though. For + /// those modes the `ReturnAsyncExport` and `ReturnAsyncImport` + /// functions are used. + Return { amt: usize, func: &'a Function } : [*amt] => [0], + + /// "Returns" from an asynchronous export. + /// + /// This is only used for compiled-to-wasm modules at this time, and + /// only for the exports of async functions in those modules. This + /// instruction receives two parameters, the first of which is the + /// original context from the start of the function which was provided + /// when the export was first called (its last parameter). The second + /// argument is a pointer into linear memory with the results of the + /// asynchronous call already encoded. This instruction should then call + /// the `async_export_done` intrinsic in the `canonical_abi` module. + ReturnAsyncExport { func: &'a Function } : [2] => [0], + + /// "Returns" from an asynchronous import. + /// + /// This is only used for host modules at this time, and + /// only for the import of async functions in those modules. This + /// instruction receives the operands used to call the completion + /// function in the wasm module. The first parameter to this instruction + /// is the index into the function table of the function to call, and + /// the remaining parameters are the parameters to invoke the function + /// with. + ReturnAsyncImport { + func: &'a Function, + params: usize, + } : [*params + 2] => [0], + + /// Calls the `realloc` function specified in a malloc-like fashion + /// allocating `size` bytes with alignment `align`. + /// + /// Pushes the returned pointer onto the stack. + Malloc { + realloc: &'static str, + size: usize, + align: usize, + } : [0] => [1], + + /// Calls the `free` function specified to deallocate the pointer on the + /// stack which has `size` bytes with alignment `align`. + Free { + free: &'static str, + size: usize, + align: usize, + } : [1] => [0], + } +} + +#[derive(Debug, PartialEq)] +pub enum Bitcast { + // Upcasts + F32ToI32, + F64ToI64, + I32ToI64, + F32ToI64, + + // Downcasts + I32ToF32, + I64ToF64, + I64ToI32, + I64ToF32, + + None, +} + +/// Whether the glue code surrounding a call is lifting arguments and lowering +/// results or vice versa. +#[derive(Clone, Copy, PartialEq, Eq)] +pub enum LiftLower { + /// When the glue code lifts arguments and lowers results. + /// + /// ```text + /// Wasm --lift-args--> SourceLanguage; call; SourceLanguage --lower-results--> Wasm + /// ``` + LiftArgsLowerResults, + /// When the glue code lowers arguments and lifts results. + /// + /// ```text + /// SourceLanguage --lower-args--> Wasm; call; Wasm --lift-results--> SourceLanguage + /// ``` + LowerArgsLiftResults, +} + +/// We use a different ABI for wasm importing functions exported by the host +/// than for wasm exporting functions imported by the host. +/// +/// Note that this reflects the flavor of ABI we generate, and not necessarily +/// the way the resulting bindings will be used by end users. See the comments +/// on the `Direction` enum in gen-core for details. +/// +/// The bindings ABI has a concept of a "guest" and a "host". There are two +/// variants of the ABI, one specialized for the "guest" importing and calling +/// a function defined and exported in the "host", and the other specialized for +/// the "host" importing and calling a function defined and exported in the "guest". +#[derive(Clone, Copy, PartialEq, Eq, Debug)] +pub enum AbiVariant { + /// The guest is importing and calling the function. + GuestImport, + /// The guest is defining and exporting the function. + GuestExport, +} + +/// Trait for language implementors to use to generate glue code between native +/// WebAssembly signatures and interface types signatures. +/// +/// This is used as an implementation detail in interpreting the ABI between +/// interface types and wasm types. Eventually this will be driven by interface +/// types adapters themselves, but for now the ABI of a function dictates what +/// instructions are fed in. +/// +/// Types implementing `Bindgen` are incrementally fed `Instruction` values to +/// generate code for. Instructions operate like a stack machine where each +/// instruction has a list of inputs and a list of outputs (provided by the +/// `emit` function). +pub trait Bindgen { + /// The intermediate type for fragments of code for this type. + /// + /// For most languages `String` is a suitable intermediate type. + type Operand: Clone; + + /// Emit code to implement the given instruction. + /// + /// Each operand is given in `operands` and can be popped off if ownership + /// is required. It's guaranteed that `operands` has the appropriate length + /// for the `inst` given, as specified with [`Instruction`]. + /// + /// Each result variable should be pushed onto `results`. This function must + /// push the appropriate number of results or binding generation will panic. + fn emit( + &mut self, + iface: &Interface, + inst: &Instruction<'_>, + operands: &mut Vec, + results: &mut Vec, + ); + + /// Gets a operand reference to the return pointer area. + /// + /// The provided size and alignment is for the function's return type. + fn return_pointer(&mut self, iface: &Interface, size: usize, align: usize) -> Self::Operand; + + /// Enters a new block of code to generate code for. + /// + /// This is currently exclusively used for constructing variants. When a + /// variant is constructed a block here will be pushed for each case of a + /// variant, generating the code necessary to translate a variant case. + /// + /// Blocks are completed with `finish_block` below. It's expected that `emit` + /// will always push code (if necessary) into the "current block", which is + /// updated by calling this method and `finish_block` below. + fn push_block(&mut self); + + /// Indicates to the code generator that a block is completed, and the + /// `operand` specified was the resulting value of the block. + /// + /// This method will be used to compute the value of each arm of lifting a + /// variant. The `operand` will be `None` if the variant case didn't + /// actually have any type associated with it. Otherwise it will be `Some` + /// as the last value remaining on the stack representing the value + /// associated with a variant's `case`. + /// + /// It's expected that this will resume code generation in the previous + /// block before `push_block` was called. This must also save the results + /// of the current block internally for instructions like `ResultLift` to + /// use later. + fn finish_block(&mut self, operand: &mut Vec); + + /// Returns size information that was previously calculated for all types. + fn sizes(&self) -> &crate::sizealign::SizeAlign; + + /// Returns whether or not the specified element type is represented in a + /// "canonical" form for lists. This dictates whether the `ListCanonLower` + /// and `ListCanonLift` instructions are used or not. + fn is_list_canonical(&self, iface: &Interface, element: &Type) -> bool; +} + +impl Interface { + /// Get the WebAssembly type signature for this interface function + /// + /// The first entry returned is the list of parameters and the second entry + /// is the list of results for the wasm function signature. + pub fn wasm_signature(&self, variant: AbiVariant, func: &Function) -> WasmSignature { + const MAX_FLAT_PARAMS: usize = 16; + const MAX_FLAT_RESULTS: usize = 1; + + let mut params = Vec::new(); + let mut indirect_params = false; + for (_, param) in func.params.iter() { + self.push_wasm(variant, param, &mut params); + } + + if params.len() > MAX_FLAT_PARAMS { + params.truncate(0); + params.push(WasmType::I32); + indirect_params = true; + } + + let mut results = Vec::new(); + self.push_wasm(variant, &func.result, &mut results); + + let mut retptr = false; + if func.is_async { + // Asynchronous functions never actually return anything since + // they're all callback-based, meaning that we always put all the + // results into a return pointer. + // + // Asynchronous exports take one extra parameter which is the + // context used to pass to the `async_export_done` intrinsic, and + // asynchronous imports take two extra parameters where the first is + // a pointer into the function table and the second is a context + // argument to pass to this function. + match variant { + AbiVariant::GuestExport => { + retptr = true; + results.truncate(0); + params.push(WasmType::I32); + } + AbiVariant::GuestImport => { + retptr = true; + results.truncate(0); + params.push(WasmType::I32); + params.push(WasmType::I32); + } + } + } else { + // Rust/C don't support multi-value well right now, so if a function + // would have multiple results then instead truncate it. Imports take a + // return pointer to write into and exports return a pointer they wrote + // into. + if results.len() > MAX_FLAT_RESULTS { + retptr = true; + results.truncate(0); + match variant { + AbiVariant::GuestImport => { + params.push(WasmType::I32); + } + AbiVariant::GuestExport => { + results.push(WasmType::I32); + } + } + } + } + + WasmSignature { + params, + indirect_params, + results, + retptr, + } + } + + fn push_wasm(&self, variant: AbiVariant, ty: &Type, result: &mut Vec) { + match ty { + Type::Unit => {} + + Type::Bool + | Type::S8 + | Type::U8 + | Type::S16 + | Type::U16 + | Type::S32 + | Type::U32 + | Type::Char + | Type::Handle(_) => result.push(WasmType::I32), + + Type::U64 | Type::S64 => result.push(WasmType::I64), + Type::Float32 => result.push(WasmType::F32), + Type::Float64 => result.push(WasmType::F64), + Type::String => { + result.push(WasmType::I32); + result.push(WasmType::I32); + } + + Type::Id(id) => match &self.types[*id].kind { + TypeDefKind::Type(t) => self.push_wasm(variant, t, result), + + TypeDefKind::Record(r) => { + for field in r.fields.iter() { + self.push_wasm(variant, &field.ty, result); + } + } + + TypeDefKind::Tuple(t) => { + for ty in t.types.iter() { + self.push_wasm(variant, ty, result); + } + } + + TypeDefKind::Flags(r) => { + for _ in 0..r.repr().count() { + result.push(WasmType::I32); + } + } + + TypeDefKind::List(_) => { + result.push(WasmType::I32); + result.push(WasmType::I32); + } + + TypeDefKind::Variant(v) => { + result.push(v.tag().into()); + self.push_wasm_variants(variant, v.cases.iter().map(|c| &c.ty), result); + } + + TypeDefKind::Enum(e) => result.push(e.tag().into()), + + TypeDefKind::Option(t) => { + result.push(WasmType::I32); + self.push_wasm_variants(variant, [&Type::Unit, t], result); + } + + TypeDefKind::Expected(e) => { + result.push(WasmType::I32); + self.push_wasm_variants(variant, [&e.ok, &e.err], result); + } + + TypeDefKind::Union(u) => { + result.push(WasmType::I32); + self.push_wasm_variants(variant, u.cases.iter().map(|c| &c.ty), result); + } + + TypeDefKind::Future(_) => { + result.push(WasmType::I32); + } + + TypeDefKind::Stream(_) => { + result.push(WasmType::I32); + } + }, + } + } + + fn push_wasm_variants<'a>( + &self, + variant: AbiVariant, + tys: impl IntoIterator, + result: &mut Vec, + ) { + let mut temp = Vec::new(); + let start = result.len(); + + // Push each case's type onto a temporary vector, and then + // merge that vector into our final list starting at + // `start`. Note that this requires some degree of + // "unification" so we can handle things like `Result` where that turns into `[i32 i32]` where the second + // `i32` might be the `f32` bitcasted. + for ty in tys { + self.push_wasm(variant, ty, &mut temp); + + for (i, ty) in temp.drain(..).enumerate() { + match result.get_mut(start + i) { + Some(prev) => *prev = join(*prev, ty), + None => result.push(ty), + } + } + } + } + + /// Generates an abstract sequence of instructions which represents this + /// function being adapted as an imported function. + /// + /// The instructions here, when executed, will emulate a language with + /// interface types calling the concrete wasm implementation. The parameters + /// for the returned instruction sequence are the language's own + /// interface-types parameters. One instruction in the instruction stream + /// will be a `Call` which represents calling the actual raw wasm function + /// signature. + /// + /// This function is useful, for example, if you're building a language + /// generator for WASI bindings. This will document how to translate + /// language-specific values into the wasm types to call a WASI function, + /// and it will also automatically convert the results of the WASI function + /// back to a language-specific value. + pub fn call( + &self, + variant: AbiVariant, + lift_lower: LiftLower, + func: &Function, + bindgen: &mut impl Bindgen, + ) { + Generator::new(self, variant, lift_lower, bindgen).call(func); + } +} + +struct Generator<'a, B: Bindgen> { + variant: AbiVariant, + lift_lower: LiftLower, + bindgen: &'a mut B, + iface: &'a Interface, + operands: Vec, + results: Vec, + stack: Vec, + return_pointer: Option, +} + +impl<'a, B: Bindgen> Generator<'a, B> { + fn new( + iface: &'a Interface, + variant: AbiVariant, + lift_lower: LiftLower, + bindgen: &'a mut B, + ) -> Generator<'a, B> { + Generator { + iface, + variant, + lift_lower, + bindgen, + operands: Vec::new(), + results: Vec::new(), + stack: Vec::new(), + return_pointer: None, + } + } + + fn call(&mut self, func: &Function) { + let sig = self.iface.wasm_signature(self.variant, func); + + match self.lift_lower { + LiftLower::LowerArgsLiftResults => { + if !sig.indirect_params { + // If the parameters for this function aren't indirect + // (there aren't too many) then we simply do a normal lower + // operation for them all. + for (nth, (_, ty)) in func.params.iter().enumerate() { + self.emit(&Instruction::GetArg { nth }); + self.lower(ty); + } + } else { + // ... otherwise if parameters are indirect space is + // allocated from them and each argument is lowered + // individually into memory. + let (size, align) = self + .bindgen + .sizes() + .record(func.params.iter().map(|t| &t.1)); + let ptr = match self.variant { + // When a wasm module calls an import it will provide + // static space that isn't dynamically allocated. + AbiVariant::GuestImport => { + self.bindgen.return_pointer(self.iface, size, align) + } + // When calling a wasm module from the outside, though, + // malloc needs to be called. + AbiVariant::GuestExport => { + self.emit(&Instruction::Malloc { + realloc: "canonical_abi_realloc", + size, + align, + }); + self.stack.pop().unwrap() + } + }; + let mut offset = 0usize; + for (nth, (_, ty)) in func.params.iter().enumerate() { + self.emit(&Instruction::GetArg { nth }); + offset = align_to(offset, self.bindgen.sizes().align(ty)); + self.write_to_memory(ty, ptr.clone(), offset as i32); + offset += self.bindgen.sizes().size(ty); + } + + self.stack.push(ptr); + } + + if func.is_async { + // We emit custom instructions for async calls since they + // have different parameters synthesized by the bindings + // generator depending on what kind of call is being made. + // + // Note that no return pointer goop happens here because + // that's all done through parameters of callbacks instead. + let mut results = Vec::new(); + self.iface + .push_wasm(self.variant, &func.result, &mut results); + match self.variant { + AbiVariant::GuestImport => { + assert_eq!(self.stack.len(), sig.params.len() - 2); + self.emit(&Instruction::CallWasmAsyncImport { + iface: self.iface, + name: &func.name, + params: &sig.params, + results: &results, + }); + } + AbiVariant::GuestExport => { + assert_eq!(self.stack.len(), sig.params.len() - 1); + self.emit(&Instruction::CallWasmAsyncExport { + module: &self.iface.name, + name: &func.name, + params: &sig.params, + results: &results, + }); + } + } + + self.lift(&func.result); + } else { + // If necessary we may need to prepare a return pointer for + // this ABI. + if self.variant == AbiVariant::GuestImport && sig.retptr { + let size = self.bindgen.sizes().size(&func.result); + let align = self.bindgen.sizes().align(&func.result); + let ptr = self.bindgen.return_pointer(self.iface, size, align); + self.return_pointer = Some(ptr.clone()); + self.stack.push(ptr); + } + + // Now that all the wasm args are prepared we can call the + // actual wasm function. + assert_eq!(self.stack.len(), sig.params.len()); + self.emit(&Instruction::CallWasm { + iface: self.iface, + name: &func.name, + sig: &sig, + }); + + if !sig.retptr { + // With no return pointer in use we can simply lift the + // result of the function from the result of the core + // wasm function. + self.lift(&func.result); + } else { + let ptr = match self.variant { + // imports into guests means it's a wasm module + // calling an imported function. We supplied the + // return poitner as the last argument (saved in + // `self.return_pointer`) so we use that to read + // the result of the function from memory. + AbiVariant::GuestImport => { + assert!(sig.results.len() == 0); + self.return_pointer.take().unwrap() + } + + // guest exports means that this is a host + // calling wasm so wasm returned a pointer to where + // the result is stored + AbiVariant::GuestExport => self.stack.pop().unwrap(), + }; + + self.read_from_memory(&func.result, ptr, 0); + } + } + + self.emit(&Instruction::Return { func, amt: 1 }); + } + LiftLower::LiftArgsLowerResults => { + if !sig.indirect_params { + // If parameters are not passed indirectly then we lift each + // argument in succession from the component wasm types that + // make-up the type. + let mut offset = 0; + let mut temp = Vec::new(); + for (_, ty) in func.params.iter() { + temp.truncate(0); + self.iface.push_wasm(self.variant, ty, &mut temp); + for _ in 0..temp.len() { + self.emit(&Instruction::GetArg { nth: offset }); + offset += 1; + } + self.lift(ty); + } + } else { + // ... otherwise argument is read in succession from memory + // where the pointer to the arguments is the first argument + // to the function. + let mut offset = 0usize; + self.emit(&Instruction::GetArg { nth: 0 }); + let ptr = self.stack.pop().unwrap(); + for (_, ty) in func.params.iter() { + offset = align_to(offset, self.bindgen.sizes().align(ty)); + self.read_from_memory(ty, ptr.clone(), offset as i32); + offset += self.bindgen.sizes().size(ty); + } + } + + // ... and that allows us to call the interface types function + self.emit(&Instruction::CallInterface { + module: &self.iface.name, + func, + }); + + // This was dynamically allocated by the caller so after + // it's been read by the guest we need to deallocate it. + if let AbiVariant::GuestExport = self.variant { + if sig.indirect_params { + let (size, align) = self + .bindgen + .sizes() + .record(func.params.iter().map(|t| &t.1)); + self.emit(&Instruction::GetArg { nth: 0 }); + self.emit(&Instruction::Free { + free: "canonical_abi_free", + size, + align, + }); + } + } + + if func.is_async { + match self.variant { + // Returning from a guest import means that the + // completion callback needs to be called which is + // currently given the lowered representation of the + // result. + AbiVariant::GuestImport => { + self.lower(&func.result); + + let mut tys = Vec::new(); + self.iface.push_wasm(self.variant, &func.result, &mut tys); + assert_eq!(self.stack.len(), tys.len()); + let operands = mem::take(&mut self.stack); + // function index to call + self.emit(&Instruction::GetArg { + nth: sig.params.len() - 2, + }); + // environment for the function + self.emit(&Instruction::GetArg { + nth: sig.params.len() - 1, + }); + self.stack.extend(operands); + self.emit(&Instruction::ReturnAsyncImport { + func, + params: tys.len(), + }); + } + + // Returning from a guest export means that we need to + // invoke the completion intrinsics with where the + // result is stored in linear memory. + AbiVariant::GuestExport => { + let size = self.bindgen.sizes().size(&func.result); + let align = self.bindgen.sizes().align(&func.result); + let ptr = self.bindgen.return_pointer(self.iface, size, align); + self.write_to_memory(&func.result, ptr.clone(), 0); + + // Get the caller's context index. + self.emit(&Instruction::GetArg { + nth: sig.params.len() - 1, + }); + self.stack.push(ptr); + + // This will call the "done" function with the + // context/pointer argument + self.emit(&Instruction::ReturnAsyncExport { func }); + } + } + } else { + if !sig.retptr { + // With no return pointer in use we simply lower the + // result and return that directly from the function. + self.lower(&func.result); + } else { + match self.variant { + // When a function is imported to a guest this means + // it's a host providing the implementation of the + // import. The result is stored in the pointer + // specified in the last argument, so we get the + // pointer here and then write the return value into + // it. + AbiVariant::GuestImport => { + self.emit(&Instruction::GetArg { + nth: sig.params.len() - 1, + }); + let ptr = self.stack.pop().unwrap(); + self.write_to_memory(&func.result, ptr, 0); + } + + // For a guest import this is a function defined in + // wasm, so we're returning a pointer where the + // value was stored at. Allocate some space here + // (statically) and then write the result into that + // memory, returning the pointer at the end. + AbiVariant::GuestExport => { + let size = self.bindgen.sizes().size(&func.result); + let align = self.bindgen.sizes().align(&func.result); + let ptr = self.bindgen.return_pointer(self.iface, size, align); + self.write_to_memory(&func.result, ptr.clone(), 0); + self.stack.push(ptr); + } + } + } + + self.emit(&Instruction::Return { + func, + amt: sig.results.len(), + }); + } + } + } + + assert!( + self.stack.is_empty(), + "stack has {} items remaining", + self.stack.len() + ); + } + + fn emit(&mut self, inst: &Instruction<'_>) { + self.operands.clear(); + self.results.clear(); + + let operands_len = inst.operands_len(); + assert!( + self.stack.len() >= operands_len, + "not enough operands on stack for {:?}", + inst + ); + self.operands + .extend(self.stack.drain((self.stack.len() - operands_len)..)); + self.results.reserve(inst.results_len()); + + self.bindgen + .emit(self.iface, inst, &mut self.operands, &mut self.results); + + assert_eq!( + self.results.len(), + inst.results_len(), + "{:?} expected {} results, got {}", + inst, + inst.results_len(), + self.results.len() + ); + self.stack.append(&mut self.results); + } + + fn push_block(&mut self) { + self.bindgen.push_block(); + } + + fn finish_block(&mut self, size: usize) { + self.operands.clear(); + assert!( + size <= self.stack.len(), + "not enough operands on stack for finishing block", + ); + self.operands + .extend(self.stack.drain((self.stack.len() - size)..)); + self.bindgen.finish_block(&mut self.operands); + } + + fn lower(&mut self, ty: &Type) { + use Instruction::*; + + match *ty { + Type::Unit => self.emit(&UnitLower), + Type::Bool => self.emit(&I32FromBool), + Type::S8 => self.emit(&I32FromS8), + Type::U8 => self.emit(&I32FromU8), + Type::S16 => self.emit(&I32FromS16), + Type::U16 => self.emit(&I32FromU16), + Type::S32 => self.emit(&I32FromS32), + Type::U32 => self.emit(&I32FromU32), + Type::S64 => self.emit(&I64FromS64), + Type::U64 => self.emit(&I64FromU64), + Type::Char => self.emit(&I32FromChar), + Type::Float32 => self.emit(&F32FromFloat32), + Type::Float64 => self.emit(&F64FromFloat64), + Type::Handle(ty) => { + let borrowed = match self.lift_lower { + // This means that a return value is being lowered, which is + // never borrowed. + LiftLower::LiftArgsLowerResults => false, + // There's one of three possible situations we're in: + // + // * The handle is defined by the wasm module itself. This + // is the only actual possible scenario today due to how + // witx is defined. In this situation the handle is owned + // by the host and "proof of ownership" is being offered + // and there's no need to relinquish ownership. + // + // * The handle is defined by the host, and it's passing it + // to a wasm module. This should use an owned conversion. + // This isn't expressible in today's `*.witx` format. + // + // * The handle is defined by neither the host or the wasm + // mdoule. This means that the host is passing a + // capability from another wasm module into this one, + // meaning it's doing so by reference since the host is + // retaining access to its own + // + // Note, again, only the first bullet here is possible + // today, hence the hardcoded `true` value. We'll need to + // refactor `witx` to expose the other possibilities. + LiftLower::LowerArgsLiftResults => true, + }; + if borrowed { + self.emit(&I32FromBorrowedHandle { ty }); + } else { + self.emit(&I32FromOwnedHandle { ty }); + } + } + Type::String => { + let realloc = self.list_realloc(); + self.emit(&StringLower { realloc }); + } + Type::Id(id) => match &self.iface.types[id].kind { + TypeDefKind::Type(t) => self.lower(t), + TypeDefKind::List(element) => { + let realloc = self.list_realloc(); + if self.bindgen.is_list_canonical(self.iface, element) { + self.emit(&ListCanonLower { element, realloc }); + } else { + self.push_block(); + self.emit(&IterElem { element }); + self.emit(&IterBasePointer); + let addr = self.stack.pop().unwrap(); + self.write_to_memory(element, addr, 0); + self.finish_block(0); + self.emit(&ListLower { element, realloc }); + } + } + TypeDefKind::Record(record) => { + self.emit(&RecordLower { + record, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + let values = self + .stack + .drain(self.stack.len() - record.fields.len()..) + .collect::>(); + for (field, value) in record.fields.iter().zip(values) { + self.stack.push(value); + self.lower(&field.ty); + } + } + TypeDefKind::Tuple(tuple) => { + self.emit(&TupleLower { tuple, ty: id }); + let values = self + .stack + .drain(self.stack.len() - tuple.types.len()..) + .collect::>(); + for (ty, value) in tuple.types.iter().zip(values) { + self.stack.push(value); + self.lower(ty); + } + } + + TypeDefKind::Flags(flags) => { + self.emit(&FlagsLower { + flags, + ty: id, + name: self.iface.types[id].name.as_ref().unwrap(), + }); + } + + TypeDefKind::Variant(v) => { + let results = self.lower_variant_arms(ty, v.cases.iter().map(|c| &c.ty)); + self.emit(&VariantLower { + variant: v, + ty: id, + results: &results, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + TypeDefKind::Enum(enum_) => { + self.emit(&EnumLower { + enum_, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + TypeDefKind::Option(t) => { + let results = self.lower_variant_arms(ty, [&Type::Unit, t]); + self.emit(&OptionLower { + payload: t, + ty: id, + results: &results, + }); + } + TypeDefKind::Expected(e) => { + let results = self.lower_variant_arms(ty, [&e.ok, &e.err]); + self.emit(&ExpectedLower { + expected: e, + ty: id, + results: &results, + }); + } + TypeDefKind::Union(union) => { + let results = self.lower_variant_arms(ty, union.cases.iter().map(|c| &c.ty)); + self.emit(&UnionLower { + union, + ty: id, + results: &results, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + TypeDefKind::Future(_) => todo!("lower future"), + TypeDefKind::Stream(_) => todo!("lower stream"), + }, + } + } + + fn lower_variant_arms<'b>( + &mut self, + ty: &Type, + cases: impl IntoIterator, + ) -> Vec { + use Instruction::*; + let mut results = Vec::new(); + let mut temp = Vec::new(); + let mut casts = Vec::new(); + self.iface.push_wasm(self.variant, ty, &mut results); + for (i, ty) in cases.into_iter().enumerate() { + self.push_block(); + self.emit(&VariantPayloadName); + let payload_name = self.stack.pop().unwrap(); + self.emit(&I32Const { val: i as i32 }); + let mut pushed = 1; + // Using the payload of this block we lower the type to + // raw wasm values. + self.stack.push(payload_name.clone()); + self.lower(ty); + + // Determine the types of all the wasm values we just + // pushed, and record how many. If we pushed too few + // then we'll need to push some zeros after this. + temp.truncate(0); + self.iface.push_wasm(self.variant, ty, &mut temp); + pushed += temp.len(); + + // For all the types pushed we may need to insert some + // bitcasts. This will go through and cast everything + // to the right type to ensure all blocks produce the + // same set of results. + casts.truncate(0); + for (actual, expected) in temp.iter().zip(&results[1..]) { + casts.push(cast(*actual, *expected)); + } + if casts.iter().any(|c| *c != Bitcast::None) { + self.emit(&Bitcasts { casts: &casts }); + } + + // If we haven't pushed enough items in this block to match + // what other variants are pushing then we need to push + // some zeros. + if pushed < results.len() { + self.emit(&ConstZero { + tys: &results[pushed..], + }); + } + self.finish_block(results.len()); + } + results + } + + fn list_realloc(&self) -> Option<&'static str> { + // Lowering parameters calling a wasm import means + // we don't need to pass ownership, but we pass + // ownership in all other cases. + match (self.variant, self.lift_lower) { + (AbiVariant::GuestImport, LiftLower::LowerArgsLiftResults) => None, + _ => Some("canonical_abi_realloc"), + } + } + + /// Note that in general everything in this function is the opposite of the + /// `lower` function above. This is intentional and should be kept this way! + fn lift(&mut self, ty: &Type) { + use Instruction::*; + + match *ty { + Type::Unit => self.emit(&UnitLift), + Type::Bool => self.emit(&BoolFromI32), + Type::S8 => self.emit(&S8FromI32), + Type::U8 => self.emit(&U8FromI32), + Type::S16 => self.emit(&S16FromI32), + Type::U16 => self.emit(&U16FromI32), + Type::S32 => self.emit(&S32FromI32), + Type::U32 => self.emit(&U32FromI32), + Type::S64 => self.emit(&S64FromI64), + Type::U64 => self.emit(&U64FromI64), + Type::Char => self.emit(&CharFromI32), + Type::Float32 => self.emit(&Float32FromF32), + Type::Float64 => self.emit(&Float64FromF64), + Type::Handle(ty) => { + // For more information on these values see the comments in + // `lower` above. + let borrowed = match self.lift_lower { + LiftLower::LiftArgsLowerResults => true, + LiftLower::LowerArgsLiftResults => false, + }; + if borrowed { + self.emit(&HandleBorrowedFromI32 { ty }); + } else { + self.emit(&HandleOwnedFromI32 { ty }); + } + } + Type::String => { + let free = self.list_free(); + self.emit(&StringLift { free }); + } + Type::Id(id) => match &self.iface.types[id].kind { + TypeDefKind::Type(t) => self.lift(t), + TypeDefKind::List(element) => { + let free = self.list_free(); + if self.is_char(element) || self.bindgen.is_list_canonical(self.iface, element) + { + self.emit(&ListCanonLift { + element, + free, + ty: id, + }); + } else { + self.push_block(); + self.emit(&IterBasePointer); + let addr = self.stack.pop().unwrap(); + self.read_from_memory(element, addr, 0); + self.finish_block(1); + self.emit(&ListLift { + element, + free, + ty: id, + }); + } + } + TypeDefKind::Record(record) => { + let mut temp = Vec::new(); + self.iface.push_wasm(self.variant, ty, &mut temp); + let mut args = self + .stack + .drain(self.stack.len() - temp.len()..) + .collect::>(); + for field in record.fields.iter() { + temp.truncate(0); + self.iface.push_wasm(self.variant, &field.ty, &mut temp); + self.stack.extend(args.drain(..temp.len())); + self.lift(&field.ty); + } + self.emit(&RecordLift { + record, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + TypeDefKind::Tuple(tuple) => { + let mut temp = Vec::new(); + self.iface.push_wasm(self.variant, ty, &mut temp); + let mut args = self + .stack + .drain(self.stack.len() - temp.len()..) + .collect::>(); + for ty in tuple.types.iter() { + temp.truncate(0); + self.iface.push_wasm(self.variant, ty, &mut temp); + self.stack.extend(args.drain(..temp.len())); + self.lift(ty); + } + self.emit(&TupleLift { tuple, ty: id }); + } + TypeDefKind::Flags(flags) => { + self.emit(&FlagsLift { + flags, + ty: id, + name: self.iface.types[id].name.as_ref().unwrap(), + }); + } + + TypeDefKind::Variant(v) => { + self.lift_variant_arms(ty, v.cases.iter().map(|c| &c.ty)); + self.emit(&VariantLift { + variant: v, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + + TypeDefKind::Enum(enum_) => { + self.emit(&EnumLift { + enum_, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + + TypeDefKind::Option(t) => { + self.lift_variant_arms(ty, [&Type::Unit, t]); + self.emit(&OptionLift { payload: t, ty: id }); + } + + TypeDefKind::Expected(e) => { + self.lift_variant_arms(ty, [&e.ok, &e.err]); + self.emit(&ExpectedLift { + expected: e, + ty: id, + }); + } + + TypeDefKind::Union(union) => { + self.lift_variant_arms(ty, union.cases.iter().map(|c| &c.ty)); + self.emit(&UnionLift { + union, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + + TypeDefKind::Future(_) => todo!("lift future"), + TypeDefKind::Stream(_) => todo!("lift stream"), + }, + } + } + + fn lift_variant_arms<'b>(&mut self, ty: &Type, cases: impl IntoIterator) { + let mut params = Vec::new(); + let mut temp = Vec::new(); + let mut casts = Vec::new(); + self.iface.push_wasm(self.variant, ty, &mut params); + let block_inputs = self + .stack + .drain(self.stack.len() + 1 - params.len()..) + .collect::>(); + for ty in cases { + self.push_block(); + // Push only the values we need for this variant onto + // the stack. + temp.truncate(0); + self.iface.push_wasm(self.variant, ty, &mut temp); + self.stack + .extend(block_inputs[..temp.len()].iter().cloned()); + + // Cast all the types we have on the stack to the actual + // types needed for this variant, if necessary. + casts.truncate(0); + for (actual, expected) in temp.iter().zip(¶ms[1..]) { + casts.push(cast(*expected, *actual)); + } + if casts.iter().any(|c| *c != Bitcast::None) { + self.emit(&Instruction::Bitcasts { casts: &casts }); + } + + // Then recursively lift this variant's payload. + self.lift(ty); + self.finish_block(1); + } + } + + fn list_free(&self) -> Option<&'static str> { + // Lifting the arguments of a defined import means that, if + // possible, the caller still retains ownership and we don't + // free anything. + match (self.variant, self.lift_lower) { + (AbiVariant::GuestImport, LiftLower::LiftArgsLowerResults) => None, + _ => Some("canonical_abi_free"), + } + } + + fn write_to_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + use Instruction::*; + + match *ty { + Type::Unit => self.lower(ty), + // Builtin types need different flavors of storage instructions + // depending on the size of the value written. + Type::Bool | Type::U8 | Type::S8 => { + self.lower_and_emit(ty, addr, &I32Store8 { offset }) + } + Type::U16 | Type::S16 => self.lower_and_emit(ty, addr, &I32Store16 { offset }), + Type::U32 | Type::S32 | Type::Handle(_) | Type::Char => { + self.lower_and_emit(ty, addr, &I32Store { offset }) + } + Type::U64 | Type::S64 => self.lower_and_emit(ty, addr, &I64Store { offset }), + Type::Float32 => self.lower_and_emit(ty, addr, &F32Store { offset }), + Type::Float64 => self.lower_and_emit(ty, addr, &F64Store { offset }), + Type::String => self.write_list_to_memory(ty, addr, offset), + + Type::Id(id) => match &self.iface.types[id].kind { + TypeDefKind::Type(t) => self.write_to_memory(t, addr, offset), + TypeDefKind::List(_) => self.write_list_to_memory(ty, addr, offset), + + // Decompose the record into its components and then write all + // the components into memory one-by-one. + TypeDefKind::Record(record) => { + self.emit(&RecordLower { + record, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + self.write_fields_to_memory( + &record.fields.iter().map(|f| f.ty).collect::>(), + addr, + offset, + ); + } + TypeDefKind::Tuple(tuple) => { + self.emit(&TupleLower { tuple, ty: id }); + self.write_fields_to_memory(&tuple.types, addr, offset); + } + + TypeDefKind::Flags(f) => { + self.lower(ty); + match f.repr() { + FlagsRepr::U8 => { + self.stack.push(addr); + self.store_intrepr(offset, Int::U8); + } + FlagsRepr::U16 => { + self.stack.push(addr); + self.store_intrepr(offset, Int::U16); + } + FlagsRepr::U32(n) => { + for i in (0..n).rev() { + self.stack.push(addr.clone()); + self.emit(&I32Store { + offset: offset + (i as i32) * 4, + }); + } + } + } + } + + // Each case will get its own block, and the first item in each + // case is writing the discriminant. After that if we have a + // payload we write the payload after the discriminant, aligned up + // to the type's alignment. + TypeDefKind::Variant(v) => { + self.write_variant_arms_to_memory( + offset, + addr, + v.tag(), + v.cases.iter().map(|c| &c.ty), + ); + self.emit(&VariantLower { + variant: v, + ty: id, + results: &[], + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + + TypeDefKind::Option(t) => { + self.write_variant_arms_to_memory(offset, addr, Int::U8, [&Type::Unit, t]); + self.emit(&OptionLower { + payload: t, + ty: id, + results: &[], + }); + } + + TypeDefKind::Expected(e) => { + self.write_variant_arms_to_memory(offset, addr, Int::U8, [&e.ok, &e.err]); + self.emit(&ExpectedLower { + expected: e, + ty: id, + results: &[], + }); + } + + TypeDefKind::Enum(e) => { + self.lower(ty); + self.stack.push(addr); + self.store_intrepr(offset, e.tag()); + } + + TypeDefKind::Union(union) => { + self.write_variant_arms_to_memory( + offset, + addr, + union.tag(), + union.cases.iter().map(|c| &c.ty), + ); + self.emit(&UnionLower { + union, + ty: id, + results: &[], + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + + TypeDefKind::Future(_) => todo!("write future to memory"), + TypeDefKind::Stream(_) => todo!("write stream to memory"), + }, + } + } + + fn write_variant_arms_to_memory<'b>( + &mut self, + offset: i32, + addr: B::Operand, + tag: Int, + cases: impl IntoIterator + Clone, + ) { + let payload_offset = + offset + (self.bindgen.sizes().payload_offset(tag, cases.clone()) as i32); + for (i, ty) in cases.into_iter().enumerate() { + self.push_block(); + self.emit(&Instruction::VariantPayloadName); + let payload_name = self.stack.pop().unwrap(); + self.emit(&Instruction::I32Const { val: i as i32 }); + self.stack.push(addr.clone()); + self.store_intrepr(offset, tag); + self.stack.push(payload_name.clone()); + self.write_to_memory(ty, addr.clone(), payload_offset); + self.finish_block(0); + } + } + + fn write_list_to_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + // After lowering the list there's two i32 values on the stack + // which we write into memory, writing the pointer into the low address + // and the length into the high address. + self.lower(ty); + self.stack.push(addr.clone()); + self.emit(&Instruction::I32Store { offset: offset + 4 }); + self.stack.push(addr); + self.emit(&Instruction::I32Store { offset }); + } + + fn write_fields_to_memory(&mut self, tys: &[Type], addr: B::Operand, offset: i32) { + let fields = self + .stack + .drain(self.stack.len() - tys.len()..) + .collect::>(); + for ((field_offset, op), ty) in self + .bindgen + .sizes() + .field_offsets(tys.iter()) + .into_iter() + .zip(fields) + .zip(tys) + { + self.stack.push(op); + self.write_to_memory(ty, addr.clone(), offset + (field_offset as i32)); + } + } + + fn lower_and_emit(&mut self, ty: &Type, addr: B::Operand, instr: &Instruction) { + self.lower(ty); + self.stack.push(addr); + self.emit(instr); + } + + fn read_from_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + use Instruction::*; + + match *ty { + Type::Unit => self.emit(&UnitLift), + Type::Bool => self.emit_and_lift(ty, addr, &I32Load8U { offset }), + Type::U8 => self.emit_and_lift(ty, addr, &I32Load8U { offset }), + Type::S8 => self.emit_and_lift(ty, addr, &I32Load8S { offset }), + Type::U16 => self.emit_and_lift(ty, addr, &I32Load16U { offset }), + Type::S16 => self.emit_and_lift(ty, addr, &I32Load16S { offset }), + Type::U32 | Type::S32 | Type::Char | Type::Handle(_) => { + self.emit_and_lift(ty, addr, &I32Load { offset }) + } + Type::U64 | Type::S64 => self.emit_and_lift(ty, addr, &I64Load { offset }), + Type::Float32 => self.emit_and_lift(ty, addr, &F32Load { offset }), + Type::Float64 => self.emit_and_lift(ty, addr, &F64Load { offset }), + Type::String => self.read_list_from_memory(ty, addr, offset), + + Type::Id(id) => match &self.iface.types[id].kind { + TypeDefKind::Type(t) => self.read_from_memory(t, addr, offset), + + TypeDefKind::List(_) => self.read_list_from_memory(ty, addr, offset), + + // Read and lift each field individually, adjusting the offset + // as we go along, then aggregate all the fields into the + // record. + TypeDefKind::Record(record) => { + self.read_fields_from_memory( + &record.fields.iter().map(|f| f.ty).collect::>(), + addr, + offset, + ); + self.emit(&RecordLift { + record, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + TypeDefKind::Tuple(tuple) => { + self.read_fields_from_memory(&tuple.types, addr, offset); + self.emit(&TupleLift { tuple, ty: id }); + } + + TypeDefKind::Flags(f) => { + match f.repr() { + FlagsRepr::U8 => { + self.stack.push(addr); + self.load_intrepr(offset, Int::U8); + } + FlagsRepr::U16 => { + self.stack.push(addr); + self.load_intrepr(offset, Int::U16); + } + FlagsRepr::U32(n) => { + for i in 0..n { + self.stack.push(addr.clone()); + self.emit(&I32Load { + offset: offset + (i as i32) * 4, + }); + } + } + } + self.lift(ty); + } + + // Each case will get its own block, and we'll dispatch to the + // right block based on the `i32.load` we initially perform. Each + // individual block is pretty simple and just reads the payload type + // from the corresponding offset if one is available. + TypeDefKind::Variant(variant) => { + self.read_variant_arms_from_memory( + offset, + addr, + variant.tag(), + variant.cases.iter().map(|c| &c.ty), + ); + self.emit(&VariantLift { + variant, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + + TypeDefKind::Option(t) => { + self.read_variant_arms_from_memory(offset, addr, Int::U8, [&Type::Unit, t]); + self.emit(&OptionLift { payload: t, ty: id }); + } + + TypeDefKind::Expected(e) => { + self.read_variant_arms_from_memory(offset, addr, Int::U8, [&e.ok, &e.err]); + self.emit(&ExpectedLift { + expected: e, + ty: id, + }); + } + + TypeDefKind::Enum(e) => { + self.stack.push(addr.clone()); + self.load_intrepr(offset, e.tag()); + self.lift(ty); + } + + TypeDefKind::Union(union) => { + self.read_variant_arms_from_memory( + offset, + addr, + union.tag(), + union.cases.iter().map(|c| &c.ty), + ); + self.emit(&UnionLift { + union, + ty: id, + name: self.iface.types[id].name.as_deref().unwrap(), + }); + } + + TypeDefKind::Future(_) => todo!("read future from memory"), + TypeDefKind::Stream(_) => todo!("read stream from memory"), + }, + } + } + + fn read_variant_arms_from_memory<'b>( + &mut self, + offset: i32, + addr: B::Operand, + tag: Int, + cases: impl IntoIterator + Clone, + ) { + self.stack.push(addr.clone()); + self.load_intrepr(offset, tag); + let payload_offset = + offset + (self.bindgen.sizes().payload_offset(tag, cases.clone()) as i32); + for ty in cases { + self.push_block(); + self.read_from_memory(ty, addr.clone(), payload_offset); + self.finish_block(1); + } + } + + fn read_list_from_memory(&mut self, ty: &Type, addr: B::Operand, offset: i32) { + // Read the pointer/len and then perform the standard lifting + // proceses. + self.stack.push(addr.clone()); + self.emit(&Instruction::I32Load { offset }); + self.stack.push(addr); + self.emit(&Instruction::I32Load { offset: offset + 4 }); + self.lift(ty); + } + + fn read_fields_from_memory(&mut self, tys: &[Type], addr: B::Operand, offset: i32) { + for (field_offset, ty) in self.bindgen.sizes().field_offsets(tys).into_iter().zip(tys) { + self.read_from_memory(ty, addr.clone(), offset + (field_offset as i32)); + } + } + + fn emit_and_lift(&mut self, ty: &Type, addr: B::Operand, instr: &Instruction) { + self.stack.push(addr); + self.emit(instr); + self.lift(ty); + } + + fn load_intrepr(&mut self, offset: i32, repr: Int) { + self.emit(&match repr { + Int::U64 => Instruction::I64Load { offset }, + Int::U32 => Instruction::I32Load { offset }, + Int::U16 => Instruction::I32Load16U { offset }, + Int::U8 => Instruction::I32Load8U { offset }, + }); + } + + fn store_intrepr(&mut self, offset: i32, repr: Int) { + self.emit(&match repr { + Int::U64 => Instruction::I64Store { offset }, + Int::U32 => Instruction::I32Store { offset }, + Int::U16 => Instruction::I32Store16 { offset }, + Int::U8 => Instruction::I32Store8 { offset }, + }); + } + + fn is_char(&self, ty: &Type) -> bool { + match ty { + Type::Char => true, + Type::Id(id) => match &self.iface.types[*id].kind { + TypeDefKind::Type(t) => self.is_char(t), + _ => false, + }, + _ => false, + } + } +} + +fn cast(from: WasmType, to: WasmType) -> Bitcast { + use WasmType::*; + + match (from, to) { + (I32, I32) | (I64, I64) | (F32, F32) | (F64, F64) => Bitcast::None, + + (I32, I64) => Bitcast::I32ToI64, + (F32, I32) => Bitcast::F32ToI32, + (F64, I64) => Bitcast::F64ToI64, + + (I64, I32) => Bitcast::I64ToI32, + (I32, F32) => Bitcast::I32ToF32, + (I64, F64) => Bitcast::I64ToF64, + + (F32, I64) => Bitcast::F32ToI64, + (I64, F32) => Bitcast::I64ToF32, + + (F32, F64) | (F64, F32) | (F64, I32) | (I32, F64) => unreachable!(), + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast.rs new file mode 100644 index 0000000..411a88f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast.rs @@ -0,0 +1,711 @@ +use anyhow::Result; +use lex::{Span, Token, Tokenizer}; +use std::borrow::Cow; +use std::collections::HashMap; +use std::convert::TryFrom; +use std::fmt; + +mod lex; +mod resolve; + +pub use lex::validate_id; + +pub struct Ast<'a> { + pub items: Vec>, +} + +pub enum Item<'a> { + Use(Use<'a>), + Resource(Resource<'a>), + TypeDef(TypeDef<'a>), + Value(Value<'a>), + Interface(Interface<'a>), +} + +pub struct Id<'a> { + pub name: Cow<'a, str>, + pub span: Span, +} + +impl<'a> From<&'a str> for Id<'a> { + fn from(s: &'a str) -> Id<'a> { + Id { + name: s.into(), + span: Span { start: 0, end: 0 }, + } + } +} + +impl<'a> From for Id<'a> { + fn from(s: String) -> Id<'a> { + Id { + name: s.into(), + span: Span { start: 0, end: 0 }, + } + } +} + +pub struct Use<'a> { + pub from: Vec>, + names: Option>>, +} + +struct UseName<'a> { + name: Id<'a>, + as_: Option>, +} + +pub struct Resource<'a> { + docs: Docs<'a>, + name: Id<'a>, + supertype: Option>, + values: Vec<(bool, Value<'a>)>, +} + +#[derive(Default)] +struct Docs<'a> { + docs: Vec>, +} + +pub struct TypeDef<'a> { + docs: Docs<'a>, + name: Id<'a>, + ty: Type<'a>, +} + +enum Type<'a> { + Unit, + Bool, + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + Float32, + Float64, + Char, + String, + Handle(Id<'a>), + Name(Id<'a>), + List(Box>), + Record(Record<'a>), + Flags(Flags<'a>), + Variant(Variant<'a>), + Tuple(Vec>), + Enum(Enum<'a>), + Option(Box>), + Expected(Expected<'a>), + Future(Box>), + Stream(Stream<'a>), + Union(Union<'a>), +} + +struct Record<'a> { + fields: Vec>, +} + +struct Field<'a> { + docs: Docs<'a>, + name: Id<'a>, + ty: Type<'a>, +} + +struct Flags<'a> { + flags: Vec>, +} + +struct Flag<'a> { + docs: Docs<'a>, + name: Id<'a>, +} + +struct Variant<'a> { + span: Span, + cases: Vec>, +} + +struct Case<'a> { + docs: Docs<'a>, + name: Id<'a>, + ty: Option>, +} + +struct Enum<'a> { + span: Span, + cases: Vec>, +} + +struct EnumCase<'a> { + docs: Docs<'a>, + name: Id<'a>, +} + +struct Expected<'a> { + ok: Box>, + err: Box>, +} + +struct Stream<'a> { + element: Box>, + end: Box>, +} + +pub struct Value<'a> { + docs: Docs<'a>, + name: Id<'a>, + kind: ValueKind<'a>, +} + +struct Union<'a> { + span: Span, + cases: Vec>, +} + +struct UnionCase<'a> { + docs: Docs<'a>, + ty: Type<'a>, +} + +enum ValueKind<'a> { + Function { + is_async: bool, + params: Vec<(Id<'a>, Type<'a>)>, + result: Type<'a>, + }, + Global(Type<'a>), +} + +#[allow(dead_code)] // TODO +pub struct Interface<'a> { + docs: Docs<'a>, + name: Id<'a>, + items: Vec>, +} + +impl<'a> Ast<'a> { + pub fn parse(input: &'a str) -> Result> { + let mut lexer = Tokenizer::new(input)?; + let mut items = Vec::new(); + while lexer.clone().next()?.is_some() { + let docs = parse_docs(&mut lexer)?; + items.push(Item::parse(&mut lexer, docs)?); + } + Ok(Ast { items }) + } + + pub fn resolve( + &self, + name: &str, + map: &HashMap, + ) -> Result { + let mut resolver = resolve::Resolver::default(); + let instance = resolver.resolve(name, &self.items, map)?; + Ok(instance) + } +} + +impl<'a> Item<'a> { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result> { + match tokens.clone().next()? { + Some((_span, Token::Use)) => Use::parse(tokens, docs).map(Item::Use), + Some((_span, Token::Type)) => TypeDef::parse(tokens, docs).map(Item::TypeDef), + Some((_span, Token::Flags)) => TypeDef::parse_flags(tokens, docs).map(Item::TypeDef), + Some((_span, Token::Enum)) => TypeDef::parse_enum(tokens, docs).map(Item::TypeDef), + Some((_span, Token::Variant)) => { + TypeDef::parse_variant(tokens, docs).map(Item::TypeDef) + } + Some((_span, Token::Record)) => TypeDef::parse_record(tokens, docs).map(Item::TypeDef), + Some((_span, Token::Union)) => TypeDef::parse_union(tokens, docs).map(Item::TypeDef), + Some((_span, Token::Resource)) => Resource::parse(tokens, docs).map(Item::Resource), + Some((_span, Token::Interface)) => Interface::parse(tokens, docs).map(Item::Interface), + Some((_span, Token::Id)) | Some((_span, Token::ExplicitId)) => { + Value::parse(tokens, docs).map(Item::Value) + } + other => Err(err_expected(tokens, "`type`, `resource`, or `func`", other).into()), + } + } +} + +impl<'a> Use<'a> { + fn parse(tokens: &mut Tokenizer<'a>, _docs: Docs<'a>) -> Result { + tokens.expect(Token::Use)?; + let mut names = None; + loop { + if names.is_none() { + if tokens.eat(Token::Star)? { + break; + } + tokens.expect(Token::LeftBrace)?; + names = Some(Vec::new()); + } + let names = names.as_mut().unwrap(); + let mut name = UseName { + name: parse_id(tokens)?, + as_: None, + }; + if tokens.eat(Token::As)? { + name.as_ = Some(parse_id(tokens)?); + } + names.push(name); + if !tokens.eat(Token::Comma)? { + break; + } + } + if names.is_some() { + tokens.expect(Token::RightBrace)?; + } + tokens.expect(Token::From_)?; + let mut from = vec![parse_id(tokens)?]; + while tokens.eat(Token::Colon)? { + tokens.expect_raw(Token::Colon)?; + from.push(parse_id(tokens)?); + } + Ok(Use { from, names }) + } +} + +impl<'a> TypeDef<'a> { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + tokens.expect(Token::Type)?; + let name = parse_id(tokens)?; + tokens.expect(Token::Equals)?; + let ty = Type::parse(tokens)?; + Ok(TypeDef { docs, name, ty }) + } + + fn parse_flags(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + tokens.expect(Token::Flags)?; + let name = parse_id(tokens)?; + let ty = Type::Flags(Flags { + flags: parse_list( + tokens, + Token::LeftBrace, + Token::RightBrace, + |docs, tokens| { + let name = parse_id(tokens)?; + Ok(Flag { docs, name }) + }, + )?, + }); + Ok(TypeDef { docs, name, ty }) + } + + fn parse_record(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + tokens.expect(Token::Record)?; + let name = parse_id(tokens)?; + let ty = Type::Record(Record { + fields: parse_list( + tokens, + Token::LeftBrace, + Token::RightBrace, + |docs, tokens| { + let name = parse_id(tokens)?; + tokens.expect(Token::Colon)?; + let ty = Type::parse(tokens)?; + Ok(Field { docs, name, ty }) + }, + )?, + }); + Ok(TypeDef { docs, name, ty }) + } + + fn parse_variant(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + tokens.expect(Token::Variant)?; + let name = parse_id(tokens)?; + let ty = Type::Variant(Variant { + span: name.span, + cases: parse_list( + tokens, + Token::LeftBrace, + Token::RightBrace, + |docs, tokens| { + let name = parse_id(tokens)?; + let ty = if tokens.eat(Token::LeftParen)? { + let ty = Type::parse(tokens)?; + tokens.expect(Token::RightParen)?; + Some(ty) + } else { + None + }; + Ok(Case { docs, name, ty }) + }, + )?, + }); + Ok(TypeDef { docs, name, ty }) + } + + fn parse_union(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + tokens.expect(Token::Union)?; + let name = parse_id(tokens)?; + let ty = Type::Union(Union { + span: name.span, + cases: parse_list( + tokens, + Token::LeftBrace, + Token::RightBrace, + |docs, tokens| { + let ty = Type::parse(tokens)?; + Ok(UnionCase { docs, ty }) + }, + )?, + }); + Ok(TypeDef { docs, name, ty }) + } + + fn parse_enum(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + tokens.expect(Token::Enum)?; + let name = parse_id(tokens)?; + let ty = Type::Enum(Enum { + span: name.span, + cases: parse_list( + tokens, + Token::LeftBrace, + Token::RightBrace, + |docs, tokens| { + let name = parse_id(tokens)?; + Ok(EnumCase { docs, name }) + }, + )?, + }); + Ok(TypeDef { docs, name, ty }) + } +} + +impl<'a> Resource<'a> { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + tokens.expect(Token::Resource)?; + let name = parse_id(tokens)?; + let supertype = if tokens.eat(Token::Implements)? { + Some(parse_id(tokens)?) + } else { + None + }; + let mut values = Vec::new(); + if tokens.eat(Token::LeftBrace)? { + loop { + let docs = parse_docs(tokens)?; + if tokens.eat(Token::RightBrace)? { + break; + } + let statik = tokens.eat(Token::Static)?; + values.push((statik, Value::parse(tokens, docs)?)); + } + } + Ok(Resource { + docs, + name, + supertype, + values, + }) + } +} + +impl<'a> Value<'a> { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + let name = parse_id(tokens)?; + tokens.expect(Token::Colon)?; + + let kind = if tokens.eat(Token::Func)? { + parse_func(tokens, false)? + } else if tokens.eat(Token::Async)? { + tokens.expect(Token::Func)?; + parse_func(tokens, true)? + } else { + ValueKind::Global(Type::parse(tokens)?) + }; + return Ok(Value { docs, name, kind }); + + fn parse_func<'a>(tokens: &mut Tokenizer<'a>, is_async: bool) -> Result> { + let params = parse_list( + tokens, + Token::LeftParen, + Token::RightParen, + |_docs, tokens| { + let name = parse_id(tokens)?; + tokens.expect(Token::Colon)?; + let ty = Type::parse(tokens)?; + Ok((name, ty)) + }, + )?; + let result = if tokens.eat(Token::RArrow)? { + Type::parse(tokens)? + } else { + Type::Unit + }; + Ok(ValueKind::Function { + is_async, + params, + result, + }) + } + } +} + +fn parse_id<'a>(tokens: &mut Tokenizer<'a>) -> Result> { + match tokens.next()? { + Some((span, Token::Id)) => Ok(Id { + name: tokens.parse_id(span)?.into(), + span, + }), + Some((span, Token::ExplicitId)) => Ok(Id { + name: tokens.parse_explicit_id(span)?.into(), + span, + }), + other => Err(err_expected(tokens, "an identifier or string", other).into()), + } +} + +fn parse_docs<'a>(tokens: &mut Tokenizer<'a>) -> Result> { + let mut docs = Docs::default(); + let mut clone = tokens.clone(); + while let Some((span, token)) = clone.next_raw()? { + match token { + Token::Whitespace => {} + Token::Comment => docs.docs.push(tokens.get_span(span).into()), + _ => break, + }; + *tokens = clone.clone(); + } + Ok(docs) +} + +impl<'a> Type<'a> { + fn parse(tokens: &mut Tokenizer<'a>) -> Result { + match tokens.next()? { + Some((_span, Token::U8)) => Ok(Type::U8), + Some((_span, Token::U16)) => Ok(Type::U16), + Some((_span, Token::U32)) => Ok(Type::U32), + Some((_span, Token::U64)) => Ok(Type::U64), + Some((_span, Token::S8)) => Ok(Type::S8), + Some((_span, Token::S16)) => Ok(Type::S16), + Some((_span, Token::S32)) => Ok(Type::S32), + Some((_span, Token::S64)) => Ok(Type::S64), + Some((_span, Token::Float32)) => Ok(Type::Float32), + Some((_span, Token::Float64)) => Ok(Type::Float64), + Some((_span, Token::Char)) => Ok(Type::Char), + Some((_span, Token::Handle)) => { + let name = parse_id(tokens)?; + Ok(Type::Handle(name)) + } + + // tuple + Some((_span, Token::Tuple)) => { + let types = parse_list( + tokens, + Token::LessThan, + Token::GreaterThan, + |_docs, tokens| Type::parse(tokens), + )?; + Ok(Type::Tuple(types)) + } + + Some((_span, Token::Unit)) => Ok(Type::Unit), + Some((_span, Token::Bool)) => Ok(Type::Bool), + Some((_span, Token::String_)) => Ok(Type::String), + + // list + Some((_span, Token::List)) => { + tokens.expect(Token::LessThan)?; + let ty = Type::parse(tokens)?; + tokens.expect(Token::GreaterThan)?; + Ok(Type::List(Box::new(ty))) + } + + // option + Some((_span, Token::Option_)) => { + tokens.expect(Token::LessThan)?; + let ty = Type::parse(tokens)?; + tokens.expect(Token::GreaterThan)?; + Ok(Type::Option(Box::new(ty))) + } + + // expected + Some((_span, Token::Expected)) => { + tokens.expect(Token::LessThan)?; + let ok = Box::new(Type::parse(tokens)?); + tokens.expect(Token::Comma)?; + let err = Box::new(Type::parse(tokens)?); + tokens.expect(Token::GreaterThan)?; + Ok(Type::Expected(Expected { ok, err })) + } + + // future + Some((_span, Token::Future)) => { + tokens.expect(Token::LessThan)?; + let ty = Box::new(Type::parse(tokens)?); + tokens.expect(Token::GreaterThan)?; + Ok(Type::Future(ty)) + } + + // stream + Some((_span, Token::Stream)) => { + tokens.expect(Token::LessThan)?; + let element = Box::new(Type::parse(tokens)?); + tokens.expect(Token::Comma)?; + let end = Box::new(Type::parse(tokens)?); + tokens.expect(Token::GreaterThan)?; + Ok(Type::Stream(Stream { element, end })) + } + + // `foo` + Some((span, Token::Id)) => Ok(Type::Name(Id { + name: tokens.parse_id(span)?.into(), + span, + })), + // `@foo` + Some((span, Token::ExplicitId)) => Ok(Type::Name(Id { + name: tokens.parse_explicit_id(span)?.into(), + span, + })), + + other => Err(err_expected(tokens, "a type", other).into()), + } + } +} + +impl<'a> Interface<'a> { + fn parse(tokens: &mut Tokenizer<'a>, docs: Docs<'a>) -> Result { + tokens.expect(Token::Interface)?; + let name = parse_id(tokens)?; + tokens.expect(Token::LeftBrace)?; + let mut items = Vec::new(); + loop { + let docs = parse_docs(tokens)?; + if tokens.eat(Token::RightBrace)? { + break; + } + items.push(Item::parse(tokens, docs)?); + } + Ok(Interface { docs, name, items }) + } +} + +fn parse_list<'a, T>( + tokens: &mut Tokenizer<'a>, + start: Token, + end: Token, + mut parse: impl FnMut(Docs<'a>, &mut Tokenizer<'a>) -> Result, +) -> Result> { + tokens.expect(start)?; + let mut items = Vec::new(); + loop { + // get docs before we skip them to try to eat the end token + let docs = parse_docs(tokens)?; + + // if we found an end token then we're done + if tokens.eat(end)? { + break; + } + + let item = parse(docs, tokens)?; + items.push(item); + + // if there's no trailing comma then this is required to be the end, + // otherwise we go through the loop to try to get another item + if !tokens.eat(Token::Comma)? { + tokens.expect(end)?; + break; + } + } + Ok(items) +} + +fn err_expected( + tokens: &Tokenizer<'_>, + expected: &'static str, + found: Option<(Span, Token)>, +) -> Error { + match found { + Some((span, token)) => Error { + span, + msg: format!("expected {}, found {}", expected, token.describe()), + }, + None => Error { + span: Span { + start: u32::try_from(tokens.input().len()).unwrap(), + end: u32::try_from(tokens.input().len()).unwrap(), + }, + msg: format!("expected {}, found eof", expected), + }, + } +} + +#[derive(Debug)] +struct Error { + span: Span, + msg: String, +} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.msg.fmt(f) + } +} + +impl std::error::Error for Error {} + +pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) { + let parse = match err.downcast_mut::() { + Some(err) => err, + None => return lex::rewrite_error(err, file, contents), + }; + let msg = highlight_err( + parse.span.start as usize, + Some(parse.span.end as usize), + file, + contents, + &parse.msg, + ); + *err = anyhow::anyhow!("{}", msg); +} + +fn highlight_err( + start: usize, + end: Option, + file: &str, + input: &str, + err: impl fmt::Display, +) -> String { + let (line, col) = linecol_in(start, input); + let snippet = input.lines().nth(line).unwrap_or(""); + let mut msg = format!( + "\ +{err} + --> {file}:{line}:{col} + | + {line:4} | {snippet} + | {marker:>0$}", + col + 1, + file = file, + line = line + 1, + col = col + 1, + err = err, + snippet = snippet, + marker = "^", + ); + if let Some(end) = end { + if let Some(s) = input.get(start..end) { + for _ in s.chars().skip(1) { + msg.push('-'); + } + } + } + return msg; + + fn linecol_in(pos: usize, text: &str) -> (usize, usize) { + let mut cur = 0; + // Use split_terminator instead of lines so that if there is a `\r`, + // it is included in the offset calculation. The `+1` values below + // account for the `\n`. + for (i, line) in text.split_terminator('\n').enumerate() { + if cur + line.len() + 1 > pos { + return (i, pos - cur); + } + cur += line.len() + 1; + } + (text.lines().count(), 0) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast/lex.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast/lex.rs new file mode 100644 index 0000000..f91c497 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast/lex.rs @@ -0,0 +1,709 @@ +use anyhow::{bail, Result}; +use std::char; +use std::convert::TryFrom; +use std::fmt; +use std::str; +use unicode_normalization::char::canonical_combining_class; +use unicode_xid::UnicodeXID; + +use self::Token::*; + +#[derive(Clone)] +pub struct Tokenizer<'a> { + input: &'a str, + chars: CrlfFold<'a>, +} + +#[derive(Clone)] +struct CrlfFold<'a> { + chars: str::CharIndices<'a>, +} + +/// A span, designating a range of bytes where a token is located. +#[derive(Eq, PartialEq, Debug, Clone, Copy)] +pub struct Span { + /// The start of the range. + pub start: u32, + /// The end of the range (exclusive). + pub end: u32, +} + +#[derive(Eq, PartialEq, Debug, Copy, Clone)] +pub enum Token { + Whitespace, + Comment, + + Equals, + Comma, + Colon, + Semicolon, + LeftParen, + RightParen, + LeftBrace, + RightBrace, + LessThan, + GreaterThan, + RArrow, + Star, + + Use, + Type, + Resource, + Func, + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + Float32, + Float64, + Char, + Handle, + Record, + Flags, + Variant, + Enum, + Union, + Bool, + String_, + Option_, + Expected, + Future, + Stream, + List, + Underscore, + As, + From_, + Static, + Interface, + Tuple, + Async, + Unit, + Implements, + + Id, + ExplicitId, +} + +#[derive(Eq, PartialEq, Debug)] +#[allow(dead_code)] +pub enum Error { + InvalidCharInString(usize, char), + InvalidCharInId(usize, char), + IdNotSSNFC(usize), + IdPartEmpty(usize), + InvalidEscape(usize, char), + // InvalidHexEscape(usize, char), + // InvalidEscapeValue(usize, u32), + Unexpected(usize, char), + UnterminatedComment(usize), + UnterminatedString(usize), + NewlineInString(usize), + Wanted { + at: usize, + expected: &'static str, + found: &'static str, + }, +} + +impl<'a> Tokenizer<'a> { + pub fn new(input: &'a str) -> Result> { + detect_invalid_input(input)?; + + let mut t = Tokenizer { + input, + chars: CrlfFold { + chars: input.char_indices(), + }, + }; + // Eat utf-8 BOM + t.eatc('\u{feff}'); + Ok(t) + } + + pub fn input(&self) -> &'a str { + self.input + } + + pub fn get_span(&self, span: Span) -> &'a str { + &self.input[span.start as usize..span.end as usize] + } + + pub fn parse_id(&self, span: Span) -> Result { + let ret = self.get_span(span).to_owned(); + validate_id(span.start as usize, &ret)?; + Ok(ret) + } + + pub fn parse_explicit_id(&self, span: Span) -> Result { + let token = self.get_span(span); + let id_part = token.strip_prefix('%').unwrap(); + validate_id(span.start as usize, id_part)?; + Ok(id_part.to_owned()) + } + + pub fn next(&mut self) -> Result, Error> { + loop { + match self.next_raw()? { + Some((_, Token::Whitespace)) | Some((_, Token::Comment)) => {} + other => break Ok(other), + } + } + } + + pub fn next_raw(&mut self) -> Result, Error> { + let (start, ch) = match self.chars.next() { + Some(pair) => pair, + None => return Ok(None), + }; + let token = match ch { + '\n' | '\t' | ' ' => { + // Eat all contiguous whitespace tokens + while self.eatc(' ') || self.eatc('\t') || self.eatc('\n') {} + Whitespace + } + '/' => { + // Eat a line comment if it's `//...` + if self.eatc('/') { + for (_, ch) in &mut self.chars { + if ch == '\n' { + break; + } + } + // eat a block comment if it's `/*...` + } else if self.eatc('*') { + let mut depth = 1; + while depth > 0 { + let (_, ch) = match self.chars.next() { + Some(pair) => pair, + None => return Err(Error::UnterminatedComment(start)), + }; + match ch { + '/' if self.eatc('*') => depth += 1, + '*' if self.eatc('/') => depth -= 1, + _ => {} + } + } + } else { + return Err(Error::Unexpected(start, ch)); + } + + Comment + } + '=' => Equals, + ',' => Comma, + ':' => Colon, + ';' => Semicolon, + '(' => LeftParen, + ')' => RightParen, + '{' => LeftBrace, + '}' => RightBrace, + '<' => LessThan, + '>' => GreaterThan, + '*' => Star, + '-' => { + if self.eatc('>') { + RArrow + } else { + return Err(Error::Unexpected(start, '-')); + } + } + '%' => { + let mut iter = self.chars.clone(); + if let Some((_, ch)) = iter.next() { + if is_keylike_start(ch) { + self.chars = iter.clone(); + while let Some((_, ch)) = iter.next() { + if !is_keylike_continue(ch) { + break; + } + self.chars = iter.clone(); + } + } + } + ExplicitId + } + ch if is_keylike_start(ch) => { + let remaining = self.chars.chars.as_str().len(); + let mut iter = self.chars.clone(); + while let Some((_, ch)) = iter.next() { + if !is_keylike_continue(ch) { + break; + } + self.chars = iter.clone(); + } + let end = start + ch.len_utf8() + (remaining - self.chars.chars.as_str().len()); + match &self.input[start..end] { + "use" => Use, + "type" => Type, + "resource" => Resource, + "func" => Func, + "u8" => U8, + "u16" => U16, + "u32" => U32, + "u64" => U64, + "s8" => S8, + "s16" => S16, + "s32" => S32, + "s64" => S64, + "float32" => Float32, + "float64" => Float64, + "char" => Char, + "handle" => Handle, + "record" => Record, + "flags" => Flags, + "variant" => Variant, + "enum" => Enum, + "union" => Union, + "bool" => Bool, + "string" => String_, + "option" => Option_, + "expected" => Expected, + "future" => Future, + "stream" => Stream, + "list" => List, + "_" => Underscore, + "as" => As, + "from" => From_, + "static" => Static, + "interface" => Interface, + "tuple" => Tuple, + "async" => Async, + "unit" => Unit, + "implements" => Implements, + _ => Id, + } + } + ch => return Err(Error::Unexpected(start, ch)), + }; + let end = match self.chars.clone().next() { + Some((i, _)) => i, + None => self.input.len(), + }; + + let start = u32::try_from(start).unwrap(); + let end = u32::try_from(end).unwrap(); + Ok(Some((Span { start, end }, token))) + } + + pub fn eat(&mut self, expected: Token) -> Result { + let mut other = self.clone(); + match other.next()? { + Some((_span, found)) if expected == found => { + *self = other; + Ok(true) + } + Some(_) => Ok(false), + None => Ok(false), + } + } + + pub fn expect(&mut self, expected: Token) -> Result { + match self.next()? { + Some((span, found)) => { + if expected == found { + Ok(span) + } else { + Err(Error::Wanted { + at: usize::try_from(span.start).unwrap(), + expected: expected.describe(), + found: found.describe(), + }) + } + } + None => Err(Error::Wanted { + at: self.input.len(), + expected: expected.describe(), + found: "eof", + }), + } + } + + pub fn expect_raw(&mut self, expected: Token) -> Result { + match self.next_raw()? { + Some((span, found)) => { + if expected == found { + Ok(span) + } else { + Err(Error::Wanted { + at: usize::try_from(span.start).unwrap(), + expected: expected.describe(), + found: found.describe(), + }) + } + } + None => Err(Error::Wanted { + at: self.input.len(), + expected: expected.describe(), + found: "eof", + }), + } + } + + fn eatc(&mut self, ch: char) -> bool { + let mut iter = self.chars.clone(); + match iter.next() { + Some((_, ch2)) if ch == ch2 => { + self.chars = iter; + true + } + _ => false, + } + } +} + +impl<'a> Iterator for CrlfFold<'a> { + type Item = (usize, char); + + fn next(&mut self) -> Option<(usize, char)> { + self.chars.next().map(|(i, c)| { + if c == '\r' { + let mut attempt = self.chars.clone(); + if let Some((_, '\n')) = attempt.next() { + self.chars = attempt; + return (i, '\n'); + } + } + (i, c) + }) + } +} + +fn detect_invalid_input(input: &str) -> Result<()> { + // Disallow specific codepoints. + let mut line = 1; + for ch in input.chars() { + match ch { + '\n' => line += 1, + '\r' | '\t' => {} + + // Bidirectional override codepoints can be used to craft source code that + // appears to have a different meaning than its actual meaning. See + // [CVE-2021-42574] for background and motivation. + // + // [CVE-2021-42574]: https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2021-42574 + '\u{202a}' | '\u{202b}' | '\u{202c}' | '\u{202d}' | '\u{202e}' | '\u{2066}' + | '\u{2067}' | '\u{2068}' | '\u{2069}' => { + bail!( + "Input contains bidirectional override codepoint {:?} at line {}", + ch.escape_unicode(), + line + ); + } + + // Disallow several characters which are deprecated or discouraged in Unicode. + // + // U+149 deprecated; see Unicode 13.0.0, sec. 7.1 Latin, Compatibility Digraphs. + // U+673 deprecated; see Unicode 13.0.0, sec. 9.2 Arabic, Additional Vowel Marks. + // U+F77 and U+F79 deprecated; see Unicode 13.0.0, sec. 13.4 Tibetan, Vowels. + // U+17A3 and U+17A4 deprecated, and U+17B4 and U+17B5 discouraged; see + // Unicode 13.0.0, sec. 16.4 Khmer, Characters Whose Use Is Discouraged. + '\u{149}' | '\u{673}' | '\u{f77}' | '\u{f79}' | '\u{17a3}' | '\u{17a4}' + | '\u{17b4}' | '\u{17b5}' => { + bail!( + "Codepoint {:?} at line {} is discouraged by Unicode", + ch.escape_unicode(), + line + ); + } + + // Disallow control codes other than the ones explicitly recognized above, + // so that viewing a wit file on a terminal doesn't have surprising side + // effects or appear to have a different meaning than its actual meaning. + ch if ch.is_control() => { + bail!("Control code '{}' at line {}", ch.escape_unicode(), line); + } + + _ => {} + } + } + + Ok(()) +} + +fn is_keylike_start(ch: char) -> bool { + // Lex any XID start, `_`, or '-'. These aren't all valid identifier chars, + // but we'll diagnose that after we've lexed the full string. + UnicodeXID::is_xid_start(ch) || ch == '_' || ch == '-' +} + +fn is_keylike_continue(ch: char) -> bool { + // Lex any XID continue (which includes `_`) or '-'. + UnicodeXID::is_xid_continue(ch) || ch == '-' +} + +pub fn validate_id(start: usize, id: &str) -> Result<(), Error> { + // Ids must be in stream-safe NFC. + if !unicode_normalization::is_nfc_stream_safe(&id) { + return Err(Error::IdNotSSNFC(start)); + } + + // IDs must have at least one part. + if id.is_empty() { + return Err(Error::IdPartEmpty(start)); + } + + // Ids consist of parts separated by '-'s. + for part in id.split("-") { + // Parts must be non-empty and start with a non-combining XID start. + match part.chars().next() { + None => return Err(Error::IdPartEmpty(start)), + Some(first) => { + // Require the first character of each part to be non-combining, + // so that if a source langauge uses `CamelCase`, they won't + // combine with the last character of the previous part. + if canonical_combining_class(first) != 0 { + return Err(Error::InvalidCharInId(start, first)); + } + + // Require the first character to be a XID start. + if !UnicodeXID::is_xid_start(first) { + return Err(Error::InvalidCharInId(start, first)); + } + + // TODO: Disallow values with 'Grapheme_Extend = Yes', to + // prevent them from combining with previous parts? + + // TODO: Disallow values with 'Grapheme_Cluster_Break = SpacingMark'? + } + }; + + // Some XID values are not valid ID part values. + for ch in part.chars() { + // Disallow uppercase and underscore, so that identifiers + // consistently use `kebab-case`, and source languages can map + // identifiers according to their own conventions (which might use + // `CamelCase` or `snake_case` or something else) without worrying + // about collisions. + if ch.is_uppercase() || ch == '_' || !UnicodeXID::is_xid_continue(ch) { + return Err(Error::InvalidCharInId(start, ch)); + } + } + } + + Ok(()) +} + +impl Token { + pub fn describe(&self) -> &'static str { + match self { + Whitespace => "whitespace", + Comment => "a comment", + Equals => "'='", + Comma => "','", + Colon => "':'", + Semicolon => "';'", + LeftParen => "'('", + RightParen => "')'", + LeftBrace => "'{'", + RightBrace => "'}'", + LessThan => "'<'", + GreaterThan => "'>'", + Use => "keyword `use`", + Type => "keyword `type`", + Resource => "keyword `resource`", + Func => "keyword `func`", + U8 => "keyword `u8`", + U16 => "keyword `u16`", + U32 => "keyword `u32`", + U64 => "keyword `u64`", + S8 => "keyword `s8`", + S16 => "keyword `s16`", + S32 => "keyword `s32`", + S64 => "keyword `s64`", + Float32 => "keyword `float32`", + Float64 => "keyword `float64`", + Char => "keyword `char`", + Handle => "keyword `handle`", + Record => "keyword `record`", + Flags => "keyword `flags`", + Variant => "keyword `variant`", + Enum => "keyword `enum`", + Union => "keyword `union`", + Bool => "keyword `bool`", + String_ => "keyword `string`", + Option_ => "keyword `option`", + Expected => "keyword `expected`", + Future => "keyword `future`", + Stream => "keyword `stream`", + List => "keyword `list`", + Underscore => "keyword `_`", + Id => "an identifier", + ExplicitId => "an '%' identifier", + RArrow => "`->`", + Star => "`*`", + As => "keyword `as`", + From_ => "keyword `from`", + Static => "keyword `static`", + Interface => "keyword `interface`", + Tuple => "keyword `tuple`", + Async => "keyword `async`", + Unit => "keyword `unit`", + Implements => "keyword `implements`", + } + } +} + +impl std::error::Error for Error {} + +impl fmt::Display for Error { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Error::Unexpected(_, ch) => write!(f, "unexpected character {:?}", ch), + Error::UnterminatedComment(_) => write!(f, "unterminated block comment"), + Error::Wanted { + expected, found, .. + } => write!(f, "expected {}, found {}", expected, found), + Error::UnterminatedString(_) => write!(f, "unterminated string literal"), + Error::NewlineInString(_) => write!(f, "newline in string literal"), + Error::InvalidCharInString(_, ch) => write!(f, "invalid character in string {:?}", ch), + Error::InvalidCharInId(_, ch) => write!(f, "invalid character in identifier {:?}", ch), + Error::IdPartEmpty(_) => write!(f, "identifiers must have characters between '-'s"), + Error::IdNotSSNFC(_) => write!(f, "identifiers must be in stream-safe NFC"), + Error::InvalidEscape(_, ch) => write!(f, "invalid escape in string {:?}", ch), + } + } +} + +pub fn rewrite_error(err: &mut anyhow::Error, file: &str, contents: &str) { + let lex = match err.downcast_mut::() { + Some(err) => err, + None => return, + }; + let pos = match lex { + Error::Unexpected(at, _) + | Error::UnterminatedComment(at) + | Error::Wanted { at, .. } + | Error::UnterminatedString(at) + | Error::NewlineInString(at) + | Error::InvalidCharInString(at, _) + | Error::InvalidCharInId(at, _) + | Error::IdNotSSNFC(at) + | Error::IdPartEmpty(at) + | Error::InvalidEscape(at, _) => *at, + }; + let msg = super::highlight_err(pos, None, file, contents, lex); + *err = anyhow::anyhow!("{}", msg); +} + +#[test] +fn test_validate_id() { + validate_id(0, "apple").unwrap(); + validate_id(0, "apple-pear").unwrap(); + validate_id(0, "apple-pear-grape").unwrap(); + validate_id(0, "garçon").unwrap(); + validate_id(0, "hühnervögel").unwrap(); + validate_id(0, "москва").unwrap(); + validate_id(0, "東京").unwrap(); + validate_id(0, "東-京").unwrap(); + validate_id(0, "garçon-hühnervögel-москва-東京").unwrap(); + validate_id(0, "garçon-hühnervögel-москва-東-京").unwrap(); + validate_id(0, "a0").unwrap(); + validate_id(0, "a").unwrap(); + validate_id(0, "a-a").unwrap(); + validate_id(0, "bool").unwrap(); + + assert!(validate_id(0, "").is_err()); + assert!(validate_id(0, "0").is_err()); + assert!(validate_id(0, "%").is_err()); + assert!(validate_id(0, "$").is_err()); + assert!(validate_id(0, "0a").is_err()); + assert!(validate_id(0, ".").is_err()); + assert!(validate_id(0, "·").is_err()); + assert!(validate_id(0, "a a").is_err()); + assert!(validate_id(0, "_").is_err()); + assert!(validate_id(0, "-").is_err()); + assert!(validate_id(0, "a-").is_err()); + assert!(validate_id(0, "-a").is_err()); + assert!(validate_id(0, "Apple").is_err()); + assert!(validate_id(0, "APPLE").is_err()); + assert!(validate_id(0, "applE").is_err()); + assert!(validate_id(0, "-apple-pear").is_err()); + assert!(validate_id(0, "apple-pear-").is_err()); + assert!(validate_id(0, "apple_pear").is_err()); + assert!(validate_id(0, "apple.pear").is_err()); + assert!(validate_id(0, "apple pear").is_err()); + assert!(validate_id(0, "apple/pear").is_err()); + assert!(validate_id(0, "apple|pear").is_err()); + assert!(validate_id(0, "apple-Pear").is_err()); + assert!(validate_id(0, "apple-0").is_err()); + assert!(validate_id(0, "()()").is_err()); + assert!(validate_id(0, "").is_err()); + assert!(validate_id(0, "*").is_err()); + assert!(validate_id(0, "apple\u{5f3}pear").is_err()); + assert!(validate_id(0, "apple\u{200c}pear").is_err()); + assert!(validate_id(0, "apple\u{200d}pear").is_err()); + assert!(validate_id(0, "apple--pear").is_err()); + assert!(validate_id(0, "_apple").is_err()); + assert!(validate_id(0, "apple_").is_err()); + assert!(validate_id(0, "_Znwj").is_err()); + assert!(validate_id(0, "__i386").is_err()); + assert!(validate_id(0, "__i386__").is_err()); + assert!(validate_id(0, "ENOENT").is_err()); + assert!(validate_id(0, "Москва").is_err()); + assert!(validate_id(0, "garçon-hühnervögel-Москва-東京").is_err()); + assert!(validate_id(0, "😼").is_err(), "non-identifier"); + assert!(validate_id(0, "\u{212b}").is_err(), "not NFC"); +} + +#[test] +fn test_tokenizer() { + fn collect(s: &str) -> Result> { + let mut t = Tokenizer::new(s)?; + let mut tokens = Vec::new(); + while let Some(token) = t.next()? { + tokens.push(token.1); + } + Ok(tokens) + } + + assert_eq!(collect("").unwrap(), vec![]); + assert_eq!(collect("_").unwrap(), vec![Token::Underscore]); + assert_eq!(collect("apple").unwrap(), vec![Token::Id]); + assert_eq!(collect("apple-pear").unwrap(), vec![Token::Id]); + assert_eq!(collect("apple--pear").unwrap(), vec![Token::Id]); + assert_eq!(collect("apple-Pear").unwrap(), vec![Token::Id]); + assert_eq!(collect("apple-pear-grape").unwrap(), vec![Token::Id]); + assert_eq!(collect("apple pear").unwrap(), vec![Token::Id, Token::Id]); + assert_eq!(collect("_a_p_p_l_e_").unwrap(), vec![Token::Id]); + assert_eq!(collect("garçon").unwrap(), vec![Token::Id]); + assert_eq!(collect("hühnervögel").unwrap(), vec![Token::Id]); + assert_eq!(collect("москва").unwrap(), vec![Token::Id]); + assert_eq!(collect("東京").unwrap(), vec![Token::Id]); + assert_eq!( + collect("garçon-hühnervögel-москва-東京").unwrap(), + vec![Token::Id] + ); + assert_eq!(collect("a0").unwrap(), vec![Token::Id]); + assert_eq!(collect("a").unwrap(), vec![Token::Id]); + assert_eq!(collect("%a").unwrap(), vec![Token::ExplicitId]); + assert_eq!(collect("%a-a").unwrap(), vec![Token::ExplicitId]); + assert_eq!(collect("%bool").unwrap(), vec![Token::ExplicitId]); + assert_eq!(collect("%").unwrap(), vec![Token::ExplicitId]); + + assert_eq!(collect("func").unwrap(), vec![Token::Func]); + assert_eq!( + collect("a: func()").unwrap(), + vec![ + Token::Id, + Token::Colon, + Token::Func, + Token::LeftParen, + Token::RightParen + ] + ); + + assert!(collect("\u{149}").is_err(), "strongly discouraged"); + assert!(collect("\u{673}").is_err(), "strongly discouraged"); + assert!(collect("\u{17a3}").is_err(), "strongly discouraged"); + assert!(collect("\u{17a4}").is_err(), "strongly discouraged"); + assert!(collect("\u{202a}").is_err(), "bidirectional override"); + assert!(collect("\u{2068}").is_err(), "bidirectional override"); + assert!(collect("\u{0}").is_err(), "control code"); + assert!(collect("\u{b}").is_err(), "control code"); + assert!(collect("\u{c}").is_err(), "control code"); + assert!(collect("\u{85}").is_err(), "control code"); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast/resolve.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast/resolve.rs new file mode 100644 index 0000000..8d9da8f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/ast/resolve.rs @@ -0,0 +1,751 @@ +use super::{Error, Item, Span, Value, ValueKind}; +use crate::*; +use anyhow::Result; +use std::collections::{HashMap, HashSet}; +use std::mem; + +#[derive(Default)] +pub struct Resolver { + type_lookup: HashMap, + types: Arena, + resource_lookup: HashMap, + resources_copied: HashMap<(String, ResourceId), ResourceId>, + types_copied: HashMap<(String, TypeId), TypeId>, + resources: Arena, + anon_types: HashMap, + functions: Vec, + globals: Vec, +} + +#[derive(PartialEq, Eq, Hash)] +enum Key { + Variant(Vec<(String, Type)>), + Record(Vec<(String, Type)>), + Flags(Vec), + Tuple(Vec), + Enum(Vec), + List(Type), + Option(Type), + Expected(Type, Type), + Union(Vec), + Future(Type), + Stream(Type, Type), +} + +impl Resolver { + pub(super) fn resolve( + &mut self, + name: &str, + fields: &[Item<'_>], + deps: &HashMap, + ) -> Result { + // First pull in any names from our dependencies + self.process_use(fields, deps)?; + // ... then register our own names + self.register_names(fields)?; + + // With all names registered we can now fully expand and translate all + // types. + for field in fields { + let t = match field { + Item::TypeDef(t) => t, + _ => continue, + }; + let id = self.type_lookup[&*t.name.name]; + let kind = self.resolve_type_def(&t.ty)?; + self.types.get_mut(id).unwrap().kind = kind; + } + + // And finally we can resolve all type references in functions/globals + // and additionally validate that types thesmelves are not recursive + let mut valid_types = HashSet::new(); + let mut visiting = HashSet::new(); + for field in fields { + match field { + Item::Value(v) => self.resolve_value(v)?, + Item::Resource(r) => self.resolve_resource(r)?, + Item::TypeDef(t) => { + self.validate_type_not_recursive( + t.name.span, + self.type_lookup[&*t.name.name], + &mut visiting, + &mut valid_types, + )?; + } + _ => continue, + } + } + + Ok(Interface { + name: name.to_string(), + module: None, + types: mem::take(&mut self.types), + type_lookup: mem::take(&mut self.type_lookup), + resources: mem::take(&mut self.resources), + resource_lookup: mem::take(&mut self.resource_lookup), + interface_lookup: Default::default(), + interfaces: Default::default(), + functions: mem::take(&mut self.functions), + globals: mem::take(&mut self.globals), + }) + } + + fn process_use<'a>( + &mut self, + fields: &[Item<'a>], + deps: &'a HashMap, + ) -> Result<()> { + for field in fields { + let u = match field { + Item::Use(u) => u, + _ => continue, + }; + let mut dep = &deps[&*u.from[0].name]; + let mut prev = &*u.from[0].name; + for name in u.from[1..].iter() { + dep = match dep.interface_lookup.get(&*name.name) { + Some(i) => &dep.interfaces[*i], + None => { + return Err(Error { + span: name.span, + msg: format!("`{}` not defined in `{}`", name.name, prev), + } + .into()) + } + }; + prev = &*name.name; + } + + let mod_name = &u.from[0]; + + match &u.names { + Some(names) => { + for name in names { + let (my_name, span) = match &name.as_ { + Some(id) => (&id.name, id.span), + None => (&name.name.name, name.name.span), + }; + let mut found = false; + + if let Some(id) = dep.resource_lookup.get(&*name.name.name) { + let resource = self.copy_resource(&mod_name.name, dep, *id); + self.define_resource(my_name, span, resource)?; + found = true; + } + + if let Some(id) = dep.type_lookup.get(&*name.name.name) { + let ty = self.copy_type_def(&mod_name.name, dep, *id); + self.define_type(my_name, span, ty)?; + found = true; + } + + if !found { + return Err(Error { + span: name.name.span, + msg: "name not defined in submodule".to_string(), + } + .into()); + } + } + } + None => { + for (id, resource) in dep.resources.iter() { + let id = self.copy_resource(&mod_name.name, dep, id); + self.define_resource(&resource.name, mod_name.span, id)?; + } + let mut names = dep.type_lookup.iter().collect::>(); + names.sort(); // produce a stable order by which to add names + for (name, id) in names { + let ty = self.copy_type_def(&mod_name.name, dep, *id); + self.define_type(name, mod_name.span, ty)?; + } + } + } + } + Ok(()) + } + + fn copy_resource(&mut self, dep_name: &str, dep: &Interface, r: ResourceId) -> ResourceId { + let resources = &mut self.resources; + *self + .resources_copied + .entry((dep_name.to_string(), r)) + .or_insert_with(|| { + let r = &dep.resources[r]; + let resource = Resource { + docs: r.docs.clone(), + name: r.name.clone(), + supertype: r.supertype.clone(), + foreign_module: Some( + r.foreign_module + .clone() + .unwrap_or_else(|| dep_name.to_string()), + ), + }; + resources.alloc(resource) + }) + } + + fn copy_type_def(&mut self, dep_name: &str, dep: &Interface, dep_id: TypeId) -> TypeId { + if let Some(id) = self.types_copied.get(&(dep_name.to_string(), dep_id)) { + return *id; + } + let ty = &dep.types[dep_id]; + + let ty = TypeDef { + docs: ty.docs.clone(), + name: ty.name.clone(), + foreign_module: Some( + ty.foreign_module + .clone() + .unwrap_or_else(|| dep_name.to_string()), + ), + kind: match &ty.kind { + TypeDefKind::Type(t) => TypeDefKind::Type(self.copy_type(dep_name, dep, *t)), + TypeDefKind::Record(r) => TypeDefKind::Record(Record { + fields: r + .fields + .iter() + .map(|field| Field { + docs: field.docs.clone(), + name: field.name.clone(), + ty: self.copy_type(dep_name, dep, field.ty), + }) + .collect(), + }), + TypeDefKind::Flags(f) => TypeDefKind::Flags(f.clone()), + TypeDefKind::Tuple(t) => TypeDefKind::Tuple(Tuple { + types: t + .types + .iter() + .map(|ty| self.copy_type(dep_name, dep, *ty)) + .collect(), + }), + TypeDefKind::Variant(v) => TypeDefKind::Variant(Variant { + cases: v + .cases + .iter() + .map(|case| Case { + docs: case.docs.clone(), + name: case.name.clone(), + ty: self.copy_type(dep_name, dep, case.ty), + }) + .collect(), + }), + TypeDefKind::Enum(e) => TypeDefKind::Enum(Enum { + cases: e.cases.clone(), + }), + TypeDefKind::List(t) => TypeDefKind::List(self.copy_type(dep_name, dep, *t)), + TypeDefKind::Option(t) => TypeDefKind::Option(self.copy_type(dep_name, dep, *t)), + TypeDefKind::Expected(e) => TypeDefKind::Expected(Expected { + ok: self.copy_type(dep_name, dep, e.ok), + err: self.copy_type(dep_name, dep, e.err), + }), + TypeDefKind::Union(u) => TypeDefKind::Union(Union { + cases: u + .cases + .iter() + .map(|c| UnionCase { + docs: c.docs.clone(), + ty: self.copy_type(dep_name, dep, c.ty), + }) + .collect(), + }), + TypeDefKind::Future(t) => TypeDefKind::Future(self.copy_type(dep_name, dep, *t)), + TypeDefKind::Stream(e) => TypeDefKind::Stream(Stream { + element: self.copy_type(dep_name, dep, e.element), + end: self.copy_type(dep_name, dep, e.end), + }), + }, + }; + let id = self.types.alloc(ty); + self.types_copied.insert((dep_name.to_string(), dep_id), id); + id + } + + fn copy_type(&mut self, dep_name: &str, dep: &Interface, ty: Type) -> Type { + match ty { + Type::Id(id) => Type::Id(self.copy_type_def(dep_name, dep, id)), + Type::Handle(id) => Type::Handle(self.copy_resource(dep_name, dep, id)), + other => other, + } + } + + fn register_names(&mut self, fields: &[Item<'_>]) -> Result<()> { + let mut values = HashSet::new(); + for field in fields { + match field { + Item::Resource(r) => { + let docs = self.docs(&r.docs); + let id = self.resources.alloc(Resource { + docs, + name: r.name.name.to_string(), + supertype: r + .supertype + .as_ref() + .map(|supertype| supertype.name.to_string()), + foreign_module: None, + }); + self.define_resource(&r.name.name, r.name.span, id)?; + let type_id = self.types.alloc(TypeDef { + docs: Docs::default(), + kind: TypeDefKind::Type(Type::Handle(id)), + name: None, + foreign_module: None, + }); + self.define_type(&r.name.name, r.name.span, type_id)?; + } + Item::TypeDef(t) => { + let docs = self.docs(&t.docs); + let id = self.types.alloc(TypeDef { + docs, + // a dummy kind is used for now which will get filled in + // later with the actual desired contents. + kind: TypeDefKind::List(Type::U8), + name: Some(t.name.name.to_string()), + foreign_module: None, + }); + self.define_type(&t.name.name, t.name.span, id)?; + } + Item::Value(f) => { + if !values.insert(&f.name.name) { + return Err(Error { + span: f.name.span, + msg: format!("{:?} defined twice", f.name.name), + } + .into()); + } + } + Item::Use(_) => {} + + Item::Interface(_) => unimplemented!(), + } + } + + Ok(()) + } + + fn define_resource(&mut self, name: &str, span: Span, id: ResourceId) -> Result<()> { + if self.resource_lookup.insert(name.to_string(), id).is_some() { + Err(Error { + span, + msg: format!("resource {:?} defined twice", name), + } + .into()) + } else { + Ok(()) + } + } + + fn define_type(&mut self, name: &str, span: Span, id: TypeId) -> Result<()> { + if self.type_lookup.insert(name.to_string(), id).is_some() { + Err(Error { + span, + msg: format!("type {:?} defined twice", name), + } + .into()) + } else { + Ok(()) + } + } + + fn resolve_type_def(&mut self, ty: &super::Type<'_>) -> Result { + Ok(match ty { + super::Type::Unit => TypeDefKind::Type(Type::Unit), + super::Type::Bool => TypeDefKind::Type(Type::Bool), + super::Type::U8 => TypeDefKind::Type(Type::U8), + super::Type::U16 => TypeDefKind::Type(Type::U16), + super::Type::U32 => TypeDefKind::Type(Type::U32), + super::Type::U64 => TypeDefKind::Type(Type::U64), + super::Type::S8 => TypeDefKind::Type(Type::S8), + super::Type::S16 => TypeDefKind::Type(Type::S16), + super::Type::S32 => TypeDefKind::Type(Type::S32), + super::Type::S64 => TypeDefKind::Type(Type::S64), + super::Type::Float32 => TypeDefKind::Type(Type::Float32), + super::Type::Float64 => TypeDefKind::Type(Type::Float64), + super::Type::Char => TypeDefKind::Type(Type::Char), + super::Type::String => TypeDefKind::Type(Type::String), + super::Type::Handle(resource) => { + let id = match self.resource_lookup.get(&*resource.name) { + Some(id) => *id, + None => { + return Err(Error { + span: resource.span, + msg: format!("no resource named `{}`", resource.name), + } + .into()) + } + }; + TypeDefKind::Type(Type::Handle(id)) + } + super::Type::Name(name) => { + let id = match self.type_lookup.get(&*name.name) { + Some(id) => *id, + None => { + return Err(Error { + span: name.span, + msg: format!("no type named `{}`", name.name), + } + .into()) + } + }; + TypeDefKind::Type(Type::Id(id)) + } + super::Type::List(list) => { + let ty = self.resolve_type(list)?; + TypeDefKind::List(ty) + } + super::Type::Record(record) => { + let fields = record + .fields + .iter() + .map(|field| { + Ok(Field { + docs: self.docs(&field.docs), + name: field.name.name.to_string(), + ty: self.resolve_type(&field.ty)?, + }) + }) + .collect::>>()?; + TypeDefKind::Record(Record { fields }) + } + super::Type::Flags(flags) => { + let flags = flags + .flags + .iter() + .map(|flag| Flag { + docs: self.docs(&flag.docs), + name: flag.name.name.to_string(), + }) + .collect::>(); + TypeDefKind::Flags(Flags { flags }) + } + super::Type::Tuple(types) => { + let types = types + .iter() + .map(|ty| self.resolve_type(ty)) + .collect::>>()?; + TypeDefKind::Tuple(Tuple { types }) + } + super::Type::Variant(variant) => { + if variant.cases.is_empty() { + return Err(Error { + span: variant.span, + msg: "empty variant".to_string(), + } + .into()); + } + let cases = variant + .cases + .iter() + .map(|case| { + Ok(Case { + docs: self.docs(&case.docs), + name: case.name.name.to_string(), + ty: match &case.ty { + Some(ty) => self.resolve_type(ty)?, + None => Type::Unit, + }, + }) + }) + .collect::>>()?; + TypeDefKind::Variant(Variant { cases }) + } + super::Type::Enum(e) => { + if e.cases.is_empty() { + return Err(Error { + span: e.span, + msg: "empty enum".to_string(), + } + .into()); + } + let cases = e + .cases + .iter() + .map(|case| { + Ok(EnumCase { + docs: self.docs(&case.docs), + name: case.name.name.to_string(), + }) + }) + .collect::>>()?; + TypeDefKind::Enum(Enum { cases }) + } + super::Type::Option(ty) => TypeDefKind::Option(self.resolve_type(ty)?), + super::Type::Expected(e) => TypeDefKind::Expected(Expected { + ok: self.resolve_type(&e.ok)?, + err: self.resolve_type(&e.err)?, + }), + super::Type::Union(e) => { + if e.cases.is_empty() { + return Err(Error { + span: e.span, + msg: "empty union".to_string(), + } + .into()); + } + let cases = e + .cases + .iter() + .map(|case| { + Ok(UnionCase { + docs: self.docs(&case.docs), + ty: self.resolve_type(&case.ty)?, + }) + }) + .collect::>>()?; + TypeDefKind::Union(Union { cases }) + } + super::Type::Future(t) => TypeDefKind::Future(self.resolve_type(t)?), + super::Type::Stream(s) => TypeDefKind::Stream(Stream { + element: self.resolve_type(&s.element)?, + end: self.resolve_type(&s.end)?, + }), + }) + } + + fn resolve_type(&mut self, ty: &super::Type<'_>) -> Result { + let kind = self.resolve_type_def(ty)?; + Ok(self.anon_type_def(TypeDef { + kind, + name: None, + docs: Docs::default(), + foreign_module: None, + })) + } + + fn anon_type_def(&mut self, ty: TypeDef) -> Type { + let key = match &ty.kind { + TypeDefKind::Type(t) => return *t, + TypeDefKind::Variant(v) => Key::Variant( + v.cases + .iter() + .map(|case| (case.name.clone(), case.ty)) + .collect::>(), + ), + TypeDefKind::Record(r) => Key::Record( + r.fields + .iter() + .map(|case| (case.name.clone(), case.ty)) + .collect::>(), + ), + TypeDefKind::Flags(r) => { + Key::Flags(r.flags.iter().map(|f| f.name.clone()).collect::>()) + } + TypeDefKind::Tuple(t) => Key::Tuple(t.types.clone()), + TypeDefKind::Enum(r) => { + Key::Enum(r.cases.iter().map(|f| f.name.clone()).collect::>()) + } + TypeDefKind::List(ty) => Key::List(*ty), + TypeDefKind::Option(t) => Key::Option(*t), + TypeDefKind::Expected(e) => Key::Expected(e.ok, e.err), + TypeDefKind::Union(u) => Key::Union(u.cases.iter().map(|c| c.ty).collect()), + TypeDefKind::Future(ty) => Key::Future(*ty), + TypeDefKind::Stream(s) => Key::Stream(s.element, s.end), + }; + let types = &mut self.types; + let id = self + .anon_types + .entry(key) + .or_insert_with(|| types.alloc(ty)); + Type::Id(*id) + } + + fn docs(&mut self, doc: &super::Docs<'_>) -> Docs { + let mut docs = None; + for doc in doc.docs.iter() { + // Comments which are not doc-comments are silently ignored + if let Some(doc) = doc.strip_prefix("///") { + let docs = docs.get_or_insert_with(String::new); + docs.push_str(doc.trim_start_matches('/').trim()); + docs.push('\n'); + } else if let Some(doc) = doc.strip_prefix("/**") { + let docs = docs.get_or_insert_with(String::new); + assert!(doc.ends_with("*/")); + for line in doc[..doc.len() - 2].lines() { + docs.push_str(line); + docs.push('\n'); + } + } + } + Docs { contents: docs } + } + + fn resolve_value(&mut self, value: &Value<'_>) -> Result<()> { + let docs = self.docs(&value.docs); + match &value.kind { + ValueKind::Function { + is_async, + params, + result, + } => { + let params = params + .iter() + .map(|(name, ty)| Ok((name.name.to_string(), self.resolve_type(ty)?))) + .collect::>()?; + let result = self.resolve_type(result)?; + self.functions.push(Function { + docs, + name: value.name.name.to_string(), + kind: FunctionKind::Freestanding, + params, + result, + is_async: *is_async, + }); + } + ValueKind::Global(ty) => { + let ty = self.resolve_type(ty)?; + self.globals.push(Global { + docs, + name: value.name.name.to_string(), + ty, + }); + } + } + Ok(()) + } + + fn resolve_resource(&mut self, resource: &super::Resource<'_>) -> Result<()> { + let mut names = HashSet::new(); + let id = self.resource_lookup[&*resource.name.name]; + for (statik, value) in resource.values.iter() { + let (is_async, params, result) = match &value.kind { + ValueKind::Function { + is_async, + params, + result, + } => (*is_async, params, result), + ValueKind::Global(_) => { + return Err(Error { + span: value.name.span, + msg: "globals not allowed in resources".to_string(), + } + .into()); + } + }; + if !names.insert(&value.name.name) { + return Err(Error { + span: value.name.span, + msg: format!("{:?} defined twice in this resource", value.name.name), + } + .into()); + } + let docs = self.docs(&value.docs); + let mut params = params + .iter() + .map(|(name, ty)| Ok((name.name.to_string(), self.resolve_type(ty)?))) + .collect::>>()?; + let result = self.resolve_type(result)?; + let kind = if *statik { + FunctionKind::Static { + resource: id, + name: value.name.name.to_string(), + } + } else { + params.insert(0, ("self".to_string(), Type::Handle(id))); + FunctionKind::Method { + resource: id, + name: value.name.name.to_string(), + } + }; + self.functions.push(Function { + is_async, + docs, + name: format!("{}::{}", resource.name.name, value.name.name), + kind, + params, + result, + }); + } + Ok(()) + } + + fn validate_type_not_recursive( + &self, + span: Span, + ty: TypeId, + visiting: &mut HashSet, + valid: &mut HashSet, + ) -> Result<()> { + if valid.contains(&ty) { + return Ok(()); + } + if !visiting.insert(ty) { + return Err(Error { + span, + msg: "type can recursively refer to itself".to_string(), + } + .into()); + } + + match &self.types[ty].kind { + TypeDefKind::List(Type::Id(id)) | TypeDefKind::Type(Type::Id(id)) => { + self.validate_type_not_recursive(span, *id, visiting, valid)? + } + TypeDefKind::Variant(v) => { + for case in v.cases.iter() { + if let Type::Id(id) = case.ty { + self.validate_type_not_recursive(span, id, visiting, valid)?; + } + } + } + TypeDefKind::Record(r) => { + for case in r.fields.iter() { + if let Type::Id(id) = case.ty { + self.validate_type_not_recursive(span, id, visiting, valid)?; + } + } + } + TypeDefKind::Tuple(t) => { + for ty in t.types.iter() { + if let Type::Id(id) = *ty { + self.validate_type_not_recursive(span, id, visiting, valid)?; + } + } + } + + TypeDefKind::Option(t) => { + if let Type::Id(id) = *t { + self.validate_type_not_recursive(span, id, visiting, valid)? + } + } + TypeDefKind::Expected(e) => { + if let Type::Id(id) = e.ok { + self.validate_type_not_recursive(span, id, visiting, valid)? + } + if let Type::Id(id) = e.err { + self.validate_type_not_recursive(span, id, visiting, valid)? + } + } + TypeDefKind::Future(t) => { + if let Type::Id(id) = *t { + self.validate_type_not_recursive(span, id, visiting, valid)? + } + } + TypeDefKind::Stream(s) => { + if let Type::Id(id) = s.element { + self.validate_type_not_recursive(span, id, visiting, valid)? + } + if let Type::Id(id) = s.end { + self.validate_type_not_recursive(span, id, visiting, valid)? + } + } + TypeDefKind::Union(u) => { + for c in u.cases.iter() { + if let Type::Id(id) = c.ty { + self.validate_type_not_recursive(span, id, visiting, valid)? + } + } + } + + TypeDefKind::Flags(_) + | TypeDefKind::List(_) + | TypeDefKind::Type(_) + | TypeDefKind::Enum(_) => {} + } + + valid.insert(ty); + visiting.remove(&ty); + Ok(()) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/lib.rs new file mode 100644 index 0000000..8ec4246 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/lib.rs @@ -0,0 +1,524 @@ +use anyhow::{anyhow, bail, Context, Result}; +use id_arena::{Arena, Id}; +use pulldown_cmark::{CodeBlockKind, CowStr, Event, Options, Parser, Tag}; +use std::collections::{HashMap, HashSet}; +use std::fs; +use std::path::{Path, PathBuf}; + +pub mod abi; +mod ast; +mod sizealign; +pub use sizealign::*; + +/// Checks if the given string is a legal identifier in wit. +pub fn validate_id(s: &str) -> Result<()> { + ast::validate_id(0, s)?; + Ok(()) +} + +#[derive(Debug, Default)] +pub struct Interface { + pub name: String, + /// The module name to use for bindings generation. + /// + /// If `None`, then the interface name will be used. + /// + /// If `Some`, then this value is used to format an export + /// name of `#` for exports or an import module + /// name of `` for imports. + pub module: Option, + pub types: Arena, + pub type_lookup: HashMap, + pub resources: Arena, + pub resource_lookup: HashMap, + pub interfaces: Arena, + pub interface_lookup: HashMap, + pub functions: Vec, + pub globals: Vec, +} + +pub type TypeId = Id; +pub type ResourceId = Id; +pub type InterfaceId = Id; + +#[derive(Debug)] +pub struct TypeDef { + pub docs: Docs, + pub kind: TypeDefKind, + pub name: Option, + /// `None` if this type is originally declared in this instance or + /// otherwise `Some` if it was originally defined in a different module. + pub foreign_module: Option, +} + +#[derive(Debug)] +pub enum TypeDefKind { + Record(Record), + Flags(Flags), + Tuple(Tuple), + Variant(Variant), + Enum(Enum), + Option(Type), + Expected(Expected), + Union(Union), + List(Type), + Future(Type), + Stream(Stream), + Type(Type), +} + +#[derive(Debug, PartialEq, Eq, Hash, Copy, Clone)] +pub enum Type { + Unit, + Bool, + U8, + U16, + U32, + U64, + S8, + S16, + S32, + S64, + Float32, + Float64, + Char, + String, + Handle(ResourceId), + Id(TypeId), +} + +#[derive(PartialEq, Debug, Copy, Clone)] +pub enum Int { + U8, + U16, + U32, + U64, +} + +#[derive(Debug)] +pub struct Record { + pub fields: Vec, +} + +#[derive(Debug)] +pub struct Field { + pub docs: Docs, + pub name: String, + pub ty: Type, +} + +#[derive(Debug, Clone)] +pub struct Flags { + pub flags: Vec, +} + +#[derive(Debug, Clone)] +pub struct Flag { + pub docs: Docs, + pub name: String, +} + +#[derive(Debug)] +pub enum FlagsRepr { + U8, + U16, + U32(usize), +} + +impl Flags { + pub fn repr(&self) -> FlagsRepr { + match self.flags.len() { + n if n <= 8 => FlagsRepr::U8, + n if n <= 16 => FlagsRepr::U16, + n => FlagsRepr::U32(sizealign::align_to(n, 32) / 32), + } + } +} + +impl FlagsRepr { + pub fn count(&self) -> usize { + match self { + FlagsRepr::U8 => 1, + FlagsRepr::U16 => 1, + FlagsRepr::U32(n) => *n, + } + } +} + +#[derive(Debug, Clone)] +pub struct Tuple { + pub types: Vec, +} + +#[derive(Debug)] +pub struct Variant { + pub cases: Vec, +} + +#[derive(Debug)] +pub struct Case { + pub docs: Docs, + pub name: String, + pub ty: Type, +} + +impl Variant { + pub fn tag(&self) -> Int { + match self.cases.len() { + n if n <= u8::max_value() as usize => Int::U8, + n if n <= u16::max_value() as usize => Int::U16, + n if n <= u32::max_value() as usize => Int::U32, + _ => panic!("too many cases to fit in a repr"), + } + } +} + +#[derive(Debug)] +pub struct Enum { + pub cases: Vec, +} + +#[derive(Debug, Clone)] +pub struct EnumCase { + pub docs: Docs, + pub name: String, +} + +impl Enum { + pub fn tag(&self) -> Int { + match self.cases.len() { + n if n <= u8::max_value() as usize => Int::U8, + n if n <= u16::max_value() as usize => Int::U16, + n if n <= u32::max_value() as usize => Int::U32, + _ => panic!("too many cases to fit in a repr"), + } + } +} + +#[derive(Debug)] +pub struct Expected { + pub ok: Type, + pub err: Type, +} + +#[derive(Debug)] +pub struct Union { + pub cases: Vec, +} + +#[derive(Debug, Clone)] +pub struct UnionCase { + pub docs: Docs, + pub ty: Type, +} + +impl Union { + pub fn tag(&self) -> Int { + match self.cases.len() { + n if n <= u8::max_value() as usize => Int::U8, + n if n <= u16::max_value() as usize => Int::U16, + n if n <= u32::max_value() as usize => Int::U32, + _ => panic!("too many cases to fit in a repr"), + } + } +} + +#[derive(Debug)] +pub struct Stream { + pub element: Type, + pub end: Type, +} + +#[derive(Clone, Default, Debug)] +pub struct Docs { + pub contents: Option, +} + +#[derive(Debug)] +pub struct Resource { + pub docs: Docs, + pub name: String, + pub supertype: Option, + /// `None` if this resource is defined within the containing instance, + /// otherwise `Some` if it's defined in an instance named here. + pub foreign_module: Option, +} + +#[derive(Debug)] +pub struct Global { + pub docs: Docs, + pub name: String, + pub ty: Type, +} + +#[derive(Debug)] +pub struct Function { + pub is_async: bool, + pub docs: Docs, + pub name: String, + pub kind: FunctionKind, + pub params: Vec<(String, Type)>, + pub result: Type, +} + +#[derive(Debug)] +pub enum FunctionKind { + Freestanding, + Static { resource: ResourceId, name: String }, + Method { resource: ResourceId, name: String }, +} + +impl Function { + pub fn item_name(&self) -> &str { + match &self.kind { + FunctionKind::Freestanding => &self.name, + FunctionKind::Static { name, .. } => name, + FunctionKind::Method { name, .. } => name, + } + } +} + +fn unwrap_md(contents: &str) -> String { + let mut wit = String::new(); + let mut last_pos = 0; + let mut in_wit_code_block = false; + Parser::new_ext(contents, Options::empty()) + .into_offset_iter() + .for_each(|(event, range)| match (event, range) { + (Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => { + in_wit_code_block = true; + } + (Event::Text(text), range) if in_wit_code_block => { + // Ensure that offsets are correct by inserting newlines to + // cover the Markdown content outside of wit code blocks. + for _ in contents[last_pos..range.start].lines() { + wit.push_str("\n"); + } + wit.push_str(&text); + last_pos = range.end; + } + (Event::End(Tag::CodeBlock(CodeBlockKind::Fenced(CowStr::Borrowed("wit")))), _) => { + in_wit_code_block = false; + } + _ => {} + }); + wit +} + +impl Interface { + pub fn parse(name: &str, input: &str) -> Result { + Interface::parse_with(name, input, |f| { + Err(anyhow!("cannot load submodule `{}`", f)) + }) + } + + pub fn parse_file(path: impl AsRef) -> Result { + let path = path.as_ref(); + let parent = path.parent().unwrap(); + let contents = std::fs::read_to_string(&path) + .with_context(|| format!("failed to read: {}", path.display()))?; + Interface::parse_with(path, &contents, |path| load_fs(parent, path)) + } + + pub fn parse_with( + filename: impl AsRef, + contents: &str, + mut load: impl FnMut(&str) -> Result<(PathBuf, String)>, + ) -> Result { + Interface::_parse_with( + filename.as_ref(), + contents, + &mut load, + &mut HashSet::new(), + &mut HashMap::new(), + ) + } + + fn _parse_with( + filename: &Path, + contents: &str, + load: &mut dyn FnMut(&str) -> Result<(PathBuf, String)>, + visiting: &mut HashSet, + map: &mut HashMap, + ) -> Result { + let mut name = filename.file_stem().unwrap(); + let mut contents = contents; + + // If we have a ".md" file, it's a wit file wrapped in a markdown file; + // parse the markdown to extract the `wit` code blocks. + let md_contents; + if filename.extension().and_then(|s| s.to_str()) == Some("md") { + md_contents = unwrap_md(contents); + contents = &md_contents[..]; + + // Also strip the inner ".wit" extension. + name = Path::new(name).file_stem().unwrap(); + } + + // Parse the `contents `into an AST + let ast = match ast::Ast::parse(contents) { + Ok(ast) => ast, + Err(mut e) => { + let file = filename.display().to_string(); + ast::rewrite_error(&mut e, &file, contents); + return Err(e); + } + }; + + // Load up any modules into our `map` that have not yet been parsed. + if !visiting.insert(filename.to_path_buf()) { + bail!("file `{}` recursively imports itself", filename.display()) + } + for item in ast.items.iter() { + let u = match item { + ast::Item::Use(u) => u, + _ => continue, + }; + if map.contains_key(&*u.from[0].name) { + continue; + } + let (filename, contents) = load(&u.from[0].name) + // TODO: insert context here about `u.name.span` and `filename` + ?; + let instance = Interface::_parse_with(&filename, &contents, load, visiting, map)?; + map.insert(u.from[0].name.to_string(), instance); + } + visiting.remove(filename); + + // and finally resolve everything into our final instance + match ast.resolve(name.to_str().unwrap(), map) { + Ok(i) => Ok(i), + Err(mut e) => { + let file = filename.display().to_string(); + ast::rewrite_error(&mut e, &file, contents); + Err(e) + } + } + } + + pub fn topological_types(&self) -> Vec { + let mut ret = Vec::new(); + let mut visited = HashSet::new(); + for (id, _) in self.types.iter() { + self.topo_visit(id, &mut ret, &mut visited); + } + ret + } + + fn topo_visit(&self, id: TypeId, list: &mut Vec, visited: &mut HashSet) { + if !visited.insert(id) { + return; + } + match &self.types[id].kind { + TypeDefKind::Flags(_) | TypeDefKind::Enum(_) => {} + TypeDefKind::Type(t) | TypeDefKind::List(t) => self.topo_visit_ty(t, list, visited), + TypeDefKind::Record(r) => { + for f in r.fields.iter() { + self.topo_visit_ty(&f.ty, list, visited); + } + } + TypeDefKind::Tuple(t) => { + for t in t.types.iter() { + self.topo_visit_ty(t, list, visited); + } + } + TypeDefKind::Variant(v) => { + for v in v.cases.iter() { + self.topo_visit_ty(&v.ty, list, visited); + } + } + TypeDefKind::Option(ty) => self.topo_visit_ty(ty, list, visited), + TypeDefKind::Expected(e) => { + self.topo_visit_ty(&e.ok, list, visited); + self.topo_visit_ty(&e.err, list, visited); + } + TypeDefKind::Union(u) => { + for t in u.cases.iter() { + self.topo_visit_ty(&t.ty, list, visited); + } + } + TypeDefKind::Future(ty) => { + self.topo_visit_ty(ty, list, visited); + } + TypeDefKind::Stream(s) => { + self.topo_visit_ty(&s.element, list, visited); + self.topo_visit_ty(&s.end, list, visited); + } + } + list.push(id); + } + + fn topo_visit_ty(&self, ty: &Type, list: &mut Vec, visited: &mut HashSet) { + if let Type::Id(id) = ty { + self.topo_visit(*id, list, visited); + } + } + + pub fn all_bits_valid(&self, ty: &Type) -> bool { + match ty { + Type::Unit + | Type::U8 + | Type::S8 + | Type::U16 + | Type::S16 + | Type::U32 + | Type::S32 + | Type::U64 + | Type::S64 + | Type::Float32 + | Type::Float64 => true, + + Type::Bool | Type::Char | Type::Handle(_) | Type::String => false, + + Type::Id(id) => match &self.types[*id].kind { + TypeDefKind::List(_) + | TypeDefKind::Variant(_) + | TypeDefKind::Enum(_) + | TypeDefKind::Option(_) + | TypeDefKind::Expected(_) + | TypeDefKind::Future(_) + | TypeDefKind::Stream(_) + | TypeDefKind::Union(_) => false, + TypeDefKind::Type(t) => self.all_bits_valid(t), + TypeDefKind::Record(r) => r.fields.iter().all(|f| self.all_bits_valid(&f.ty)), + TypeDefKind::Tuple(t) => t.types.iter().all(|t| self.all_bits_valid(t)), + + // FIXME: this could perhaps be `true` for multiples-of-32 but + // seems better to probably leave this as unconditionally + // `false` for now, may want to reconsider later? + TypeDefKind::Flags(_) => false, + }, + } + } + + pub fn get_variant(&self, ty: &Type) -> Option<&Variant> { + if let Type::Id(id) = ty { + match &self.types[*id].kind { + TypeDefKind::Variant(v) => Some(v), + _ => None, + } + } else { + None + } + } +} + +fn load_fs(root: &Path, name: &str) -> Result<(PathBuf, String)> { + let wit = root.join(name).with_extension("wit"); + + // Attempt to read a ".wit" file. + match fs::read_to_string(&wit) { + Ok(contents) => Ok((wit, contents)), + + // If no such file was found, attempt to read a ".wit.md" file. + Err(err) if err.kind() == std::io::ErrorKind::NotFound => { + let wit_md = wit.with_extension("wit.md"); + match fs::read_to_string(&wit_md) { + Ok(contents) => Ok((wit_md, contents)), + Err(_err) => Err(err.into()), + } + } + + Err(err) => return Err(err.into()), + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/sizealign.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/sizealign.rs new file mode 100644 index 0000000..be39579 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/src/sizealign.rs @@ -0,0 +1,121 @@ +use crate::{FlagsRepr, Int, Interface, Type, TypeDef, TypeDefKind}; + +#[derive(Default)] +pub struct SizeAlign { + map: Vec<(usize, usize)>, +} + +impl SizeAlign { + pub fn fill(&mut self, iface: &Interface) { + self.map = vec![(0, 0); iface.types.len()]; + for ty in iface.topological_types() { + let pair = self.calculate(&iface.types[ty]); + self.map[ty.index()] = pair; + } + } + + fn calculate(&self, ty: &TypeDef) -> (usize, usize) { + match &ty.kind { + TypeDefKind::Type(t) => (self.size(t), self.align(t)), + TypeDefKind::List(_) => (8, 4), + TypeDefKind::Record(r) => self.record(r.fields.iter().map(|f| &f.ty)), + TypeDefKind::Tuple(t) => self.record(t.types.iter()), + TypeDefKind::Flags(f) => match f.repr() { + FlagsRepr::U8 => (1, 1), + FlagsRepr::U16 => (2, 2), + FlagsRepr::U32(n) => (n * 4, 4), + }, + TypeDefKind::Variant(v) => self.variant(v.tag(), v.cases.iter().map(|c| &c.ty)), + TypeDefKind::Enum(e) => self.variant(e.tag(), []), + TypeDefKind::Option(t) => self.variant(Int::U8, [&Type::Unit, t]), + TypeDefKind::Expected(e) => self.variant(Int::U8, [&e.ok, &e.err]), + TypeDefKind::Union(u) => self.variant(u.tag(), u.cases.iter().map(|c| &c.ty)), + // A future is represented as an index. + TypeDefKind::Future(_) => (4, 4), + // A stream is represented as an index. + TypeDefKind::Stream(_) => (4, 4), + } + } + + pub fn size(&self, ty: &Type) -> usize { + match ty { + Type::Unit => 0, + Type::Bool | Type::U8 | Type::S8 => 1, + Type::U16 | Type::S16 => 2, + Type::U32 | Type::S32 | Type::Float32 | Type::Char | Type::Handle(_) => 4, + Type::U64 | Type::S64 | Type::Float64 | Type::String => 8, + Type::Id(id) => self.map[id.index()].0, + } + } + + pub fn align(&self, ty: &Type) -> usize { + match ty { + Type::Unit | Type::Bool | Type::U8 | Type::S8 => 1, + Type::U16 | Type::S16 => 2, + Type::U32 | Type::S32 | Type::Float32 | Type::Char | Type::Handle(_) | Type::String => { + 4 + } + Type::U64 | Type::S64 | Type::Float64 => 8, + Type::Id(id) => self.map[id.index()].1, + } + } + + pub fn field_offsets<'a>(&self, types: impl IntoIterator) -> Vec { + let mut cur = 0; + types + .into_iter() + .map(|ty| { + let ret = align_to(cur, self.align(ty)); + cur = ret + self.size(ty); + ret + }) + .collect() + } + + pub fn payload_offset<'a>(&self, tag: Int, cases: impl IntoIterator) -> usize { + let mut max_align = 1; + for ty in cases { + max_align = max_align.max(self.align(ty)); + } + let tag_size = int_size_align(tag).0; + align_to(tag_size, max_align) + } + + pub fn record<'a>(&self, types: impl Iterator) -> (usize, usize) { + let mut size = 0; + let mut align = 1; + for ty in types { + let field_size = self.size(ty); + let field_align = self.align(ty); + size = align_to(size, field_align) + field_size; + align = align.max(field_align); + } + (align_to(size, align), align) + } + + fn variant<'a>(&self, tag: Int, types: impl IntoIterator) -> (usize, usize) { + let (discrim_size, discrim_align) = int_size_align(tag); + let mut size = discrim_size; + let mut align = discrim_align; + for ty in types { + let case_size = self.size(ty); + let case_align = self.align(ty); + align = align.max(case_align); + size = size.max(align_to(discrim_size, case_align) + case_size); + } + (size, align) + } +} + +fn int_size_align(i: Int) -> (usize, usize) { + match i { + Int::U8 => (1, 1), + Int::U16 => (2, 2), + Int::U32 => (4, 4), + Int::U64 => (8, 8), + } +} + +pub(crate) fn align_to(val: usize, align: usize) -> usize { + (val + align - 1) & !(align - 1) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/all.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/all.rs new file mode 100644 index 0000000..2e3af49 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/all.rs @@ -0,0 +1,344 @@ +//! You can run this test suite with: +//! +//! cargo test --test all +//! +//! An argument can be passed as well to filter, based on filename, which test +//! to run +//! +//! cargo test --test all foo.wit + +use anyhow::{bail, Context, Result}; +use rayon::prelude::*; +use serde::Serialize; +use std::env; +use std::ffi::OsStr; +use std::fs; +use std::io; +use std::path::{Path, PathBuf}; +use std::str; +use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; +use wit_parser::*; + +fn main() { + let tests = find_tests(); + let filter = std::env::args().nth(1); + + let tests = tests + .par_iter() + .filter_map(|test| { + if let Some(filter) = &filter { + if let Some(s) = test.to_str() { + if !s.contains(filter) { + return None; + } + } + } + let contents = fs::read(test).unwrap(); + Some((test, contents)) + }) + .collect::>(); + + println!("running {} test files\n", tests.len()); + + let ntests = AtomicUsize::new(0); + let errors = tests + .par_iter() + .filter_map(|(test, contents)| { + Runner { ntests: &ntests } + .run(test, contents) + .context(format!("test {:?} failed", test)) + .err() + }) + .collect::>(); + + if !errors.is_empty() { + for msg in errors.iter() { + eprintln!("{:?}", msg); + } + + panic!("{} tests failed", errors.len()) + } + + println!( + "test result: ok. {} directives passed\n", + ntests.load(SeqCst) + ); +} + +/// Recursively finds all tests in a whitelisted set of directories which we +/// then load up and test in parallel. +fn find_tests() -> Vec { + let mut tests = Vec::new(); + find_tests("tests/ui".as_ref(), &mut tests); + tests.sort(); + return tests; + + fn find_tests(path: &Path, tests: &mut Vec) { + for f in path.read_dir().unwrap() { + let f = f.unwrap(); + if f.file_type().unwrap().is_dir() { + find_tests(&f.path(), tests); + continue; + } + + match f.path().extension().and_then(|s| s.to_str()) { + Some("md") => {} + Some("wit") => {} + _ => continue, + } + tests.push(f.path()); + } + } +} + +struct Runner<'a> { + ntests: &'a AtomicUsize, +} + +impl Runner<'_> { + fn run(&mut self, test: &Path, contents: &[u8]) -> Result<()> { + let contents = str::from_utf8(contents)?; + + let result = Interface::parse_file(test); + + let result = if contents.contains("// parse-fail") { + match result { + Ok(_) => bail!("expected test to not parse but it did"), + Err(mut e) => { + if let Some(err) = e.downcast_mut::() { + *err = io::Error::new( + io::ErrorKind::Other, + "some generic platform-agnostic error message", + ); + } + normalize(test, &format!("{:?}", e)) + } + } + } else { + let instance = result?; + to_json(&instance) + }; + + // "foo.wit" => "foo.wit.result" + // "foo.wit.md" => "foo.wit.md.result" + let result_file = if test.extension() == Some(OsStr::new("md")) + && test + .file_stem() + .and_then(|path| Path::new(path).extension()) + == Some(OsStr::new("wit")) + { + test.with_extension("md.result") + } else { + test.with_extension("wit.result") + }; + if env::var_os("BLESS").is_some() { + fs::write(&result_file, result)?; + } else { + let expected = fs::read_to_string(&result_file).context(format!( + "failed to read test expectation file {:?}\nthis can be fixed with BLESS=1", + result_file + ))?; + let expected = normalize(test, &expected); + if expected != result { + bail!( + "failed test: expected `{:?}` but found `{:?}`", + expected, + result + ); + } + } + self.bump_ntests(); + return Ok(()); + + fn normalize(test: &Path, s: &str) -> String { + s.replace( + &test.display().to_string(), + &test.display().to_string().replace("\\", "/"), + ) + .replace("\\parse-fail\\", "/parse-fail/") + .replace("\r\n", "\n") + } + } + + fn bump_ntests(&self) { + self.ntests.fetch_add(1, SeqCst); + } +} + +fn to_json(i: &Interface) -> String { + #[derive(Serialize)] + struct Interface { + #[serde(skip_serializing_if = "Vec::is_empty")] + resources: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + types: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + functions: Vec, + #[serde(skip_serializing_if = "Vec::is_empty")] + globals: Vec, + } + + #[derive(Serialize)] + struct Resource { + name: String, + #[serde(skip_serializing_if = "Option::is_none")] + supertype: Option, + #[serde(skip_serializing_if = "Option::is_none")] + foreign_module: Option, + } + + #[derive(Serialize)] + struct TypeDef { + idx: usize, + #[serde(skip_serializing_if = "Option::is_none")] + name: Option, + #[serde(flatten)] + ty: Type, + #[serde(skip_serializing_if = "Option::is_none")] + foreign_module: Option, + } + + #[derive(Serialize)] + #[serde(rename_all = "kebab-case")] + enum Type { + Primitive(String), + Record { fields: Vec<(String, String)> }, + Flags { flags: Vec }, + Enum { cases: Vec }, + Variant { cases: Vec<(String, String)> }, + Tuple { types: Vec }, + Option(String), + Expected { ok: String, err: String }, + Future(String), + Stream { element: String, end: String }, + List(String), + Union { cases: Vec }, + } + + #[derive(Serialize)] + struct Function { + name: String, + #[serde(rename = "async", skip_serializing_if = "Option::is_none")] + is_async: Option, + params: Vec, + result: String, + } + + #[derive(Serialize)] + struct Global { + name: String, + ty: String, + } + + let resources = i + .resources + .iter() + .map(|(_, r)| Resource { + name: r.name.clone(), + supertype: r.supertype.as_ref().map(|supertype| supertype.clone()), + foreign_module: r.foreign_module.clone(), + }) + .collect::>(); + + let types = i + .types + .iter() + .map(|(i, r)| TypeDef { + idx: i.index(), + name: r.name.clone(), + ty: translate_typedef(r), + foreign_module: r.foreign_module.clone(), + }) + .collect::>(); + let functions = i + .functions + .iter() + .map(|f| Function { + name: f.name.clone(), + is_async: if f.is_async { Some(f.is_async) } else { None }, + params: f.params.iter().map(|(_, ty)| translate_type(ty)).collect(), + result: translate_type(&f.result), + }) + .collect::>(); + let globals = i + .globals + .iter() + .map(|g| Global { + name: g.name.clone(), + ty: translate_type(&g.ty), + }) + .collect::>(); + + let iface = Interface { + resources, + types, + functions, + globals, + }; + return serde_json::to_string_pretty(&iface).unwrap(); + + fn translate_typedef(ty: &wit_parser::TypeDef) -> Type { + match &ty.kind { + TypeDefKind::Type(t) => Type::Primitive(translate_type(t)), + TypeDefKind::Record(r) => Type::Record { + fields: r + .fields + .iter() + .map(|f| (f.name.clone(), translate_type(&f.ty))) + .collect(), + }, + TypeDefKind::Tuple(t) => Type::Tuple { + types: t.types.iter().map(|ty| translate_type(ty)).collect(), + }, + TypeDefKind::Flags(r) => Type::Flags { + flags: r.flags.iter().map(|f| f.name.clone()).collect(), + }, + TypeDefKind::Enum(r) => Type::Enum { + cases: r.cases.iter().map(|f| f.name.clone()).collect(), + }, + TypeDefKind::Variant(v) => Type::Variant { + cases: v + .cases + .iter() + .map(|f| (f.name.clone(), translate_type(&f.ty))) + .collect(), + }, + TypeDefKind::Option(t) => Type::Option(translate_type(t)), + TypeDefKind::Expected(e) => Type::Expected { + ok: translate_type(&e.ok), + err: translate_type(&e.err), + }, + TypeDefKind::Future(t) => Type::Future(translate_type(t)), + TypeDefKind::Stream(s) => Type::Stream { + element: translate_type(&s.element), + end: translate_type(&s.end), + }, + TypeDefKind::List(ty) => Type::List(translate_type(ty)), + TypeDefKind::Union(u) => Type::Union { + cases: u.cases.iter().map(|c| translate_type(&c.ty)).collect(), + }, + } + } + + fn translate_type(ty: &wit_parser::Type) -> String { + use wit_parser::Type; + match ty { + Type::Unit => format!("unit"), + Type::Bool => format!("bool"), + Type::U8 => format!("u8"), + Type::U16 => format!("u16"), + Type::U32 => format!("u32"), + Type::U64 => format!("u64"), + Type::S8 => format!("s8"), + Type::S16 => format!("s16"), + Type::S32 => format!("s32"), + Type::S64 => format!("s64"), + Type::Float32 => format!("float32"), + Type::Float64 => format!("float64"), + Type::Char => format!("char"), + Type::String => format!("string"), + Type::Handle(resource) => format!("handle-{}", resource.index()), + Type::Id(id) => format!("type-{}", id.index()), + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/async.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/async.wit new file mode 100644 index 0000000..70ec80d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/async.wit @@ -0,0 +1,9 @@ +a: async func() +b: async func(x: s32) +c: async func() -> u32 + +resource y { + a: async func() + b: async func(x: s32) + c: async func() -> u32 +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/async.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/async.wit.result new file mode 100644 index 0000000..3d97f8d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/async.wit.result @@ -0,0 +1,60 @@ +{ + "resources": [ + { + "name": "y" + } + ], + "types": [ + { + "idx": 0, + "primitive": "handle-0" + } + ], + "functions": [ + { + "name": "a", + "async": true, + "params": [], + "result": "unit" + }, + { + "name": "b", + "async": true, + "params": [ + "s32" + ], + "result": "unit" + }, + { + "name": "c", + "async": true, + "params": [], + "result": "u32" + }, + { + "name": "y::a", + "async": true, + "params": [ + "handle-0" + ], + "result": "unit" + }, + { + "name": "y::b", + "async": true, + "params": [ + "handle-0", + "s32" + ], + "result": "unit" + }, + { + "name": "y::c", + "async": true, + "params": [ + "handle-0" + ], + "result": "u32" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/comments.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/comments.wit new file mode 100644 index 0000000..1a0b7bd --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/comments.wit @@ -0,0 +1,19 @@ +// hello +// world +// why, yes +// this is a comment +/* this too */ /* is a comment */ +/* this /* is /* a */ nested */ comment */ + + +type /* foo */ bar /* baz */ = // +handle // +// +// + + + + +x + +resource /* x */ x // ... diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/comments.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/comments.wit.result new file mode 100644 index 0000000..b84b7c1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/comments.wit.result @@ -0,0 +1,18 @@ +{ + "resources": [ + { + "name": "x" + } + ], + "types": [ + { + "idx": 0, + "name": "bar", + "primitive": "handle-0" + }, + { + "idx": 1, + "primitive": "handle-0" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/embedded.wit.md b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/embedded.wit.md new file mode 100644 index 0000000..77342a9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/embedded.wit.md @@ -0,0 +1,24 @@ +# A Markdown file! + +containing stuff, and also some code blocks, wit and other. + +```wit +x: func() +``` + +Intervening content, including a non-wit codeblock: +```js +function func() {} +``` + +```wit +y: func() +``` + +## A new section + +In which, another wit code block! + +```wit +z: func() +``` diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/embedded.wit.md.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/embedded.wit.md.result new file mode 100644 index 0000000..661095c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/embedded.wit.md.result @@ -0,0 +1,19 @@ +{ + "functions": [ + { + "name": "x", + "params": [], + "result": "unit" + }, + { + "name": "y", + "params": [], + "result": "unit" + }, + { + "name": "z", + "params": [], + "result": "unit" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/empty.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/empty.wit new file mode 100644 index 0000000..e69de29 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/empty.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/empty.wit.result new file mode 100644 index 0000000..9e26dfe --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/empty.wit.result @@ -0,0 +1 @@ +{} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/functions.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/functions.wit new file mode 100644 index 0000000..44cbe83 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/functions.wit @@ -0,0 +1,7 @@ +f1: func() +f2: func(a: u32) +f3: func(a: u32,) +f4: func() -> u32 +f6: func() -> tuple +f7: func(a: float32, b: float32) -> tuple +f8: func(a: option) -> expected diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/functions.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/functions.wit.result new file mode 100644 index 0000000..c2c5e86 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/functions.wit.result @@ -0,0 +1,70 @@ +{ + "types": [ + { + "idx": 0, + "tuple": { + "types": [ + "u32", + "u32" + ] + } + }, + { + "idx": 1, + "option": "u32" + }, + { + "idx": 2, + "expected": { + "ok": "u32", + "err": "float32" + } + } + ], + "functions": [ + { + "name": "f1", + "params": [], + "result": "unit" + }, + { + "name": "f2", + "params": [ + "u32" + ], + "result": "unit" + }, + { + "name": "f3", + "params": [ + "u32" + ], + "result": "unit" + }, + { + "name": "f4", + "params": [], + "result": "u32" + }, + { + "name": "f6", + "params": [], + "result": "type-0" + }, + { + "name": "f7", + "params": [ + "float32", + "float32" + ], + "result": "type-0" + }, + { + "name": "f8", + "params": [ + "type-1" + ], + "result": "type-2" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me-too.wit.md b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me-too.wit.md new file mode 100644 index 0000000..ef708a7 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me-too.wit.md @@ -0,0 +1,28 @@ +# Title + +This file is like import-me.wit, but it's a Markdown file with embedded wit +code blocks. + +## `foo` +```wit +/// This is foo. +type foo = u32 +``` + +## `x` +```wit +/// This is x. +resource x +``` + +## `handle` +```wit +/// This is handle. +type %handle = handle x +``` + +## `some-record` +```wit +/// This is some-record. +type some-record = tuple +``` diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me-too.wit.md.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me-too.wit.md.result new file mode 100644 index 0000000..94a04f5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me-too.wit.md.result @@ -0,0 +1,34 @@ +{ + "resources": [ + { + "name": "x" + } + ], + "types": [ + { + "idx": 0, + "name": "foo", + "primitive": "u32" + }, + { + "idx": 1, + "primitive": "handle-0" + }, + { + "idx": 2, + "name": "handle", + "primitive": "handle-0" + }, + { + "idx": 3, + "name": "some-record", + "tuple": { + "types": [ + "u32", + "u64", + "float32" + ] + } + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me.wit new file mode 100644 index 0000000..4a87038 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me.wit @@ -0,0 +1,7 @@ +type foo = u32 + +resource x + +type %handle = handle x + +type some-record = tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me.wit.result new file mode 100644 index 0000000..94a04f5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/import-me.wit.result @@ -0,0 +1,34 @@ +{ + "resources": [ + { + "name": "x" + } + ], + "types": [ + { + "idx": 0, + "name": "foo", + "primitive": "u32" + }, + { + "idx": 1, + "primitive": "handle-0" + }, + { + "idx": 2, + "name": "handle", + "primitive": "handle-0" + }, + { + "idx": 3, + "name": "some-record", + "tuple": { + "types": [ + "u32", + "u64", + "float32" + ] + } + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports-from-wit-md.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports-from-wit-md.wit new file mode 100644 index 0000000..cccc01f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports-from-wit-md.wit @@ -0,0 +1,18 @@ +// This test is like imports.wit, but uses import-me-too, which is a markdown +// file instead of a plain wit file. + +use { foo } from import-me-too +use { foo as bar } from import-me-too +use { x as import-me-x } from import-me-too + +type x = foo +type y = bar +type z1 = import-me-x +type z2 = handle import-me-x + +use { %handle } from import-me-too +resource xyz +type my-handle = handle xyz +type my-handle2 = xyz + +use { some-record } from import-me-too diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports-from-wit-md.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports-from-wit-md.wit.result new file mode 100644 index 0000000..7c22c4f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports-from-wit-md.wit.result @@ -0,0 +1,76 @@ +{ + "resources": [ + { + "name": "x", + "foreign_module": "import-me-too" + }, + { + "name": "xyz" + } + ], + "types": [ + { + "idx": 0, + "name": "foo", + "primitive": "u32", + "foreign_module": "import-me-too" + }, + { + "idx": 1, + "primitive": "handle-0", + "foreign_module": "import-me-too" + }, + { + "idx": 2, + "name": "handle", + "primitive": "handle-0", + "foreign_module": "import-me-too" + }, + { + "idx": 3, + "name": "some-record", + "tuple": { + "types": [ + "u32", + "u64", + "float32" + ] + }, + "foreign_module": "import-me-too" + }, + { + "idx": 4, + "name": "x", + "primitive": "type-0" + }, + { + "idx": 5, + "name": "y", + "primitive": "type-0" + }, + { + "idx": 6, + "name": "z1", + "primitive": "type-1" + }, + { + "idx": 7, + "name": "z2", + "primitive": "handle-0" + }, + { + "idx": 8, + "primitive": "handle-1" + }, + { + "idx": 9, + "name": "my-handle", + "primitive": "handle-1" + }, + { + "idx": 10, + "name": "my-handle2", + "primitive": "type-8" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports.wit new file mode 100644 index 0000000..31415ad --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports.wit @@ -0,0 +1,15 @@ +use { foo } from import-me +use { foo as bar } from import-me +use { x as import-me-x } from import-me + +type x = foo +type y = bar +type z1 = import-me-x +type z2 = handle import-me-x + +use { %handle } from import-me +resource xyz +type my-handle = handle xyz +type my-handle2 = xyz + +use { some-record } from import-me diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports.wit.result new file mode 100644 index 0000000..dbe0a34 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports.wit.result @@ -0,0 +1,76 @@ +{ + "resources": [ + { + "name": "x", + "foreign_module": "import-me" + }, + { + "name": "xyz" + } + ], + "types": [ + { + "idx": 0, + "name": "foo", + "primitive": "u32", + "foreign_module": "import-me" + }, + { + "idx": 1, + "primitive": "handle-0", + "foreign_module": "import-me" + }, + { + "idx": 2, + "name": "handle", + "primitive": "handle-0", + "foreign_module": "import-me" + }, + { + "idx": 3, + "name": "some-record", + "tuple": { + "types": [ + "u32", + "u64", + "float32" + ] + }, + "foreign_module": "import-me" + }, + { + "idx": 4, + "name": "x", + "primitive": "type-0" + }, + { + "idx": 5, + "name": "y", + "primitive": "type-0" + }, + { + "idx": 6, + "name": "z1", + "primitive": "type-1" + }, + { + "idx": 7, + "name": "z2", + "primitive": "handle-0" + }, + { + "idx": 8, + "primitive": "handle-1" + }, + { + "idx": 9, + "name": "my-handle", + "primitive": "handle-1" + }, + { + "idx": 10, + "name": "my-handle2", + "primitive": "type-8" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports2.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports2.wit new file mode 100644 index 0000000..40a453c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports2.wit @@ -0,0 +1,4 @@ +use * from import-me + +type my-handle = handle x +type my-handle2 = x diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports2.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports2.wit.result new file mode 100644 index 0000000..aa3f1fd --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/imports2.wit.result @@ -0,0 +1,49 @@ +{ + "resources": [ + { + "name": "x", + "foreign_module": "import-me" + } + ], + "types": [ + { + "idx": 0, + "name": "foo", + "primitive": "u32", + "foreign_module": "import-me" + }, + { + "idx": 1, + "name": "handle", + "primitive": "handle-0", + "foreign_module": "import-me" + }, + { + "idx": 2, + "name": "some-record", + "tuple": { + "types": [ + "u32", + "u64", + "float32" + ] + }, + "foreign_module": "import-me" + }, + { + "idx": 3, + "primitive": "handle-0", + "foreign_module": "import-me" + }, + { + "idx": 4, + "name": "my-handle", + "primitive": "handle-0" + }, + { + "idx": 5, + "name": "my-handle2", + "primitive": "type-3" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async.wit new file mode 100644 index 0000000..6b05801 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async.wit @@ -0,0 +1,2 @@ +// parse-fail +a: async diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async.wit.result new file mode 100644 index 0000000..c206072 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async.wit.result @@ -0,0 +1,5 @@ +expected keyword `func`, found eof + --> tests/ui/parse-fail/async.wit:3:1 + | + 3 | + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async1.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async1.wit new file mode 100644 index 0000000..663585a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async1.wit @@ -0,0 +1,3 @@ +// parse-fail +a: async() + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async1.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async1.wit.result new file mode 100644 index 0000000..caab404 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/async1.wit.result @@ -0,0 +1,5 @@ +expected keyword `func`, found '(' + --> tests/ui/parse-fail/async1.wit:2:9 + | + 2 | a: async() + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-list.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-list.wit new file mode 100644 index 0000000..122e8be --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-list.wit @@ -0,0 +1,5 @@ +// parse-fail + +type x = list', found keyword `type` + --> tests/ui/parse-fail/bad-list.wit:5:1 + | + 5 | type y = u32 + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource.wit new file mode 100644 index 0000000..f2a8e13 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource.wit @@ -0,0 +1,5 @@ +// parse-fail + +resource x { + x: s32 +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource.wit.result new file mode 100644 index 0000000..2c6572b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource.wit.result @@ -0,0 +1,5 @@ +globals not allowed in resources + --> tests/ui/parse-fail/bad-resource.wit:4:3 + | + 4 | x: s32 + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource2.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource2.wit new file mode 100644 index 0000000..585045a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource2.wit @@ -0,0 +1,6 @@ +// parse-fail + +resource x { + x: func() + x: func() +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource2.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource2.wit.result new file mode 100644 index 0000000..661e879 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-resource2.wit.result @@ -0,0 +1,5 @@ +"x" defined twice in this resource + --> tests/ui/parse-fail/bad-resource2.wit:5:3 + | + 5 | x: func() + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use.wit new file mode 100644 index 0000000..8df2e41 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use.wit @@ -0,0 +1,2 @@ +// parse-fail +use {} from foo diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use.wit.result new file mode 100644 index 0000000..1164001 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use.wit.result @@ -0,0 +1,5 @@ +expected an identifier or string, found '}' + --> tests/ui/parse-fail/bad-use.wit:2:6 + | + 2 | use {} from foo + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use2.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use2.wit new file mode 100644 index 0000000..765c554 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use2.wit @@ -0,0 +1,4 @@ +// parse-fail +use { a } from + +type foo = u32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use2.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use2.wit.result new file mode 100644 index 0000000..23c1065 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use2.wit.result @@ -0,0 +1,5 @@ +expected an identifier or string, found keyword `type` + --> tests/ui/parse-fail/bad-use2.wit:4:1 + | + 4 | type foo = u32 + | ^--- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use3.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use3.wit new file mode 100644 index 0000000..760b36a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use3.wit @@ -0,0 +1,2 @@ +// parse-fail +use * from type diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use3.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use3.wit.result new file mode 100644 index 0000000..74f4b16 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use3.wit.result @@ -0,0 +1,5 @@ +expected an identifier or string, found keyword `type` + --> tests/ui/parse-fail/bad-use3.wit:2:12 + | + 2 | use * from type + | ^--- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use4.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use4.wit new file mode 100644 index 0000000..87dfcb5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use4.wit @@ -0,0 +1,2 @@ +// parse-fail +use { foo } from bar: diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use4.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use4.wit.result new file mode 100644 index 0000000..8cc6544 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use4.wit.result @@ -0,0 +1,5 @@ +expected ':', found whitespace + --> tests/ui/parse-fail/bad-use4.wit:2:22 + | + 2 | use { foo } from bar: + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use5.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use5.wit new file mode 100644 index 0000000..c39c9d6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use5.wit @@ -0,0 +1,2 @@ +// parse-fail +use { foo } from bar:baz diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use5.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use5.wit.result new file mode 100644 index 0000000..bd51dc5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use5.wit.result @@ -0,0 +1,5 @@ +expected ':', found an identifier + --> tests/ui/parse-fail/bad-use5.wit:2:22 + | + 2 | use { foo } from bar:baz + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use6.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use6.wit new file mode 100644 index 0000000..ec973c3 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use6.wit @@ -0,0 +1,3 @@ +// parse-fail + +use { foo } from import-me::bar diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use6.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use6.wit.result new file mode 100644 index 0000000..14522ba --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use6.wit.result @@ -0,0 +1,5 @@ +`bar` not defined in `import-me` + --> tests/ui/parse-fail/bad-use6.wit:3:29 + | + 3 | use { foo } from import-me::bar + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use7.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use7.wit new file mode 100644 index 0000000..8a35091 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use7.wit @@ -0,0 +1,3 @@ +// parse-fail + +use from import-me diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use7.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use7.wit.result new file mode 100644 index 0000000..417b4a8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/bad-use7.wit.result @@ -0,0 +1,5 @@ +expected '{', found keyword `from` + --> tests/ui/parse-fail/bad-use7.wit:3:5 + | + 3 | use from import-me + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle.wit new file mode 100644 index 0000000..4b66203 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle.wit @@ -0,0 +1,2 @@ +// parse-fail +type foo = foo diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle.wit.result new file mode 100644 index 0000000..efc8525 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle.wit.result @@ -0,0 +1,5 @@ +type can recursively refer to itself + --> tests/ui/parse-fail/cycle.wit:2:6 + | + 2 | type foo = foo + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle2.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle2.wit new file mode 100644 index 0000000..806472e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle2.wit @@ -0,0 +1,3 @@ +// parse-fail +type foo = bar +type bar = foo diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle2.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle2.wit.result new file mode 100644 index 0000000..db34036 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle2.wit.result @@ -0,0 +1,5 @@ +type can recursively refer to itself + --> tests/ui/parse-fail/cycle2.wit:2:6 + | + 2 | type foo = bar + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle3.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle3.wit new file mode 100644 index 0000000..14b3582 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle3.wit @@ -0,0 +1,3 @@ +// parse-fail +type foo = bar +type bar = option diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle3.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle3.wit.result new file mode 100644 index 0000000..19ef36e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle3.wit.result @@ -0,0 +1,5 @@ +type can recursively refer to itself + --> tests/ui/parse-fail/cycle3.wit:2:6 + | + 2 | type foo = bar + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle4.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle4.wit new file mode 100644 index 0000000..a86f9d5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle4.wit @@ -0,0 +1,3 @@ +// parse-fail +type foo = bar +record bar { x: foo } diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle4.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle4.wit.result new file mode 100644 index 0000000..5bf263b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle4.wit.result @@ -0,0 +1,5 @@ +type can recursively refer to itself + --> tests/ui/parse-fail/cycle4.wit:2:6 + | + 2 | type foo = bar + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle5.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle5.wit new file mode 100644 index 0000000..229db20 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle5.wit @@ -0,0 +1,3 @@ +// parse-fail +type foo = bar +type bar = list diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle5.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle5.wit.result new file mode 100644 index 0000000..9b143b5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/cycle5.wit.result @@ -0,0 +1,5 @@ +type can recursively refer to itself + --> tests/ui/parse-fail/cycle5.wit:2:6 + | + 2 | type foo = bar + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/dangling-type.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/dangling-type.wit new file mode 100644 index 0000000..cfc0fd5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/dangling-type.wit @@ -0,0 +1,3 @@ +// parse-fail + +type diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/dangling-type.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/dangling-type.wit.result new file mode 100644 index 0000000..9594527 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/dangling-type.wit.result @@ -0,0 +1,5 @@ +expected an identifier or string, found eof + --> tests/ui/parse-fail/dangling-type.wit:4:1 + | + 4 | + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-functions.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-functions.wit new file mode 100644 index 0000000..0d8ebe9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-functions.wit @@ -0,0 +1,4 @@ +// parse-fail + +foo: func() +foo: func() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-functions.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-functions.wit.result new file mode 100644 index 0000000..1d6aeb0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-functions.wit.result @@ -0,0 +1,5 @@ +"foo" defined twice + --> tests/ui/parse-fail/duplicate-functions.wit:4:1 + | + 4 | foo: func() + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-resource.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-resource.wit new file mode 100644 index 0000000..336ea11 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-resource.wit @@ -0,0 +1,4 @@ +// parse-fail + +resource a +resource a diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-resource.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-resource.wit.result new file mode 100644 index 0000000..287fbad --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-resource.wit.result @@ -0,0 +1,5 @@ +resource "a" defined twice + --> tests/ui/parse-fail/duplicate-resource.wit:4:10 + | + 4 | resource a + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-type.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-type.wit new file mode 100644 index 0000000..73290f6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-type.wit @@ -0,0 +1,4 @@ +// parse-fail + +type foo = s32 +type foo = s32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-type.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-type.wit.result new file mode 100644 index 0000000..81421c4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-type.wit.result @@ -0,0 +1,5 @@ +type "foo" defined twice + --> tests/ui/parse-fail/duplicate-type.wit:4:6 + | + 4 | type foo = s32 + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-value.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-value.wit new file mode 100644 index 0000000..b34ba98 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-value.wit @@ -0,0 +1,3 @@ +// parse-fail +a: s32 +a: u32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-value.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-value.wit.result new file mode 100644 index 0000000..7cce26d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/duplicate-value.wit.result @@ -0,0 +1,5 @@ +"a" defined twice + --> tests/ui/parse-fail/duplicate-value.wit:3:1 + | + 3 | a: u32 + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-enum.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-enum.wit new file mode 100644 index 0000000..a916550 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-enum.wit @@ -0,0 +1,2 @@ +// parse-fail +enum t {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-enum.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-enum.wit.result new file mode 100644 index 0000000..02d4389 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-enum.wit.result @@ -0,0 +1,5 @@ +empty enum + --> tests/ui/parse-fail/empty-enum.wit:2:6 + | + 2 | enum t {} + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-union.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-union.wit new file mode 100644 index 0000000..3791503 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-union.wit @@ -0,0 +1,2 @@ +// parse-fail +union t {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-union.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-union.wit.result new file mode 100644 index 0000000..e65b95d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-union.wit.result @@ -0,0 +1,5 @@ +empty union + --> tests/ui/parse-fail/empty-union.wit:2:7 + | + 2 | union t {} + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-variant1.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-variant1.wit new file mode 100644 index 0000000..e265d73 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-variant1.wit @@ -0,0 +1,2 @@ +// parse-fail +variant t {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-variant1.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-variant1.wit.result new file mode 100644 index 0000000..e90c0b6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/empty-variant1.wit.result @@ -0,0 +1,5 @@ +empty variant + --> tests/ui/parse-fail/empty-variant1.wit:2:9 + | + 2 | variant t {} + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/handle-no-resource.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/handle-no-resource.wit new file mode 100644 index 0000000..2151415 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/handle-no-resource.wit @@ -0,0 +1,2 @@ +// parse-fail +type foo = handle foo diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/handle-no-resource.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/handle-no-resource.wit.result new file mode 100644 index 0000000..cb50f28 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/handle-no-resource.wit.result @@ -0,0 +1,5 @@ +no resource named `foo` + --> tests/ui/parse-fail/handle-no-resource.wit:2:19 + | + 2 | type foo = handle foo + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad.wit new file mode 100644 index 0000000..28bc3bc --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad.wit @@ -0,0 +1,2 @@ +// parse-fail +use { nonexistent } from import-me diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad.wit.result new file mode 100644 index 0000000..c64060b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad.wit.result @@ -0,0 +1,5 @@ +name not defined in submodule + --> tests/ui/parse-fail/import-bad.wit:2:7 + | + 2 | use { nonexistent } from import-me + | ^---------- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad2.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad2.wit new file mode 100644 index 0000000..aa749d8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad2.wit @@ -0,0 +1,3 @@ +// parse-fail +use { foo } from import-me +use { foo } from import-me diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad2.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad2.wit.result new file mode 100644 index 0000000..99c842a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad2.wit.result @@ -0,0 +1,5 @@ +type "foo" defined twice + --> tests/ui/parse-fail/import-bad2.wit:3:7 + | + 3 | use { foo } from import-me + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad3.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad3.wit new file mode 100644 index 0000000..e9ad08f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad3.wit @@ -0,0 +1,3 @@ +// parse-fail +use { bar } from import-me +use { bar } from import-me diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad3.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad3.wit.result new file mode 100644 index 0000000..1d5035c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad3.wit.result @@ -0,0 +1,5 @@ +resource "bar" defined twice + --> tests/ui/parse-fail/import-bad3.wit:3:7 + | + 3 | use { bar } from import-me + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad4.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad4.wit new file mode 100644 index 0000000..0a5b19e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad4.wit @@ -0,0 +1,2 @@ +// parse-fail +use { bar, bar } from import-me diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad4.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad4.wit.result new file mode 100644 index 0000000..2c136e0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad4.wit.result @@ -0,0 +1,5 @@ +resource "bar" defined twice + --> tests/ui/parse-fail/import-bad4.wit:2:12 + | + 2 | use { bar, bar } from import-me + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad5.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad5.wit new file mode 100644 index 0000000..d57dcef --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad5.wit @@ -0,0 +1,3 @@ +// parse-fail +use { foo } from import-me +use * from import-me diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad5.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad5.wit.result new file mode 100644 index 0000000..976fea9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-bad5.wit.result @@ -0,0 +1,5 @@ +type "foo" defined twice + --> tests/ui/parse-fail/import-bad5.wit:3:12 + | + 3 | use * from import-me + | ^-------- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v1.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v1.wit new file mode 100644 index 0000000..7c3f5a6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v1.wit @@ -0,0 +1,2 @@ +// parse-fail +use { foo } from import-cycle2-v2 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v1.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v1.wit.result new file mode 100644 index 0000000..85c7fe8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v1.wit.result @@ -0,0 +1 @@ +file `tests/ui/parse-fail/import-cycle2-v1.wit` recursively imports itself \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v2.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v2.wit new file mode 100644 index 0000000..bdcb372 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v2.wit @@ -0,0 +1,2 @@ +// parse-fail +use { foo } from import-cycle2-v1 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v2.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v2.wit.result new file mode 100644 index 0000000..103742e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-cycle2-v2.wit.result @@ -0,0 +1 @@ +file `tests/ui/parse-fail/import-cycle2-v2.wit` recursively imports itself \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-me.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-me.wit new file mode 100644 index 0000000..fe776fa --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-me.wit @@ -0,0 +1,2 @@ +type foo = u32 +resource bar diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-me.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-me.wit.result new file mode 100644 index 0000000..915fe49 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import-me.wit.result @@ -0,0 +1,18 @@ +{ + "resources": [ + { + "name": "bar" + } + ], + "types": [ + { + "idx": 0, + "name": "foo", + "primitive": "u32" + }, + { + "idx": 1, + "primitive": "handle-0" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import1.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import1.wit new file mode 100644 index 0000000..8aaf589 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import1.wit @@ -0,0 +1,2 @@ +// parse-fail +use { foo } from import1 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import1.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import1.wit.result new file mode 100644 index 0000000..1d81fb1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/import1.wit.result @@ -0,0 +1 @@ +file `tests/ui/parse-fail/import1.wit` recursively imports itself \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-md.md b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-md.md new file mode 100644 index 0000000..9464aaa --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-md.md @@ -0,0 +1,7 @@ +// parse-fail + +hello + +```wit +type foo = bar +``` diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-md.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-md.wit.result new file mode 100644 index 0000000..72dbca7 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-md.wit.result @@ -0,0 +1,5 @@ +no type named `bar` + --> tests/ui/parse-fail/invalid-md.md:6:12 + | + 6 | type foo = bar + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-toplevel.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-toplevel.wit new file mode 100644 index 0000000..2c99c26 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-toplevel.wit @@ -0,0 +1,2 @@ +// parse-fail +abcd diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-toplevel.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-toplevel.wit.result new file mode 100644 index 0000000..244a793 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/invalid-toplevel.wit.result @@ -0,0 +1,5 @@ +expected ':', found eof + --> tests/ui/parse-fail/invalid-toplevel.wit:3:1 + | + 3 | + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/keyword.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/keyword.wit new file mode 100644 index 0000000..7d16469 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/keyword.wit @@ -0,0 +1,3 @@ +// parse-fail + +type option = u32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/keyword.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/keyword.wit.result new file mode 100644 index 0000000..5e24b7d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/keyword.wit.result @@ -0,0 +1,5 @@ +expected an identifier or string, found keyword `option` + --> tests/ui/parse-fail/keyword.wit:3:6 + | + 3 | type option = u32 + | ^----- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/undefined-typed.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/undefined-typed.wit new file mode 100644 index 0000000..3f5add4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/undefined-typed.wit @@ -0,0 +1,3 @@ +// parse-fail + +type foo = bar diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/undefined-typed.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/undefined-typed.wit.result new file mode 100644 index 0000000..3dcf4c6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/undefined-typed.wit.result @@ -0,0 +1,5 @@ +no type named `bar` + --> tests/ui/parse-fail/undefined-typed.wit:3:12 + | + 3 | type foo = bar + | ^-- \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/unterminated-string.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/unterminated-string.wit.result new file mode 100644 index 0000000..0d117b2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/parse-fail/unterminated-string.wit.result @@ -0,0 +1,5 @@ +unterminated string literal + --> tests/ui/parse-fail/unterminated-string.wit:3:1 + | + 3 | " + | ^ \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/resource.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/resource.wit new file mode 100644 index 0000000..ceb5a1e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/resource.wit @@ -0,0 +1,16 @@ +resource a +resource b +resource c +resource d {} +resource e { + x: func() +} +resource f { + x: func() + y: func() +} +resource g implements a +resource h implements b {} +resource i implements e { + z: func() +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/resource.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/resource.wit.result new file mode 100644 index 0000000..b09cb84 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/resource.wit.result @@ -0,0 +1,102 @@ +{ + "resources": [ + { + "name": "a" + }, + { + "name": "b" + }, + { + "name": "c" + }, + { + "name": "d" + }, + { + "name": "e" + }, + { + "name": "f" + }, + { + "name": "g", + "supertype": "a" + }, + { + "name": "h", + "supertype": "b" + }, + { + "name": "i", + "supertype": "e" + } + ], + "types": [ + { + "idx": 0, + "primitive": "handle-0" + }, + { + "idx": 1, + "primitive": "handle-1" + }, + { + "idx": 2, + "primitive": "handle-2" + }, + { + "idx": 3, + "primitive": "handle-3" + }, + { + "idx": 4, + "primitive": "handle-4" + }, + { + "idx": 5, + "primitive": "handle-5" + }, + { + "idx": 6, + "primitive": "handle-6" + }, + { + "idx": 7, + "primitive": "handle-7" + }, + { + "idx": 8, + "primitive": "handle-8" + } + ], + "functions": [ + { + "name": "e::x", + "params": [ + "handle-4" + ], + "result": "unit" + }, + { + "name": "f::x", + "params": [ + "handle-5" + ], + "result": "unit" + }, + { + "name": "f::y", + "params": [ + "handle-5" + ], + "result": "unit" + }, + { + "name": "i::z", + "params": [ + "handle-8" + ], + "result": "unit" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/type-then-eof.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/type-then-eof.wit new file mode 100644 index 0000000..e5f6573 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/type-then-eof.wit @@ -0,0 +1 @@ +foo: func() -> string \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/type-then-eof.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/type-then-eof.wit.result new file mode 100644 index 0000000..ae65191 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/type-then-eof.wit.result @@ -0,0 +1,9 @@ +{ + "functions": [ + { + "name": "foo", + "params": [], + "result": "string" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/types.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/types.wit new file mode 100644 index 0000000..014fafe --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/types.wit @@ -0,0 +1,60 @@ +resource x + +type t1 = u8 +type t2 = u16 +type t3 = u32 +type t4 = u64 +type t5 = s8 +type t6 = s16 +type t7 = s32 +type t8 = s64 +type t9 = float32 +type t10 = float64 +type t11 = char +type t12 = list +type t13 = string +type t14 = option +type t15 = expected +type t16 = expected +type t17 = expected +type t18 = expected +type t19 = handle x +record t20 {} +record t21 { a: u32 } +record t22 { a: u32, } +record t23 { a: u32, b: u64 } +record t24 { a: u32, b: u64, } +record t25 { %x: u32 } +record %record {} +type t26 = tuple<> +type t27 = tuple +type t28 = tuple +type t29 = tuple +flags t30 {} +flags t31 { a, b, c } +flags t32 { a, b, c, } +variant t33 { a } +variant t34 { a, b } +variant t35 { a, b, } +variant t36 { a, b(u32), } +variant t37 { a, b(option), } +union t38 { u32, u64 } +union t39 { u32, option } +union t40 { u32, option, } +enum t41 { a, b, c } +enum t42 { a, b, c, } +type t43 = bool +type t44 = string +type t45 = list>> +type t46 = t44 +type t47 = %t44 +type t48 = stream +type t49 = stream +type t50 = stream +type t51 = stream +type t52 = future +type t53 = future + +// type order doesn't matter +type foo = bar +type bar = u32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/types.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/types.wit.result new file mode 100644 index 0000000..60a4f75 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/types.wit.result @@ -0,0 +1,483 @@ +{ + "resources": [ + { + "name": "x" + } + ], + "types": [ + { + "idx": 0, + "primitive": "handle-0" + }, + { + "idx": 1, + "name": "t1", + "primitive": "u8" + }, + { + "idx": 2, + "name": "t2", + "primitive": "u16" + }, + { + "idx": 3, + "name": "t3", + "primitive": "u32" + }, + { + "idx": 4, + "name": "t4", + "primitive": "u64" + }, + { + "idx": 5, + "name": "t5", + "primitive": "s8" + }, + { + "idx": 6, + "name": "t6", + "primitive": "s16" + }, + { + "idx": 7, + "name": "t7", + "primitive": "s32" + }, + { + "idx": 8, + "name": "t8", + "primitive": "s64" + }, + { + "idx": 9, + "name": "t9", + "primitive": "float32" + }, + { + "idx": 10, + "name": "t10", + "primitive": "float64" + }, + { + "idx": 11, + "name": "t11", + "primitive": "char" + }, + { + "idx": 12, + "name": "t12", + "list": "char" + }, + { + "idx": 13, + "name": "t13", + "primitive": "string" + }, + { + "idx": 14, + "name": "t14", + "option": "u32" + }, + { + "idx": 15, + "name": "t15", + "expected": { + "ok": "u32", + "err": "u32" + } + }, + { + "idx": 16, + "name": "t16", + "expected": { + "ok": "unit", + "err": "u32" + } + }, + { + "idx": 17, + "name": "t17", + "expected": { + "ok": "u32", + "err": "unit" + } + }, + { + "idx": 18, + "name": "t18", + "expected": { + "ok": "unit", + "err": "unit" + } + }, + { + "idx": 19, + "name": "t19", + "primitive": "handle-0" + }, + { + "idx": 20, + "name": "t20", + "record": { + "fields": [] + } + }, + { + "idx": 21, + "name": "t21", + "record": { + "fields": [ + [ + "a", + "u32" + ] + ] + } + }, + { + "idx": 22, + "name": "t22", + "record": { + "fields": [ + [ + "a", + "u32" + ] + ] + } + }, + { + "idx": 23, + "name": "t23", + "record": { + "fields": [ + [ + "a", + "u32" + ], + [ + "b", + "u64" + ] + ] + } + }, + { + "idx": 24, + "name": "t24", + "record": { + "fields": [ + [ + "a", + "u32" + ], + [ + "b", + "u64" + ] + ] + } + }, + { + "idx": 25, + "name": "t25", + "record": { + "fields": [ + [ + "x", + "u32" + ] + ] + } + }, + { + "idx": 26, + "name": "record", + "record": { + "fields": [] + } + }, + { + "idx": 27, + "name": "t26", + "tuple": { + "types": [] + } + }, + { + "idx": 28, + "name": "t27", + "tuple": { + "types": [ + "u32" + ] + } + }, + { + "idx": 29, + "name": "t28", + "tuple": { + "types": [ + "u32" + ] + } + }, + { + "idx": 30, + "name": "t29", + "tuple": { + "types": [ + "u32", + "u64" + ] + } + }, + { + "idx": 31, + "name": "t30", + "flags": { + "flags": [] + } + }, + { + "idx": 32, + "name": "t31", + "flags": { + "flags": [ + "a", + "b", + "c" + ] + } + }, + { + "idx": 33, + "name": "t32", + "flags": { + "flags": [ + "a", + "b", + "c" + ] + } + }, + { + "idx": 34, + "name": "t33", + "variant": { + "cases": [ + [ + "a", + "unit" + ] + ] + } + }, + { + "idx": 35, + "name": "t34", + "variant": { + "cases": [ + [ + "a", + "unit" + ], + [ + "b", + "unit" + ] + ] + } + }, + { + "idx": 36, + "name": "t35", + "variant": { + "cases": [ + [ + "a", + "unit" + ], + [ + "b", + "unit" + ] + ] + } + }, + { + "idx": 37, + "name": "t36", + "variant": { + "cases": [ + [ + "a", + "unit" + ], + [ + "b", + "u32" + ] + ] + } + }, + { + "idx": 38, + "name": "t37", + "variant": { + "cases": [ + [ + "a", + "unit" + ], + [ + "b", + "type-57" + ] + ] + } + }, + { + "idx": 39, + "name": "t38", + "union": { + "cases": [ + "u32", + "u64" + ] + } + }, + { + "idx": 40, + "name": "t39", + "union": { + "cases": [ + "u32", + "type-57" + ] + } + }, + { + "idx": 41, + "name": "t40", + "union": { + "cases": [ + "u32", + "type-57" + ] + } + }, + { + "idx": 42, + "name": "t41", + "enum": { + "cases": [ + "a", + "b", + "c" + ] + } + }, + { + "idx": 43, + "name": "t42", + "enum": { + "cases": [ + "a", + "b", + "c" + ] + } + }, + { + "idx": 44, + "name": "t43", + "primitive": "bool" + }, + { + "idx": 45, + "name": "t44", + "primitive": "string" + }, + { + "idx": 46, + "name": "t45", + "list": "type-59" + }, + { + "idx": 47, + "name": "t46", + "primitive": "type-45" + }, + { + "idx": 48, + "name": "t47", + "primitive": "type-45" + }, + { + "idx": 49, + "name": "t48", + "stream": { + "element": "u32", + "end": "u32" + } + }, + { + "idx": 50, + "name": "t49", + "stream": { + "element": "unit", + "end": "u32" + } + }, + { + "idx": 51, + "name": "t50", + "stream": { + "element": "u32", + "end": "unit" + } + }, + { + "idx": 52, + "name": "t51", + "stream": { + "element": "unit", + "end": "unit" + } + }, + { + "idx": 53, + "name": "t52", + "future": "u32" + }, + { + "idx": 54, + "name": "t53", + "future": "unit" + }, + { + "idx": 55, + "name": "foo", + "primitive": "type-56" + }, + { + "idx": 56, + "name": "bar", + "primitive": "u32" + }, + { + "idx": 57, + "option": "u32" + }, + { + "idx": 58, + "list": "type-33" + }, + { + "idx": 59, + "list": "type-58" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/values.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/values.wit new file mode 100644 index 0000000..413bdfd --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/values.wit @@ -0,0 +1,4 @@ +a: s32 +b: tuple<> +record a {} +c: tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/values.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/values.wit.result new file mode 100644 index 0000000..d04150c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/values.wit.result @@ -0,0 +1,40 @@ +{ + "types": [ + { + "idx": 0, + "name": "a", + "record": { + "fields": [] + } + }, + { + "idx": 1, + "tuple": { + "types": [] + } + }, + { + "idx": 2, + "tuple": { + "types": [ + "u32", + "type-0" + ] + } + } + ], + "globals": [ + { + "name": "a", + "ty": "s32" + }, + { + "name": "b", + "ty": "type-1" + }, + { + "name": "c", + "ty": "type-2" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-clock.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-clock.wit new file mode 100644 index 0000000..7f360b9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-clock.wit @@ -0,0 +1,15 @@ +// WASI Clocks. +// +// Some content here is derived from [CloudABI](https://github.com/NuxiNL/cloudabi). + +use { clockid, timestamp, errno } from wasi + +// Return the resolution of a clock. +// Implementations are required to provide a non-zero value for supported clocks. For unsupported clocks, +// return `errno::inval`. +// Note: This is similar to `clock-getres` in POSIX. +res-get: func(id: clockid) -> expected + +// Return the time value of a clock. +// Note: This is similar to `clock-gettime` in POSIX. +time-get: func(id: clockid, precision: timestamp) -> expected diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-clock.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-clock.wit.result new file mode 100644 index 0000000..ee1e14c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-clock.wit.result @@ -0,0 +1,131 @@ +{ + "types": [ + { + "idx": 0, + "name": "clockid", + "enum": { + "cases": [ + "realtime", + "monotonic" + ] + }, + "foreign_module": "wasi" + }, + { + "idx": 1, + "name": "timestamp", + "primitive": "u64", + "foreign_module": "wasi" + }, + { + "idx": 2, + "name": "errno", + "enum": { + "cases": [ + "success", + "toobig", + "access", + "addrinuse", + "addrnotavail", + "afnosupport", + "again", + "already", + "badf", + "badmsg", + "busy", + "canceled", + "child", + "connaborted", + "connrefused", + "connreset", + "deadlk", + "destaddrreq", + "dom", + "dquot", + "exist", + "fault", + "fbig", + "hostunreach", + "idrm", + "ilseq", + "inprogress", + "intr", + "inval", + "io", + "isconn", + "isdir", + "loop", + "mfile", + "mlink", + "msgsize", + "multihop", + "nametoolong", + "netdown", + "netreset", + "netunreach", + "nfile", + "nobufs", + "nodev", + "noent", + "noexec", + "nolck", + "nolink", + "nomem", + "nomsg", + "noprotoopt", + "nospc", + "nosys", + "notconn", + "notdir", + "notempty", + "notrecoverable", + "notsock", + "notsup", + "notty", + "nxio", + "overflow", + "ownerdead", + "perm", + "pipe", + "proto", + "protonosupport", + "prototype", + "range", + "rofs", + "spipe", + "srch", + "stale", + "timedout", + "txtbsy", + "xdev", + "notcapable" + ] + }, + "foreign_module": "wasi" + }, + { + "idx": 3, + "expected": { + "ok": "type-1", + "err": "type-2" + } + } + ], + "functions": [ + { + "name": "res-get", + "params": [ + "type-0" + ], + "result": "type-3" + }, + { + "name": "time-get", + "params": [ + "type-0", + "type-1" + ], + "result": "type-3" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-http.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-http.wit new file mode 100644 index 0000000..67e0061 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-http.wit @@ -0,0 +1,159 @@ +// Note: this note isn't a doc comment. + +// Note: the following comment block is a doc comment, because it start with `///`. +// It's also a comment about the interface defined in this file, because it starts with `@interface` + +/// @interface WASI HTTP Core functionality. +/// +/// Defines requests & responses and associated types, and `fetch`. + +// `use` imports things from namespaces. The first identifier of an `ident1::ident2::[..]::identN` +// namespace chain needs to be resolved out of band. The remaining part of the chain is resolved +// as nested interfaces inside the definition of `ident1`. +// Note: I'm not sure if Result should just be an Interface Types thing instead of a WASI thing. +//use { Result, Option } from wasi::core + +/// A blocking function for turning requests into responses. +/// @param req - a `request` object to process +/// @return resp - the `Result` object produced once the blocking operation has finished. +// +// Note: the syntax for function definitions is chosen with three goals in mind: +// +// 1. maximizing human parseability and intuitive understanding, by not presupposing +// familiarity with any specific notations. Having the `function` keyword isn't otherwise +// strictly required for disambiguation. This becomes even more important given that part of +// the "weirdness budget" needs to be used for having support for multiple return values. +// +// 2. uniformity in the face of multiple return values. Basically, it's always a mapping +// of N arguments to M return values, with the same syntax for both. +// +// 3. provide an obvious place for adding attributes, like `blocking` and `reentrant`. +// +// Note: the `blocking` attribute means that different lowering can be generated for this +// function, either based on synchronous, blocking calls, or on a callback-style interface. +fetch: /*blocking*/ func(req: handle request) -> handle response + +/// A request resource, with lots of things missing for now. +// Note: `resource` blocks serve two purposes: +// 1. namespacing the definition of a Handle type with functions for operating on that handle +// 2. providing a way to define said functions such that they implicitly take the handle itself as +// the first argument. E.g., `method` in `request` is roughly equivalent to +// `request::method: func(self: handle request) -> (string)` +// ("Roughly" because the `resource` semantics allow us to generate better bindings for languages +// that have a concept of implicit receivers, such as `this` in JS.) +resource request { + + /// A static function acting as a constructor + /// @return req - returns a new `request` + // Note: `static` here simply means that no implicit receiver argument will be passed. + // I.e., this is ~roughly~ analogous to a top-level definition of + // `request::request: func() -> (req: handle request)` + // whereas without `static`, it'd be analogous to + // `request::request: func(self: handle) -> (req: handle)` + static request: func() -> handle request + + method: func() -> string + + // Note: We could consider allowing the parens to be omitted for single return values, like so: + headers: func() -> handle headers + + // Note: Or we could even allow leaving off the return value identifier, making it use the + // function name, like so: + body: func() -> handle body // This return type would be shorthand for `(body: body)` +} + +/// A response resource, with lots of things missing for now. +resource response { + status: func() -> u16 + headers: func() -> handle headers + body: func() -> handle body +} + +/// A headers resource, with lots of things missing for now. +resource headers { + /// Return values for the given header name. + /// @param name - the header's name. + /// @return values - the values for this header, seperated by ", " + // Note: in reality, it might make sense for the values to be a sequence of some kind. + get: func(name: string) -> option +} + +/// A body resource. +/// Bodies are interesting in that they can be both sources and sinks, on both requests and responses. +resource body { + /// Read from a body. + /// @param dest - destination buffer to write into + /// @return result - a result containing the number of bytes written on success + // TODO: check if `out-buffer` is the right way to express this + // NOTE: s/expected/result/ + read: func(dest: list) -> expected + + /// Write to a body. + /// @param source - source buffer to read from + /// @return result - a result containing the number of bytes read on success + // TODO: check if `in-buffer` is the right way to express this + write: func(source: list) -> expected +} +/* + +/// A nested interface, doing something neat and elaborate. +interface nested-interface1 { + /// An even better request resource than the one everybody's using. + resource better-request { + // TODO: do we need to have a ctor with special semantics? E.g. to generate actual + // constructors for languages that have them, such as JS? + new: func(request: handle) -> handle + // Note: sadly, the sauce better-request uses must remain secret, so it doesn't actually + // expose any of its functionality, and hence doesn't need any other methods. + } + /// Maps a request to an even better request. + // Note: the containing scope's members are in scope in a nested interface + fun: func(request: handle) -> handle +} + +/// Another nested interface, doing something even more neat and elaborate. +/// It does this by adding shiny stuff to what nested-interface1 is doing. +interface nested-interface2 { + // Note: as mentioned in the comment on this file's first `use` statement, the first + // ident in a namespace chain needs to be resolved out of band. `self` is an exception to + // this rule, and allows referring to the outermost containing scope. + use self::nested-interface1::better-request + + /// The secret sauce. It's so secret and magic that you're not allowed to touch it, quite sadly. + resource the-shiny { + } + + /// Maps a better request to a plain old response. + /// @param request - the already pretty good request to add the shiny to + /// @return response - a boring, normal response + /// @return added-shiny - the shiny! + // Note: this just exists to demonstrate multiple return values, including their documentation + fun: func(request: better-request) -> tuple, handle> +} +*/ + +/// An enum with some values +// Note: should we have a way to declare the underlying representation, along the lines of +// `enum error: u8 {..}`? +// Note: what about non-numeric types? +// Note: what about heterogeneous enums? +enum error { + overflow, + unavailable, +} + +/// A function returning a Result, with the enum above as one of the options +/// @return result - a `Result` containing either the desired number, or an `error` +maybe-number: func() -> expected + +/// A simple struct +record timestamp { + seconds: u64, + nanoseconds: u64, +} + +/// A simple value +my-int: u32 + +/// A handle to a request +my-request: handle request diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-http.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-http.wit.result new file mode 100644 index 0000000..037e009 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi-http.wit.result @@ -0,0 +1,170 @@ +{ + "resources": [ + { + "name": "request" + }, + { + "name": "response" + }, + { + "name": "headers" + }, + { + "name": "body" + } + ], + "types": [ + { + "idx": 0, + "primitive": "handle-0" + }, + { + "idx": 1, + "primitive": "handle-1" + }, + { + "idx": 2, + "primitive": "handle-2" + }, + { + "idx": 3, + "primitive": "handle-3" + }, + { + "idx": 4, + "name": "error", + "enum": { + "cases": [ + "overflow", + "unavailable" + ] + } + }, + { + "idx": 5, + "name": "timestamp", + "record": { + "fields": [ + [ + "seconds", + "u64" + ], + [ + "nanoseconds", + "u64" + ] + ] + } + }, + { + "idx": 6, + "option": "string" + }, + { + "idx": 7, + "list": "u8" + }, + { + "idx": 8, + "expected": { + "ok": "u64", + "err": "type-4" + } + } + ], + "functions": [ + { + "name": "fetch", + "params": [ + "handle-0" + ], + "result": "handle-1" + }, + { + "name": "request::request", + "params": [], + "result": "handle-0" + }, + { + "name": "request::method", + "params": [ + "handle-0" + ], + "result": "string" + }, + { + "name": "request::headers", + "params": [ + "handle-0" + ], + "result": "handle-2" + }, + { + "name": "request::body", + "params": [ + "handle-0" + ], + "result": "handle-3" + }, + { + "name": "response::status", + "params": [ + "handle-1" + ], + "result": "u16" + }, + { + "name": "response::headers", + "params": [ + "handle-1" + ], + "result": "handle-2" + }, + { + "name": "response::body", + "params": [ + "handle-1" + ], + "result": "handle-3" + }, + { + "name": "headers::get", + "params": [ + "handle-2", + "string" + ], + "result": "type-6" + }, + { + "name": "body::read", + "params": [ + "handle-3", + "type-7" + ], + "result": "type-8" + }, + { + "name": "body::write", + "params": [ + "handle-3", + "type-7" + ], + "result": "type-8" + }, + { + "name": "maybe-number", + "params": [], + "result": "type-8" + } + ], + "globals": [ + { + "name": "my-int", + "ty": "u32" + }, + { + "name": "my-request", + "ty": "handle-0" + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi.wit new file mode 100644 index 0000000..19db0f0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi.wit @@ -0,0 +1,174 @@ +enum clockid { + // The clock measuring real time. Time value zero corresponds with + // 1970-01-01T00:00:00Z. + realtime, + // The store-wide monotonic clock, which is defined as a clock measuring + // real time, whose value cannot be adjusted and which cannot have negative + // clock jumps. The epoch of this clock is undefined. The absolute time + // value of this clock therefore has no meaning. + monotonic, +} + +// Timestamp in nanoseconds. +type timestamp = u64 + +// Error codes returned by functions. +// Not all of these error codes are returned by the functions provided by this +// API/ some are used in higher-level library layers, and others are provided +// merely for alignment with POSIX. +enum errno { + // No error occurred. System call completed successfully. + success, + // Argument list too long. + toobig, + // Permission denied. + access, + // Address in use. + addrinuse, + // Address not available. + addrnotavail, + // Address family not supported. + afnosupport, + // Resource unavailable, or operation would block. + again, + // Connection already in progress. + already, + // Bad file descriptor. + badf, + // Bad message. + badmsg, + // Device or resource busy. + busy, + // Operation canceled. + canceled, + // No child processes. + child, + // Connection aborted. + connaborted, + // Connection refused. + connrefused, + // Connection reset. + connreset, + // Resource deadlock would occur. + deadlk, + // Destination address required. + destaddrreq, + // Mathematics argument out of domain of function. + dom, + // Reserved. + dquot, + // File exists. + exist, + // Bad address. + fault, + // File too large. + fbig, + // Host is unreachable. + hostunreach, + // Identifier removed. + idrm, + // Illegal byte sequence. + ilseq, + // Operation in progress. + inprogress, + // Interrupted function. + intr, + // Invalid argument. + inval, + // I/O error. + io, + // Socket is connected. + isconn, + // Is a directory. + isdir, + // Too many levels of symbolic links. + loop, + // File descriptor value too large. + mfile, + // Too many links. + mlink, + // Message too large. + msgsize, + // Reserved. + multihop, + // Filename too long. + nametoolong, + // Network is down. + netdown, + // Connection aborted by network. + netreset, + // Network unreachable. + netunreach, + // Too many files open in system. + nfile, + // No buffer space available. + nobufs, + // No such device. + nodev, + // No such file or directory. + noent, + // Executable file format error. + noexec, + // No locks available. + nolck, + // Reserved. + nolink, + // Not enough space. + nomem, + // No message of the desired type. + nomsg, + // Protocol not available. + noprotoopt, + // No space left on device. + nospc, + // Function not supported. + nosys, + // The socket is not connected. + notconn, + // Not a directory or a symbolic link to a directory. + notdir, + // Directory not empty. + notempty, + // State not recoverable. + notrecoverable, + // Not a socket. + notsock, + // Not supported, or operation not supported on socket. + notsup, + // Inappropriate I/O control operation. + notty, + // No such device or address. + nxio, + // Value too large to be stored in data type. + overflow, + // Previous owner died. + ownerdead, + // Operation not permitted. + perm, + // Broken pipe. + pipe, + // Protocol error. + proto, + // Protocol not supported. + protonosupport, + // Protocol wrong type for socket. + prototype, + // Result too large. + range, + // Read-only file system. + rofs, + // Invalid seek. + spipe, + // No such process. + srch, + // Reserved. + stale, + // Connection timed out. + timedout, + // Text file busy. + txtbsy, + // Cross-device link. + xdev, + // Extension: Capabilities insufficient. + notcapable, +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi.wit.result b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi.wit.result new file mode 100644 index 0000000..b9f495b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/parser/tests/ui/wasi.wit.result @@ -0,0 +1,104 @@ +{ + "types": [ + { + "idx": 0, + "name": "clockid", + "enum": { + "cases": [ + "realtime", + "monotonic" + ] + } + }, + { + "idx": 1, + "name": "timestamp", + "primitive": "u64" + }, + { + "idx": 2, + "name": "errno", + "enum": { + "cases": [ + "success", + "toobig", + "access", + "addrinuse", + "addrnotavail", + "afnosupport", + "again", + "already", + "badf", + "badmsg", + "busy", + "canceled", + "child", + "connaborted", + "connrefused", + "connreset", + "deadlk", + "destaddrreq", + "dom", + "dquot", + "exist", + "fault", + "fbig", + "hostunreach", + "idrm", + "ilseq", + "inprogress", + "intr", + "inval", + "io", + "isconn", + "isdir", + "loop", + "mfile", + "mlink", + "msgsize", + "multihop", + "nametoolong", + "netdown", + "netreset", + "netunreach", + "nfile", + "nobufs", + "nodev", + "noent", + "noexec", + "nolck", + "nolink", + "nomem", + "nomsg", + "noprotoopt", + "nospc", + "nosys", + "notconn", + "notdir", + "notempty", + "notrecoverable", + "notsock", + "notsup", + "notty", + "nxio", + "overflow", + "ownerdead", + "perm", + "pipe", + "proto", + "protonosupport", + "prototype", + "range", + "rofs", + "spipe", + "srch", + "stale", + "timedout", + "txtbsy", + "xdev", + "notcapable" + ] + } + } + ] +} \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm-impl/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm-impl/Cargo.toml new file mode 100644 index 0000000..ed311f6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm-impl/Cargo.toml @@ -0,0 +1,16 @@ +[package] +name = "wit-bindgen-rust-impl" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +proc-macro = true +doctest = false +test = false + +[dependencies] +proc-macro2 = "1.0" +syn = "1.0" +wit-bindgen-gen-core = { path = "../gen-core", version = "0.1" } +wit-bindgen-gen-rust-wasm = { path = "../gen-rust-wasm", version = "0.1" } diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm-impl/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm-impl/src/lib.rs new file mode 100644 index 0000000..69398c2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm-impl/src/lib.rs @@ -0,0 +1,148 @@ +use std::path::{Path, PathBuf}; + +use proc_macro::TokenStream; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{token, Token}; +use wit_bindgen_gen_core::{wit_parser::Interface, Direction, Files, Generator}; + +#[proc_macro] +pub fn import(input: TokenStream) -> TokenStream { + run(input, Direction::Import) +} + +#[proc_macro] +pub fn export(input: TokenStream) -> TokenStream { + run(input, Direction::Export) +} + +fn run(input: TokenStream, dir: Direction) -> TokenStream { + let input = syn::parse_macro_input!(input as Opts); + let mut gen = input.opts.build(); + let mut files = Files::default(); + let (imports, exports) = match dir { + Direction::Import => (input.interfaces, vec![]), + Direction::Export => (vec![], input.interfaces), + }; + gen.generate_all(&imports, &exports, &mut files); + let (_, contents) = files.iter().next().unwrap(); + let mut contents = std::str::from_utf8(contents).unwrap().to_string(); + + // Include a dummy `include_str!` for any files we read so rustc knows that + // we depend on the contents of those files. + let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + for file in input.files.iter() { + contents.push_str(&format!( + "const _: &str = include_str!(r#\"{}\"#);\n", + Path::new(&cwd).join(file).display() + )); + } + + contents.parse().unwrap() +} + +struct Opts { + opts: wit_bindgen_gen_rust_wasm::Opts, + interfaces: Vec, + files: Vec, +} + +mod kw { + syn::custom_keyword!(src); + syn::custom_keyword!(paths); + syn::custom_keyword!(unchecked); + syn::custom_keyword!(multi_module); +} + +impl Parse for Opts { + fn parse(input: ParseStream<'_>) -> Result { + let mut opts = wit_bindgen_gen_rust_wasm::Opts::default(); + let call_site = proc_macro2::Span::call_site(); + let mut files = Vec::new(); + let interfaces = if input.peek(token::Brace) { + let content; + syn::braced!(content in input); + let mut interfaces = Vec::new(); + let fields = Punctuated::::parse_terminated(&content)?; + for field in fields.into_pairs() { + match field.into_value() { + ConfigField::Unchecked => opts.unchecked = true, + ConfigField::MultiModule => opts.multi_module = true, + ConfigField::Interfaces(v) => interfaces = v, + } + } + if interfaces.is_empty() { + return Err(Error::new( + call_site, + "must either specify `src` or `paths` keys", + )); + } + interfaces + } else { + while !input.is_empty() { + let s = input.parse::()?; + files.push(s.value()); + } + let mut interfaces = Vec::new(); + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + for path in files.iter() { + let path = manifest_dir.join(path); + let iface = Interface::parse_file(path).map_err(|e| Error::new(call_site, e))?; + interfaces.push(iface); + } + interfaces + }; + Ok(Opts { + files, + opts, + interfaces, + }) + } +} + +enum ConfigField { + Interfaces(Vec), + Unchecked, + MultiModule, +} + +impl Parse for ConfigField { + fn parse(input: ParseStream<'_>) -> Result { + let l = input.lookahead1(); + if l.peek(kw::src) { + input.parse::()?; + let name; + syn::bracketed!(name in input); + let name = name.parse::()?; + input.parse::()?; + let s = input.parse::()?; + let interface = + Interface::parse(&name.value(), &s.value()).map_err(|e| Error::new(s.span(), e))?; + Ok(ConfigField::Interfaces(vec![interface])) + } else if l.peek(kw::paths) { + input.parse::()?; + input.parse::()?; + let paths; + let bracket = syn::bracketed!(paths in input); + let paths = Punctuated::::parse_terminated(&paths)?; + let values = paths.iter().map(|s| s.value()).collect::>(); + let mut interfaces = Vec::new(); + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + for value in &values { + let value = manifest_dir.join(value); + let interface = + Interface::parse_file(value).map_err(|e| Error::new(bracket.span, e))?; + interfaces.push(interface); + } + Ok(ConfigField::Interfaces(interfaces)) + } else if l.peek(kw::unchecked) { + input.parse::()?; + Ok(ConfigField::Unchecked) + } else if l.peek(kw::multi_module) { + input.parse::()?; + Ok(ConfigField::MultiModule) + } else { + Err(l.error()) + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/Cargo.toml new file mode 100644 index 0000000..eda2fab --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "wit-bindgen-rust" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[dependencies] +wit-bindgen-rust-impl = { path = "../rust-wasm-impl", optional = true } +async-trait = { version = "0.1.51", optional = true } +bitflags = "1.3" + +[features] +default = ["macros", "async"] +macros = ["wit-bindgen-rust-impl"] +async = ["async-trait"] diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/src/futures.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/src/futures.rs new file mode 100644 index 0000000..b06919f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/src/futures.rs @@ -0,0 +1,202 @@ +//! Helper library support for `async` wit functions, used for both + +use std::cell::RefCell; +use std::future::Future; +use std::mem; +use std::pin::Pin; +use std::rc::Rc; +use std::sync::Arc; +use std::task::*; + +#[cfg(target_arch = "wasm32")] +#[link(wasm_import_module = "canonical_abi")] +extern "C" { + pub fn async_export_done(ctx: i32, ptr: i32); +} + +#[cfg(not(target_arch = "wasm32"))] +pub unsafe extern "C" fn async_export_done(_ctx: i32, _ptr: i32) { + panic!("only supported on wasm"); +} + +struct PollingWaker { + state: RefCell, +} + +enum State { + Waiting(Pin>>), + Polling, + Woken, +} + +// These are valid for single-threaded WebAssembly because everything is +// single-threaded and send/sync don't matter much. This module will need +// an alternative implementation for threaded WebAssembly when that comes about +// to host runtimes off-the-web. +#[cfg(not(target_feature = "atomics"))] +unsafe impl Send for PollingWaker {} +#[cfg(not(target_feature = "atomics"))] +unsafe impl Sync for PollingWaker {} + +/// Runs the `future` provided to completion, polling the future whenever its +/// waker receives a call to `wake`. +pub fn execute(future: impl Future + 'static) { + let waker = Arc::new(PollingWaker { + state: RefCell::new(State::Waiting(Box::pin(future))), + }); + waker.wake() +} + +impl Wake for PollingWaker { + fn wake(self: Arc) { + let mut state = self.state.borrow_mut(); + let mut future = match mem::replace(&mut *state, State::Polling) { + // We are the first wake to come in to wake-up this future. This + // means that we need to actually poll the future, so leave the + // `Polling` state in place. + State::Waiting(future) => future, + + // Otherwise the future is either already polling or it was already + // woken while it was being polled, in both instances we reset the + // state back to `Woken` and then we return. This means that the + // future is owned by some previous stack frame and will drive the + // future as necessary. + State::Polling | State::Woken => { + *state = State::Woken; + return; + } + }; + drop(state); + + // Create the futures waker/context from ourselves, used for polling. + let waker = self.clone().into(); + let mut cx = Context::from_waker(&waker); + loop { + match future.as_mut().poll(&mut cx) { + // The future is finished! By returning here we destroy the + // future and release all of its resources. + Poll::Ready(()) => break, + + // The future has work yet-to-do, so continue below. + Poll::Pending => {} + } + + let mut state = self.state.borrow_mut(); + match *state { + // This means that we were not woken while we were polling and + // the state is as it was when we took out the future before. By + // `Pending` being returned at this point we're guaranteed that + // our waker will be woken up at some point in the future, which + // will come look at this future again. This means that we + // simply store our future and return, since this call to `wake` + // is now finished. + State::Polling => { + *state = State::Waiting(future); + break; + } + + // This means that we received a call to `wake` while we were + // polling. Ideally we'd enqueue some sort of microtask-tick + // here or something like that but for now we just loop around + // and poll again. + State::Woken => {} + + // This shouldn't be possible since we own the future, and no + // one else should insert another future here. + State::Waiting(_) => unreachable!(), + } + } + } +} + +pub struct Oneshot { + inner: Rc>, +} + +pub struct Sender { + inner: Rc>, +} + +struct OneshotInner { + state: RefCell>, +} + +enum OneshotState { + Start, + Waiting(Waker), + Done(T), +} + +impl Oneshot { + /// Returns a new "oneshot" channel as well as a completion callback. + pub fn new() -> (Oneshot, Sender) { + let inner = Rc::new(OneshotInner { + state: RefCell::new(OneshotState::Start), + }); + ( + Oneshot { + inner: Rc::clone(&inner), + }, + Sender { inner }, + ) + } +} + +impl Future for Oneshot { + type Output = T; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut state = self.inner.state.borrow_mut(); + match mem::replace(&mut *state, OneshotState::Start) { + OneshotState::Done(t) => Poll::Ready(t), + OneshotState::Waiting(_) | OneshotState::Start => { + *state = OneshotState::Waiting(cx.waker().clone()); + Poll::Pending + } + } + } +} + +impl Sender { + pub fn into_usize(self) -> usize { + Rc::into_raw(self.inner) as usize + } + + pub unsafe fn from_usize(ptr: usize) -> Sender { + Sender { + inner: Rc::from_raw(ptr as *const _), + } + } + + pub fn send(self, val: T) { + let mut state = self.inner.state.borrow_mut(); + let prev = mem::replace(&mut *state, OneshotState::Done(val)); + // Must `drop` before the `wake` below because waking may induce + // polling which would induce another `borrow_mut` which would + // conflict with this `borrow_mut` otherwise. + drop(state); + + match prev { + // nothing has polled the returned future just yet, so we just + // filled in the result of the computation. Presumably this will + // get picked up at some point in the future. + OneshotState::Start => {} + + // Something was waiting for the result, so we wake the waker + // here which, for wasm, will likely induce polling immediately. + OneshotState::Waiting(waker) => waker.wake(), + + // Shouldn't be possible, this is the only closure that writes + // `Done` and this can only be invoked once. + OneshotState::Done(_) => unreachable!(), + } + } +} + +impl Drop for OneshotInner { + fn drop(&mut self) { + if let OneshotState::Waiting(waker) = &*self.state.borrow() { + waker.wake_by_ref(); + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/src/lib.rs new file mode 100644 index 0000000..d7c4133 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/rust-wasm/src/lib.rs @@ -0,0 +1,203 @@ +use std::fmt; +use std::marker; +use std::mem; +use std::ops::Deref; + +#[cfg(feature = "macros")] +pub use wit_bindgen_rust_impl::{export, import}; + +#[cfg(feature = "async")] +pub use async_trait::async_trait; +#[cfg(feature = "async")] +mod futures; + +// Re-export `bitflags` so that we can reference it from macros. +#[doc(hidden)] +pub use bitflags; + +/// A type for handles to resources that appear in exported functions. +/// +/// This type is used as `Handle` for argument types and return values of +/// exported functions when exposing a Rust-defined resource. A `Handle` +/// represents an owned reference count on the interface-types-managed resource. +/// It's similar to an `Rc` in Rust. Internally a `Handle` can provide +/// access to `&T` when `T` is defined in the current module. +pub struct Handle { + val: i32, + _marker: marker::PhantomData, +} + +impl Handle { + /// Creates a new handle around the specified value. + /// + /// Note that the lifetime of `T` will afterwards be managed by the + /// interface types runtime. The host may hold references to `T` that wasm + /// isn't necessarily aware of, preventing its destruction. Nevertheless + /// though the `Drop for T` implementation will still be run when there are + /// no more references to `T`. + pub fn new(val: T) -> Handle + where + T: LocalHandle, + { + unsafe { Handle::from_raw(T::new(Box::into_raw(Box::new(val)) as i32)) } + } + + /// Consumes a handle and returns the underlying raw wasm i32 descriptor. + /// + /// Note that this, if used improperly, will leak the resource `T`. This + /// generally should be avoided unless you're calling raw ABI bindings and + /// managing the lifetime manually. + pub fn into_raw(handle: Handle) -> i32 { + let ret = handle.val; + mem::forget(handle); + ret + } + + /// Returns the raw underlying handle value for this handle. + /// + /// This typically isn't necessary to interact with, but can be useful when + /// interacting with raw ABI bindings. + pub fn as_raw(handle: &Handle) -> i32 { + handle.val + } + + /// Unsafely assumes that the given integer descriptor is a handle for `T`. + /// + /// This is unsafe because no validation is performed to ensure that `val` + /// is actually a valid descriptor for `T`. + pub unsafe fn from_raw(val: i32) -> Handle { + Handle { + val, + _marker: marker::PhantomData, + } + } +} + +impl Deref for Handle { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*(T::get(self.val) as *const T) } + } +} + +impl From for Handle { + fn from(val: T) -> Handle { + Handle::new(val) + } +} + +impl Clone for Handle { + fn clone(&self) -> Self { + unsafe { Handle::from_raw(T::clone(self.val)) } + } +} + +impl fmt::Debug for Handle { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Handle").field("val", &self.val).finish() + } +} + +impl Drop for Handle { + fn drop(&mut self) { + T::drop(self.val); + } +} + +/// A trait for types that can show up as the `T` in `Handle`. +/// +/// This trait is automatically synthesized for exported handles and typically +/// shouldn't be implemented manually. +pub unsafe trait HandleType { + #[doc(hidden)] + fn clone(val: i32) -> i32; + #[doc(hidden)] + fn drop(val: i32); +} + +/// An extension of the [`HandleType`] trait for locally-defined types. +/// +/// This trait may not stick around forever... +pub unsafe trait LocalHandle: HandleType { + #[doc(hidden)] + fn new(val: i32) -> i32; + #[doc(hidden)] + fn get(val: i32) -> i32; +} + +#[doc(hidden)] +pub mod rt { + use std::alloc::{self, Layout}; + + #[cfg(feature = "async")] + pub use crate::futures::*; + + #[no_mangle] + unsafe extern "C" fn canonical_abi_realloc( + old_ptr: *mut u8, + old_len: usize, + align: usize, + new_len: usize, + ) -> *mut u8 { + let layout; + let ptr = if old_len == 0 { + if new_len == 0 { + return align as *mut u8; + } + layout = Layout::from_size_align_unchecked(new_len, align); + alloc::alloc(layout) + } else { + layout = Layout::from_size_align_unchecked(old_len, align); + alloc::realloc(old_ptr, layout, new_len) + }; + if ptr.is_null() { + alloc::handle_alloc_error(layout); + } + return ptr; + } + + #[no_mangle] + pub unsafe extern "C" fn canonical_abi_free(ptr: *mut u8, len: usize, align: usize) { + if len == 0 { + return; + } + let layout = Layout::from_size_align_unchecked(len, align); + alloc::dealloc(ptr, layout); + } + + macro_rules! as_traits { + ($(($trait_:ident $func:ident $ty:ident <=> $($tys:ident)*))*) => ($( + pub fn $func(t: T) -> $ty { + t.$func() + } + + pub trait $trait_ { + fn $func(self) -> $ty; + } + + impl<'a, T: Copy + $trait_> $trait_ for &'a T { + fn $func(self) -> $ty{ + (*self).$func() + } + } + + $( + impl $trait_ for $tys { + #[inline] + fn $func(self) -> $ty { + self as $ty + } + } + )* + + )*) + } + + as_traits! { + (AsI64 as_i64 i64 <=> i64 u64) + (AsI32 as_i32 i32 <=> i32 u32 i16 u16 i8 u8 char usize) + (AsF32 as_f32 f32 <=> f32) + (AsF64 as_f64 f64 <=> f64) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/Cargo.toml new file mode 100644 index 0000000..84a2a04 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/Cargo.toml @@ -0,0 +1,38 @@ +[package] +name = "test-helpers" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" +publish = false + +[lib] +proc-macro = true +doctest = false +test = false + +[dependencies] +backtrace = "0.3" +heck = "0.3" +ignore = "0.4" +proc-macro2 = "1.0.27" +quote = "1.0.9" +wit-bindgen-gen-core = { path = '../gen-core' } +wit-bindgen-gen-rust-wasm = { path = '../gen-rust-wasm', optional = true } +wit-bindgen-gen-wasmtime = { path = '../gen-wasmtime', optional = true } +wit-bindgen-gen-wasmtime-py = { path = '../gen-wasmtime-py', optional = true } +wit-bindgen-gen-js = { path = '../gen-js', optional = true } +wit-bindgen-gen-c = { path = '../gen-c', optional = true } +wit-bindgen-gen-spidermonkey = { path = '../gen-spidermonkey', optional = true } +wit-parser = { path = '../parser' } +filetime = "0.2" + +[build-dependencies] +wit-bindgen-gen-c = { path = '../gen-c' } +wit-bindgen-gen-spidermonkey = { path = '../gen-spidermonkey' } +wit-bindgen-gen-core = { path = '../gen-core' } + +[features] +default = ['wasm-rust', 'wasm-c', 'wasm-spidermonkey'] +wasm-rust = [] +wasm-c = [] +wasm-spidermonkey = [] diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/build.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/build.rs new file mode 100644 index 0000000..542dfe4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/build.rs @@ -0,0 +1,168 @@ +use std::env; +use std::fs; +use std::path::PathBuf; +use std::process::Command; +use wit_bindgen_gen_core::{wit_parser::Interface, Generator}; + +fn main() { + let out_dir = PathBuf::from(std::env::var_os("OUT_DIR").unwrap()); + + let mut wasms = Vec::new(); + + if cfg!(feature = "wasm-rust") { + let mut cmd = Command::new("cargo"); + cmd.arg("build") + .current_dir("../test-rust-wasm") + .arg("--target=wasm32-wasi") + .env("CARGO_TARGET_DIR", &out_dir) + .env("CARGO_PROFILE_DEV_DEBUG", "1") + .env("RUSTFLAGS", "-Clink-args=--export-table") + .env_remove("CARGO_ENCODED_RUSTFLAGS"); + let status = cmd.status().unwrap(); + assert!(status.success()); + for file in out_dir.join("wasm32-wasi/debug").read_dir().unwrap() { + let file = file.unwrap().path(); + if file.extension().and_then(|s| s.to_str()) != Some("wasm") { + continue; + } + wasms.push(( + "rust", + file.file_stem().unwrap().to_str().unwrap().to_string(), + file.to_str().unwrap().to_string(), + )); + + let dep_file = file.with_extension("d"); + let deps = fs::read_to_string(&dep_file).expect("failed to read dep file"); + for dep in deps + .splitn(2, ":") + .skip(1) + .next() + .unwrap() + .split_whitespace() + { + println!("cargo:rerun-if-changed={}", dep); + } + } + println!("cargo:rerun-if-changed=../test-rust-wasm/Cargo.toml"); + } + + if cfg!(feature = "wasm-c") { + for test_dir in fs::read_dir("../../tests/runtime").unwrap() { + let test_dir = test_dir.unwrap().path(); + let c_impl = test_dir.join("wasm.c"); + if !c_impl.exists() { + continue; + } + let imports = test_dir.join("imports.wit"); + let exports = test_dir.join("exports.wit"); + println!("cargo:rerun-if-changed={}", imports.display()); + println!("cargo:rerun-if-changed={}", exports.display()); + println!("cargo:rerun-if-changed={}", c_impl.display()); + + let import = Interface::parse_file(&test_dir.join("imports.wit")).unwrap(); + let export = Interface::parse_file(&test_dir.join("exports.wit")).unwrap(); + let mut files = Default::default(); + // TODO: should combine this into one + wit_bindgen_gen_c::Opts::default() + .build() + .generate_all(&[import], &[], &mut files); + wit_bindgen_gen_c::Opts::default() + .build() + .generate_all(&[], &[export], &mut files); + + let out_dir = out_dir.join(format!( + "c-{}", + test_dir.file_name().unwrap().to_str().unwrap() + )); + drop(fs::remove_dir_all(&out_dir)); + fs::create_dir(&out_dir).unwrap(); + for (file, contents) in files.iter() { + let dst = out_dir.join(file); + fs::write(dst, contents).unwrap(); + } + + let path = PathBuf::from(env::var_os("WASI_SDK_PATH").expect( + "point the `WASI_SDK_PATH` environment variable to the path of your wasi-sdk", + )); + let mut cmd = Command::new(path.join("bin/clang")); + let out_wasm = out_dir.join("c.wasm"); + cmd.arg("--sysroot").arg(path.join("share/wasi-sysroot")); + cmd.arg(c_impl) + .arg(out_dir.join("imports.c")) + .arg(out_dir.join("exports.c")) + .arg("-I") + .arg(&out_dir) + .arg("-Wall") + .arg("-Wextra") + .arg("-Werror") + .arg("-Wno-unused-parameter") + .arg("-mexec-model=reactor") + .arg("-g") + .arg("-o") + .arg(&out_wasm); + println!("{:?}", cmd); + let output = match cmd.output() { + Ok(output) => output, + Err(e) => panic!("failed to spawn compiler: {}", e), + }; + + if !output.status.success() { + println!("status: {}", output.status); + println!("stdout: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stdout)); + println!("stderr: ------------------------------------------"); + println!("{}", String::from_utf8_lossy(&output.stderr)); + panic!("failed to compile"); + } + + wasms.push(( + "c", + test_dir.file_stem().unwrap().to_str().unwrap().to_string(), + out_wasm.to_str().unwrap().to_string(), + )); + } + } + + if cfg!(feature = "wasm-spidermonkey") { + for test_dir in fs::read_dir("../../tests/runtime").unwrap() { + let test_dir = test_dir.unwrap().path(); + let js_impl = test_dir.join("wasm.js"); + if !js_impl.exists() { + continue; + } + let imports = test_dir.join("imports.wit"); + let exports = test_dir.join("exports.wit"); + println!("cargo:rerun-if-changed={}", imports.display()); + println!("cargo:rerun-if-changed={}", exports.display()); + println!("cargo:rerun-if-changed={}", js_impl.display()); + + let import = Interface::parse_file(&test_dir.join("imports.wit")).unwrap(); + let export = Interface::parse_file(&test_dir.join("exports.wit")).unwrap(); + let mut files = Default::default(); + let js = fs::read_to_string(&js_impl).unwrap(); + let mut gen = wit_bindgen_gen_spidermonkey::SpiderMonkeyWasm::new("wasm.js", &js); + gen.import_spidermonkey(true); + gen.generate_all(&[import], &[export], &mut files); + + let out_dir = out_dir.join(format!( + "js-{}", + test_dir.file_name().unwrap().to_str().unwrap() + )); + drop(fs::remove_dir_all(&out_dir)); + fs::create_dir(&out_dir).unwrap(); + for (file, contents) in files.iter() { + let dst = out_dir.join(file); + fs::write(dst, contents).unwrap(); + } + + wasms.push(( + "spidermonkey", + test_dir.file_stem().unwrap().to_str().unwrap().to_string(), + out_dir.join("wasm.wasm").to_str().unwrap().to_string(), + )); + } + } + + let src = format!("const WASMS: &[(&str, &str, &str)] = &{:?};", wasms); + std::fs::write(out_dir.join("wasms.rs"), src).unwrap(); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/src/lib.rs new file mode 100644 index 0000000..6de97bc --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-helpers/src/lib.rs @@ -0,0 +1,556 @@ +use ignore::gitignore::GitignoreBuilder; +use proc_macro::{TokenStream, TokenTree}; +use std::env; +use std::fs; +use std::path::{Path, PathBuf}; +use std::time::{Duration, SystemTime}; +use wit_bindgen_gen_core::{Direction, Generator}; + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-rust-wasm")] +pub fn codegen_rust_wasm_import(input: TokenStream) -> TokenStream { + gen_rust( + input, + Direction::Import, + &[ + ( + "import", + || wit_bindgen_gen_rust_wasm::Opts::default().build(), + |_| quote::quote!(), + ), + ( + "import-unchecked", + || { + let mut opts = wit_bindgen_gen_rust_wasm::Opts::default(); + opts.unchecked = true; + opts.build() + }, + |_| quote::quote!(), + ), + ], + ) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-rust-wasm")] +pub fn codegen_rust_wasm_export(input: TokenStream) -> TokenStream { + use heck::*; + use std::collections::BTreeMap; + use wit_parser::{FunctionKind, Type, TypeDefKind}; + + return gen_rust( + input, + Direction::Export, + &[ + ( + "export", + || wit_bindgen_gen_rust_wasm::Opts::default().build(), + gen_extra, + ), + ( + "export-unchecked", + || { + let mut opts = wit_bindgen_gen_rust_wasm::Opts::default(); + opts.unchecked = true; + opts.symbol_namespace = "unchecked".to_string(); + opts.build() + }, + gen_extra, + ), + ], + ); + + fn gen_extra(iface: &wit_parser::Interface) -> proc_macro2::TokenStream { + let mut ret = quote::quote!(); + if iface.resources.len() == 0 && iface.functions.len() == 0 { + return ret; + } + + let snake = quote::format_ident!("{}", iface.name.to_snake_case()); + let camel = quote::format_ident!("{}", iface.name.to_camel_case()); + + for (_, r) in iface.resources.iter() { + let name = quote::format_ident!("{}", r.name.to_camel_case()); + ret.extend(quote::quote!(pub struct #name;)); + } + + let mut methods = Vec::new(); + let mut resources = BTreeMap::new(); + + let mut async_trait = quote::quote!(); + for f in iface.functions.iter() { + let name = quote::format_ident!("{}", f.item_name().to_snake_case()); + let mut params = f + .params + .iter() + .map(|(_, t)| quote_ty(true, iface, t)) + .collect::>(); + let ret = quote_ty(false, iface, &f.result); + let mut self_ = quote::quote!(); + if let FunctionKind::Method { .. } = &f.kind { + params.remove(0); + self_ = quote::quote!(&self,); + } + let async_ = if f.is_async { + async_trait = quote::quote!(#[wit_bindgen_rust::async_trait(?Send)]); + quote::quote!(async) + } else { + quote::quote!() + }; + let method = quote::quote! { + #async_ fn #name(#self_ #(_: #params),*) -> #ret { + loop {} + } + }; + match &f.kind { + FunctionKind::Freestanding => methods.push(method), + FunctionKind::Static { resource, .. } | FunctionKind::Method { resource, .. } => { + resources + .entry(*resource) + .or_insert(Vec::new()) + .push(method); + } + } + } + ret.extend(quote::quote! { + struct #camel; + + #async_trait + impl #snake::#camel for #camel { + #(#methods)* + } + }); + for (id, methods) in resources { + let name = quote::format_ident!("{}", iface.resources[id].name.to_camel_case()); + ret.extend(quote::quote! { + #async_trait + impl #snake::#name for #name { + #(#methods)* + } + }); + } + + ret + } + + fn quote_ty( + param: bool, + iface: &wit_parser::Interface, + ty: &wit_parser::Type, + ) -> proc_macro2::TokenStream { + match *ty { + Type::Unit => quote::quote! { () }, + Type::Bool => quote::quote! { bool }, + Type::U8 => quote::quote! { u8 }, + Type::S8 => quote::quote! { i8 }, + Type::U16 => quote::quote! { u16 }, + Type::S16 => quote::quote! { i16 }, + Type::U32 => quote::quote! { u32 }, + Type::S32 => quote::quote! { i32 }, + Type::U64 => quote::quote! { u64 }, + Type::S64 => quote::quote! { i64 }, + Type::Float32 => quote::quote! { f32 }, + Type::Float64 => quote::quote! { f64 }, + Type::Char => quote::quote! { char }, + Type::String => quote::quote! { String }, + Type::Handle(resource) => { + let name = + quote::format_ident!("{}", iface.resources[resource].name.to_camel_case()); + quote::quote! { wit_bindgen_rust::Handle<#name> } + } + Type::Id(id) => quote_id(param, iface, id), + } + } + + fn quote_id( + param: bool, + iface: &wit_parser::Interface, + id: wit_parser::TypeId, + ) -> proc_macro2::TokenStream { + let ty = &iface.types[id]; + if let Some(name) = &ty.name { + let name = quote::format_ident!("{}", name.to_camel_case()); + let module = quote::format_ident!("{}", iface.name.to_snake_case()); + return quote::quote! { #module::#name }; + } + match &ty.kind { + TypeDefKind::Type(t) => quote_ty(param, iface, t), + TypeDefKind::List(t) => { + let t = quote_ty(param, iface, t); + quote::quote! { Vec<#t> } + } + TypeDefKind::Flags(_) => panic!("unknown flags"), + TypeDefKind::Enum(_) => panic!("unknown enum"), + TypeDefKind::Record(_) => panic!("unknown record"), + TypeDefKind::Variant(_) => panic!("unknown variant"), + TypeDefKind::Union(_) => panic!("unknown union"), + TypeDefKind::Tuple(t) => { + let fields = t.types.iter().map(|ty| quote_ty(param, iface, ty)); + quote::quote! { (#(#fields,)*) } + } + TypeDefKind::Option(ty) => { + let ty = quote_ty(param, iface, ty); + quote::quote! { Option<#ty> } + } + TypeDefKind::Expected(e) => { + let ok = quote_ty(param, iface, &e.ok); + let err = quote_ty(param, iface, &e.err); + quote::quote! { Result<#ok, #err> } + } + TypeDefKind::Future(_) => todo!("unknown future"), + TypeDefKind::Stream(_) => todo!("unknown stream"), + } + } +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmtime")] +pub fn codegen_wasmtime_export(input: TokenStream) -> TokenStream { + gen_rust( + input, + Direction::Export, + &[ + ( + "export", + || wit_bindgen_gen_wasmtime::Opts::default().build(), + |_| quote::quote!(), + ), + ( + "export-tracing-and-custom-error", + || { + let mut opts = wit_bindgen_gen_wasmtime::Opts::default(); + opts.tracing = true; + opts.custom_error = true; + opts.build() + }, + |_| quote::quote!(), + ), + ( + "export-async", + || { + let mut opts = wit_bindgen_gen_wasmtime::Opts::default(); + opts.async_ = wit_bindgen_gen_wasmtime::Async::All; + opts.build() + }, + |_| quote::quote!(), + ), + ], + ) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmtime")] +pub fn codegen_wasmtime_import(input: TokenStream) -> TokenStream { + gen_rust( + input, + Direction::Import, + &[ + ( + "import", + || wit_bindgen_gen_wasmtime::Opts::default().build(), + |_| quote::quote!(), + ), + ( + "import-async", + || { + let mut opts = wit_bindgen_gen_wasmtime::Opts::default(); + opts.async_ = wit_bindgen_gen_wasmtime::Async::All; + opts.build() + }, + |_| quote::quote!(), + ), + ], + ) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-js")] +pub fn codegen_js_export(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Export, "export", || { + wit_bindgen_gen_js::Opts::default().build() + }) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-js")] +pub fn codegen_js_import(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Import, "import", || { + wit_bindgen_gen_js::Opts::default().build() + }) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-c")] +pub fn codegen_c_import(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Import, "import", || { + wit_bindgen_gen_c::Opts::default().build() + }) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-c")] +pub fn codegen_c_export(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Export, "export", || { + wit_bindgen_gen_c::Opts::default().build() + }) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmtime-py")] +pub fn codegen_py_export(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Export, "export", || { + wit_bindgen_gen_wasmtime_py::Opts::default().build() + }) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmtime-py")] +pub fn codegen_py_import(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Import, "import", || { + wit_bindgen_gen_wasmtime_py::Opts::default().build() + }) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-spidermonkey")] +pub fn codegen_spidermonkey_import(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Import, "import", || { + let mut gen = wit_bindgen_gen_spidermonkey::SpiderMonkeyWasm::new("foo.js", ""); + gen.import_spidermonkey(true); + gen + }) +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-spidermonkey")] +pub fn codegen_spidermonkey_export(input: TokenStream) -> TokenStream { + gen_verify(input, Direction::Export, "export", || { + let mut gen = wit_bindgen_gen_spidermonkey::SpiderMonkeyWasm::new("foo.js", ""); + gen.import_spidermonkey(true); + gen + }) +} + +fn generate_tests( + input: TokenStream, + dir: &str, + mkgen: impl Fn(&Path) -> (G, Direction), +) -> Vec<(wit_parser::Interface, PathBuf, PathBuf)> +where + G: Generator, +{ + static INIT: std::sync::Once = std::sync::Once::new(); + INIT.call_once(|| { + let prev = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + eprintln!("panic: {:?}", backtrace::Backtrace::new()); + prev(info); + })); + }); + + let mut builder = GitignoreBuilder::new("tests"); + for token in input { + let lit = match token { + TokenTree::Literal(l) => l.to_string(), + _ => panic!("invalid input"), + }; + assert!(lit.starts_with("\"")); + assert!(lit.ends_with("\"")); + builder.add_line(None, &lit[1..lit.len() - 1]).unwrap(); + } + let ignore = builder.build().unwrap(); + let tests = ignore::Walk::new("tests/codegen").filter_map(|d| { + let d = d.unwrap(); + let path = d.path(); + match ignore.matched(path, d.file_type().map(|d| d.is_dir()).unwrap_or(false)) { + ignore::Match::None => None, + ignore::Match::Ignore(_) => Some(d.into_path()), + ignore::Match::Whitelist(_) => None, + } + }); + let mut out_dir = PathBuf::from(env::var_os("OUT_DIR").expect("OUT_DIR not set")); + out_dir.push(dir); + let mut sources = Vec::new(); + let cwd = env::current_dir().unwrap(); + for test in tests { + let (mut gen, dir) = mkgen(&test); + let mut files = Default::default(); + let iface = wit_parser::Interface::parse_file(&test).unwrap(); + let (mut imports, mut exports) = match dir { + Direction::Import => (vec![iface], vec![]), + Direction::Export => (vec![], vec![iface]), + }; + gen.generate_all(&imports, &exports, &mut files); + + let dst = out_dir.join(test.file_stem().unwrap()); + drop(fs::remove_dir_all(&dst)); + fs::create_dir_all(&dst).unwrap(); + for (file, contents) in files.iter() { + write_old_file(dst.join(file), contents); + } + sources.push(( + imports.pop().or(exports.pop()).unwrap(), + dst, + cwd.join(test), + )); + } + sources +} + +// Files written in this proc-macro are loaded as source code in Rust. This is +// done to assist with compiler error messages so there's an actual file to go +// look at, but this causes issues with mtime-tracking in Cargo since it appears +// to Cargo that a file was modified after the build started, which causes Cargo +// to rebuild on subsequent builds. All our dependencies are tracked via the +// inputs to the proc-macro itself, so there's no need for Cargo to track these +// files, so we specifically set the mtime of the file to something older to +// prevent triggering rebuilds. +fn write_old_file(path: impl AsRef, contents: impl AsRef<[u8]>) { + let path = path.as_ref(); + fs::write(path, contents).unwrap(); + let now = filetime::FileTime::from_system_time(SystemTime::now() - Duration::from_secs(600)); + filetime::set_file_mtime(path, now).unwrap(); +} + +#[allow(dead_code)] +fn gen_rust( + // input to the original procedural macro + input: TokenStream, + // whether we're generating bindings for imports or exports + dir: Direction, + // a list of tests, tuples of: + // * name of the test (directory to generate code into) + // * method to create the `G` which will generate code + // * method to generate auxiliary tokens to place in the module, + // optionally. + tests: &[( + &'static str, + fn() -> G, + fn(&wit_parser::Interface) -> proc_macro2::TokenStream, + )], +) -> TokenStream { + let mut ret = proc_macro2::TokenStream::new(); + for (name, mk, extra) in tests { + let tests = generate_tests(input.clone(), name, |_path| (mk(), dir)); + let mut sources = proc_macro2::TokenStream::new(); + for (iface, gen_dir, _input_wit) in tests.iter() { + let test = gen_dir.join("bindings.rs"); + let test = test.display().to_string(); + sources.extend(quote::quote!(include!(#test);)); + let extra = extra(iface); + if extra.is_empty() { + continue; + } + let test = gen_dir.join("extra.rs"); + let test = test.display().to_string(); + sources.extend(quote::quote!(include!(#test);)); + write_old_file(&test, extra.to_string()); + } + let name = quote::format_ident!("{}", name.replace("-", "_")); + ret.extend(quote::quote!( mod #name { #sources } )); + } + ret.into() +} + +#[allow(dead_code)] +fn gen_verify( + input: TokenStream, + dir: Direction, + name: &str, + mkgen: fn() -> G, +) -> TokenStream { + use heck::*; + + let tests = generate_tests(input, name, |_path| (mkgen(), dir)); + let tests = tests.iter().map(|(iface, test, wit)| { + let test = test.display().to_string(); + let wit = wit.display().to_string(); + let name = quote::format_ident!("{}", iface.name.to_snake_case()); + let iface_name = iface.name.to_kebab_case(); + quote::quote! { + #[test] + fn #name() { + const _: &str = include_str!(#wit); + crate::verify(#test, #iface_name); + } + } + }); + (quote::quote!(#(#tests)*)).into() +} + +include!(concat!(env!("OUT_DIR"), "/wasms.rs")); + +/// Invoked as `runtime_tests!("js")` to run a top-level `execute` function with +/// all host tests that use the "js" extension. +#[proc_macro] +pub fn runtime_tests(input: TokenStream) -> TokenStream { + let host_extension = input.to_string(); + let host_extension = host_extension.trim_matches('"'); + let host_file = format!("host.{}", host_extension); + let mut tests = Vec::new(); + let cwd = std::env::current_dir().unwrap(); + for entry in std::fs::read_dir(cwd.join("tests/runtime")).unwrap() { + let entry = entry.unwrap().path(); + if !entry.join(&host_file).exists() { + continue; + } + let name_str = entry.file_name().unwrap().to_str().unwrap(); + for (lang, name, wasm) in WASMS { + if *name != name_str { + continue; + } + let name_str = format!("{}_{}", name_str, lang); + let name = quote::format_ident!("{}", name_str); + let host_file = entry.join(&host_file).to_str().unwrap().to_string(); + let import_wit = entry.join("imports.wit").to_str().unwrap().to_string(); + let export_wit = entry.join("exports.wit").to_str().unwrap().to_string(); + tests.push(quote::quote! { + #[test] + fn #name() { + crate::execute( + #name_str, + #wasm.as_ref(), + #host_file.as_ref(), + #import_wit.as_ref(), + #export_wit.as_ref(), + ) + } + }); + } + } + + (quote::quote!(#(#tests)*)).into() +} + +#[proc_macro] +#[cfg(feature = "wit-bindgen-gen-wasmtime")] +pub fn runtime_tests_wasmtime(_input: TokenStream) -> TokenStream { + let mut tests = Vec::new(); + let cwd = std::env::current_dir().unwrap(); + for entry in std::fs::read_dir(cwd.join("tests/runtime")).unwrap() { + let entry = entry.unwrap().path(); + if !entry.join("host.rs").exists() { + continue; + } + let name_str = entry.file_name().unwrap().to_str().unwrap(); + for (lang, name, wasm) in WASMS { + if *name != name_str { + continue; + } + let name = quote::format_ident!("{}_{}", name_str, lang); + let host_file = entry.join("host.rs").to_str().unwrap().to_string(); + tests.push(quote::quote! { + mod #name { + include!(#host_file); + + #[test] + fn test() -> anyhow::Result<()> { + run(#wasm) + } + } + }); + } + } + + (quote::quote!(#(#tests)*)).into() +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/Cargo.toml new file mode 100644 index 0000000..efb40d7 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/Cargo.toml @@ -0,0 +1,57 @@ +[package] +name = "test-rust-wasm" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" +publish = false + +[dependencies] +futures-util = { version = "0.3.17", default-features = true } +wit-bindgen-rust = { path = "../rust-wasm" } + +[features] +unchecked = [] + +[[bin]] +name = "smoke" +test = false + +[[bin]] +name = "numbers" +test = false + +[[bin]] +name = "records" +test = false + +[[bin]] +name = "unions" +test = false + +[[bin]] +name = "variants" +test = false + +[[bin]] +name = "lists" +test = false + +[[bin]] +name = "handles" +test = false + +[[bin]] +name = "flavorful" +test = false + +[[bin]] +name = "invalid" +test = false + +[[bin]] +name = "async_functions" +test = false + +[[bin]] +name = "many_arguments" +test = false diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/async_functions.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/async_functions.rs new file mode 100644 index 0000000..6e4f29e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/async_functions.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/async_functions/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/flavorful.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/flavorful.rs new file mode 100644 index 0000000..c820cb1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/flavorful.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/flavorful/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/handles.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/handles.rs new file mode 100644 index 0000000..4d57328 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/handles.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/handles/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/invalid.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/invalid.rs new file mode 100644 index 0000000..bcbe1a5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/invalid.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/invalid/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/lists.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/lists.rs new file mode 100644 index 0000000..2f58681 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/lists.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/lists/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/many_arguments.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/many_arguments.rs new file mode 100644 index 0000000..6033fcc --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/many_arguments.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/many_arguments/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/numbers.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/numbers.rs new file mode 100644 index 0000000..3589a56 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/numbers.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/numbers/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/records.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/records.rs new file mode 100644 index 0000000..01323e6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/records.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/records/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/smoke.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/smoke.rs new file mode 100644 index 0000000..6856dcb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/smoke.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/smoke/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/unions.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/unions.rs new file mode 100644 index 0000000..c50c83d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/unions.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/unions/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/variants.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/variants.rs new file mode 100644 index 0000000..a3daf32 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/bin/variants.rs @@ -0,0 +1,3 @@ +include!("../../../../tests/runtime/variants/wasm.rs"); + +fn main() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/lib.rs new file mode 100644 index 0000000..c8aab01 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/test-rust-wasm/src/lib.rs @@ -0,0 +1,47 @@ +//! A small global allocator implementation which is intended to keep track of +//! the number of allocated bytes to ensure that all our integration glue indeed +//! manages memory correctly and doesn't leak anything. + +use std::alloc::{GlobalAlloc, Layout, System}; +use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; + +#[global_allocator] +static ALLOC: A = A; + +static ALLOC_AMT: AtomicUsize = AtomicUsize::new(0); + +struct A; + +unsafe impl GlobalAlloc for A { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + let ptr = System.alloc(layout); + if !ptr.is_null() { + ALLOC_AMT.fetch_add(layout.size(), SeqCst); + } + return ptr; + } + + unsafe fn dealloc(&self, ptr: *mut u8, layout: Layout) { + // Poison all deallocations to try to catch any use-after-free in the + // bindings as early as possible. + std::ptr::write_bytes(ptr, 0xde, layout.size()); + ALLOC_AMT.fetch_sub(layout.size(), SeqCst); + System.dealloc(ptr, layout) + } +} + +pub fn get() -> usize { + ALLOC_AMT.load(SeqCst) +} + +pub fn guard() -> impl Drop { + struct A(usize); + + impl Drop for A { + fn drop(&mut self) { + assert_eq!(get(), self.0); + } + } + + A(get()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime-impl/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime-impl/Cargo.toml new file mode 100644 index 0000000..13e93b6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime-impl/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "wit-bindgen-wasmtime-impl" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[lib] +proc-macro = true +doctest = false +test = false + +[dependencies] +proc-macro2 = "1.0" +syn = "1.0" +wit-bindgen-gen-core = { path = "../gen-core", version = "0.1" } +wit-bindgen-gen-wasmtime = { path = "../gen-wasmtime", version = "0.1" } + +[features] +tracing = [] +async = [] diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime-impl/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime-impl/src/lib.rs new file mode 100644 index 0000000..4635e8b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime-impl/src/lib.rs @@ -0,0 +1,179 @@ +use std::path::{Path, PathBuf}; + +use proc_macro::TokenStream; +use syn::parse::{Error, Parse, ParseStream, Result}; +use syn::punctuated::Punctuated; +use syn::{token, Token}; +use wit_bindgen_gen_core::{wit_parser::Interface, Direction, Files, Generator}; +use wit_bindgen_gen_wasmtime::Async; + +/// Generate code to support consuming the given interfaces, importaing them +/// from wasm modules. +#[proc_macro] +pub fn import(input: TokenStream) -> TokenStream { + run(input, Direction::Import) +} + +/// Generate code to support implementing the given interfaces and exporting +/// them to wasm modules. +#[proc_macro] +pub fn export(input: TokenStream) -> TokenStream { + run(input, Direction::Export) +} + +fn run(input: TokenStream, dir: Direction) -> TokenStream { + let input = syn::parse_macro_input!(input as Opts); + let mut gen = input.opts.build(); + let mut files = Files::default(); + let (imports, exports) = match dir { + Direction::Import => (input.interfaces, vec![]), + Direction::Export => (vec![], input.interfaces), + }; + gen.generate_all(&imports, &exports, &mut files); + + let (_, contents) = files.iter().next().unwrap(); + + let contents = std::str::from_utf8(contents).unwrap(); + let mut contents = contents.parse::().unwrap(); + + // Include a dummy `include_str!` for any files we read so rustc knows that + // we depend on the contents of those files. + let cwd = std::env::var("CARGO_MANIFEST_DIR").unwrap(); + for file in input.files.iter() { + contents.extend( + format!( + "const _: &str = include_str!(r#\"{}\"#);\n", + Path::new(&cwd).join(file).display() + ) + .parse::() + .unwrap(), + ); + } + + return contents; +} + +struct Opts { + opts: wit_bindgen_gen_wasmtime::Opts, + interfaces: Vec, + files: Vec, +} + +mod kw { + syn::custom_keyword!(src); + syn::custom_keyword!(paths); + syn::custom_keyword!(custom_error); +} + +impl Parse for Opts { + fn parse(input: ParseStream<'_>) -> Result { + let call_site = proc_macro2::Span::call_site(); + let mut opts = wit_bindgen_gen_wasmtime::Opts::default(); + let mut files = Vec::new(); + opts.tracing = cfg!(feature = "tracing"); + + let interfaces = if input.peek(token::Brace) { + let content; + syn::braced!(content in input); + let mut interfaces = Vec::new(); + let fields = Punctuated::::parse_terminated(&content)?; + for field in fields.into_pairs() { + match field.into_value() { + ConfigField::Interfaces(v) => interfaces = v, + ConfigField::Async(v) => opts.async_ = v, + ConfigField::CustomError(v) => opts.custom_error = v, + } + } + if interfaces.is_empty() { + return Err(Error::new( + call_site, + "must either specify `src` or `paths` keys", + )); + } + interfaces + } else { + while !input.is_empty() { + let s = input.parse::()?; + files.push(s.value()); + } + let mut interfaces = Vec::new(); + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + for path in files.iter() { + let path = manifest_dir.join(path); + let iface = Interface::parse_file(path).map_err(|e| Error::new(call_site, e))?; + interfaces.push(iface); + } + interfaces + }; + Ok(Opts { + opts, + interfaces, + files, + }) + } +} + +enum ConfigField { + Interfaces(Vec), + Async(wit_bindgen_gen_wasmtime::Async), + CustomError(bool), +} + +impl Parse for ConfigField { + fn parse(input: ParseStream<'_>) -> Result { + let l = input.lookahead1(); + if l.peek(kw::src) { + input.parse::()?; + let name; + syn::bracketed!(name in input); + let name = name.parse::()?; + input.parse::()?; + let s = input.parse::()?; + let interface = + Interface::parse(&name.value(), &s.value()).map_err(|e| Error::new(s.span(), e))?; + Ok(ConfigField::Interfaces(vec![interface])) + } else if l.peek(kw::paths) { + input.parse::()?; + input.parse::()?; + let paths; + let bracket = syn::bracketed!(paths in input); + let paths = Punctuated::::parse_terminated(&paths)?; + let values = paths.iter().map(|s| s.value()).collect::>(); + let mut interfaces = Vec::new(); + let manifest_dir = PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()); + for value in &values { + let value = manifest_dir.join(value); + let interface = + Interface::parse_file(value).map_err(|e| Error::new(bracket.span, e))?; + interfaces.push(interface); + } + Ok(ConfigField::Interfaces(interfaces)) + } else if l.peek(token::Async) { + if !cfg!(feature = "async") { + return Err( + input.error("async support not enabled in the `wit-bindgen-wasmtime` crate") + ); + } + input.parse::()?; + input.parse::()?; + let val = if input.parse::>()?.is_some() { + Async::All + } else { + let names; + syn::bracketed!(names in input); + let paths = Punctuated::::parse_terminated(&names)?; + let values = paths.iter().map(|s| s.value()).collect(); + Async::Only(values) + }; + Ok(ConfigField::Async(val)) + } else if l.peek(kw::custom_error) { + input.parse::()?; + input.parse::()?; + Ok(ConfigField::CustomError( + input.parse::()?.value, + )) + } else { + Err(l.error()) + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/Cargo.toml new file mode 100644 index 0000000..beb65b2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "wit-bindgen-wasmtime" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" + +[dependencies] +anyhow = "1.0" +bitflags = "1.2" +thiserror = "1.0" +wasmtime = "0.38.0" +wit-bindgen-wasmtime-impl = { path = "../wasmtime-impl", version = "0.1" } +tracing-lib = { version = "0.1.26", optional = true, package = 'tracing' } +async-trait = { version = "0.1.50", optional = true } + +[features] +# Enables generated code to emit events via the `tracing` crate whenever wasm is +# entered and when native functions are called. Note that tracing is currently +# only done for imported functions. +tracing = ['tracing-lib', 'wit-bindgen-wasmtime-impl/tracing'] + +# Enables async support for generated code, although when enabled this still +# needs to be configured through the macro invocation. +async = ['async-trait', 'wit-bindgen-wasmtime-impl/async'] diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/error.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/error.rs new file mode 100644 index 0000000..5cfab66 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/error.rs @@ -0,0 +1,40 @@ +use crate::Region; +use thiserror::Error; + +#[derive(Debug, Error, PartialEq, Eq)] +pub enum GuestError { + #[error("Invalid flag value {0}")] + InvalidFlagValue(&'static str), + #[error("Invalid enum value {0}")] + InvalidEnumValue(&'static str), + #[error("Pointer overflow")] + PtrOverflow, + #[error("Pointer out of bounds: {0:?}")] + PtrOutOfBounds(Region), + #[error("Pointer not aligned to {1}: {0:?}")] + PtrNotAligned(Region, u32), + #[error("Pointer already borrowed: {0:?}")] + PtrBorrowed(Region), + #[error("Borrow checker out of handles")] + BorrowCheckerOutOfHandles, + #[error("Slice length mismatch")] + SliceLengthsDiffer, + #[error("In func {funcname}:{location}:")] + InFunc { + funcname: &'static str, + location: &'static str, + #[source] + err: Box, + }, + #[error("In data {typename}.{field}:")] + InDataField { + typename: String, + field: String, + #[source] + err: Box, + }, + #[error("Invalid UTF-8 encountered: {0:?}")] + InvalidUtf8(#[from] ::std::str::Utf8Error), + #[error("Int conversion error: {0:?}")] + TryFromIntError(#[from] ::std::num::TryFromIntError), +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/le.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/le.rs new file mode 100644 index 0000000..4dafa7f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/le.rs @@ -0,0 +1,194 @@ +use crate::AllBytesValid; +use std::cmp::Ordering; +use std::fmt; +use std::mem; +use std::slice; + +/// Helper type representing a 1-byte-aligned little-endian value in memory. +/// +/// This type is used in slice types for Wasmtime host bindings. Guest types are +/// not guaranteed to be either aligned or in the native endianness. This type +/// wraps these types and provides explicit getters/setters to interact with the +/// underlying value in a safe host-agnostic manner. +#[repr(packed)] +pub struct Le(T); + +impl Le +where + T: Endian, +{ + /// Creates a new `Le` value where the internals are stored in a way + /// that's safe to copy into wasm linear memory. + pub fn new(t: T) -> Le { + Le(t.into_le()) + } + + /// Reads the value stored in this `Le`. + /// + /// This will perform a correct read even if the underlying memory is + /// unaligned, and it will also convert to the host's endianness for the + /// right representation of `T`. + pub fn get(&self) -> T { + self.0.from_le() + } + + /// Writes the `val` to this slot. + /// + /// This will work correctly even if the underlying memory is unaligned and + /// it will also automatically convert the `val` provided to an endianness + /// appropriate for WebAssembly (little-endian). + pub fn set(&mut self, val: T) { + self.0 = val.into_le(); + } + + pub(crate) fn from_slice(bytes: &[u8]) -> &[Le] { + // SAFETY: The invariants we uphold here are: + // + // * the lifetime of the input is the same as the output, so we're only + // dealing with valid memory. + // * the alignment of the input is the same as the output (1) + // * the input isn't being truncated and we're consuming all of it (it + // must be a multiple of the size of `Le`) + // * all byte-patterns for `Le` are valid. This is guaranteed by the + // `AllBytesValid` supertrait of `Endian`. + unsafe { + assert_eq!(mem::align_of::>(), 1); + assert!(bytes.len() % mem::size_of::>() == 0); + fn all_bytes_valid() {} + all_bytes_valid::>(); + + slice::from_raw_parts( + bytes.as_ptr().cast::>(), + bytes.len() / mem::size_of::>(), + ) + } + } + + pub(crate) fn from_slice_mut(bytes: &mut [u8]) -> &mut [Le] { + // SAFETY: see `from_slice` above + // + // Note that both the input and the output are `mut`, helping to + // maintain the guarantee of uniqueness. + unsafe { + assert_eq!(mem::align_of::>(), 1); + assert!(bytes.len() % mem::size_of::>() == 0); + slice::from_raw_parts_mut( + bytes.as_mut_ptr().cast::>(), + bytes.len() / mem::size_of::>(), + ) + } + } +} + +impl Clone for Le { + fn clone(&self) -> Self { + *self + } +} + +impl Copy for Le {} + +impl PartialEq for Le { + fn eq(&self, other: &Le) -> bool { + self.get() == other.get() + } +} + +impl PartialEq for Le { + fn eq(&self, other: &T) -> bool { + self.get() == *other + } +} + +impl Eq for Le {} + +impl PartialOrd for Le { + fn partial_cmp(&self, other: &Le) -> Option { + self.get().partial_cmp(&other.get()) + } +} + +impl Ord for Le { + fn cmp(&self, other: &Le) -> Ordering { + self.get().cmp(&other.get()) + } +} + +impl fmt::Debug for Le { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.get().fmt(f) + } +} + +impl From for Le { + fn from(t: T) -> Le { + Le::new(t) + } +} + +unsafe impl AllBytesValid for Le {} + +/// Trait used for the implementation of the `Le` type. +pub trait Endian: AllBytesValid + Copy + Sized { + /// Converts this value and any aggregate fields (if any) into little-endian + /// byte order + fn into_le(self) -> Self; + /// Converts this value and any aggregate fields (if any) from + /// little-endian byte order + fn from_le(self) -> Self; +} + +macro_rules! primitives { + ($($t:ident)*) => ($( + impl Endian for $t { + #[inline] + fn into_le(self) -> Self { + Self::from_ne_bytes(self.to_le_bytes()) + } + + #[inline] + fn from_le(self) -> Self { + Self::from_le_bytes(self.to_ne_bytes()) + } + } + )*) +} + +primitives! { + u8 i8 + u16 i16 + u32 i32 + u64 i64 + f32 f64 +} + +macro_rules! tuples { + ($(($($t:ident)*))*) => ($( + #[allow(non_snake_case)] + impl <$($t:Endian,)*> Endian for ($($t,)*) { + fn into_le(self) -> Self { + let ($($t,)*) = self; + ($($t.into_le(),)*) + } + + fn from_le(self) -> Self { + let ($($t,)*) = self; + ($($t.from_le(),)*) + } + } + )*) +} + +tuples! { + () + (T1) + (T1 T2) + (T1 T2 T3) + (T1 T2 T3 T4) + (T1 T2 T3 T4 T5) + (T1 T2 T3 T4 T5 T6) + (T1 T2 T3 T4 T5 T6 T7) + (T1 T2 T3 T4 T5 T6 T7 T8) + (T1 T2 T3 T4 T5 T6 T7 T8 T9) + (T1 T2 T3 T4 T5 T6 T7 T8 T9 T10) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/lib.rs new file mode 100644 index 0000000..caa7fbc --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/lib.rs @@ -0,0 +1,248 @@ +pub use wit_bindgen_wasmtime_impl::{export, import}; + +#[cfg(feature = "async")] +pub use async_trait::async_trait; +#[cfg(feature = "tracing-lib")] +pub use tracing_lib as tracing; +#[doc(hidden)] +pub use {anyhow, bitflags, wasmtime}; + +mod error; +mod le; +mod region; +mod slab; +mod table; + +pub use error::GuestError; +pub use le::{Endian, Le}; +pub use region::{AllBytesValid, BorrowChecker, Region}; +pub use table::*; + +#[doc(hidden)] +pub mod rt { + use crate::slab::Slab; + use crate::{Endian, Le}; + use std::mem; + use wasmtime::*; + + pub trait RawMem { + fn store(&mut self, offset: i32, val: T) -> Result<(), Trap>; + fn store_many(&mut self, offset: i32, vals: &[T]) -> Result<(), Trap>; + fn load(&self, offset: i32) -> Result; + } + + impl RawMem for [u8] { + fn store(&mut self, offset: i32, val: T) -> Result<(), Trap> { + let mem = self + .get_mut(offset as usize..) + .and_then(|m| m.get_mut(..mem::size_of::())) + .ok_or_else(|| Trap::new("out of bounds write"))?; + Le::from_slice_mut(mem)[0].set(val); + Ok(()) + } + + fn store_many(&mut self, offset: i32, val: &[T]) -> Result<(), Trap> { + let mem = self + .get_mut(offset as usize..) + .and_then(|m| { + let len = mem::size_of::().checked_mul(val.len())?; + m.get_mut(..len) + }) + .ok_or_else(|| Trap::new("out of bounds write"))?; + for (slot, val) in Le::from_slice_mut(mem).iter_mut().zip(val) { + slot.set(*val); + } + Ok(()) + } + + fn load(&self, offset: i32) -> Result { + let mem = self + .get(offset as usize..) + .and_then(|m| m.get(..mem::size_of::>())) + .ok_or_else(|| Trap::new("out of bounds read"))?; + Ok(Le::from_slice(mem)[0].get()) + } + } + + pub fn char_from_i32(val: i32) -> Result { + core::char::from_u32(val as u32).ok_or_else(|| Trap::new("char value out of valid range")) + } + + pub fn invalid_variant(name: &str) -> Trap { + let msg = format!("invalid discriminant for `{}`", name); + Trap::new(msg) + } + + pub fn validate_flags( + bits: T, + all: T, + name: &str, + mk: impl FnOnce(T) -> U, + ) -> Result + where + T: std::ops::Not + std::ops::BitAnd + From + PartialEq + Copy, + { + if bits & !all != 0u8.into() { + let msg = format!("invalid flags specified for `{}`", name); + Err(Trap::new(msg)) + } else { + Ok(mk(bits)) + } + } + + pub fn get_func(caller: &mut Caller<'_, T>, func: &str) -> Result { + let func = caller + .get_export(func) + .ok_or_else(|| { + let msg = format!("`{}` export not available", func); + Trap::new(msg) + })? + .into_func() + .ok_or_else(|| { + let msg = format!("`{}` export not a function", func); + Trap::new(msg) + })?; + Ok(func) + } + + pub fn get_memory(caller: &mut Caller<'_, T>, mem: &str) -> Result { + let mem = caller + .get_export(mem) + .ok_or_else(|| { + let msg = format!("`{}` export not available", mem); + Trap::new(msg) + })? + .into_memory() + .ok_or_else(|| { + let msg = format!("`{}` export not a memory", mem); + Trap::new(msg) + })?; + Ok(mem) + } + + pub fn bad_int(_: std::num::TryFromIntError) -> Trap { + let msg = "out-of-bounds integer conversion"; + Trap::new(msg) + } + + pub fn copy_slice( + store: impl AsContextMut, + memory: &Memory, + base: i32, + len: i32, + _align: i32, + ) -> Result, Trap> { + let size = (len as u32) + .checked_mul(mem::size_of::() as u32) + .ok_or_else(|| Trap::new("array too large to fit in wasm memory"))?; + let slice = memory + .data(&store) + .get(base as usize..) + .and_then(|s| s.get(..size as usize)) + .ok_or_else(|| Trap::new("out of bounds read"))?; + Ok(Le::from_slice(slice).iter().map(|s| s.get()).collect()) + } + + macro_rules! as_traits { + ($(($name:ident $tr:ident $ty:ident ($($tys:ident)*)))*) => ($( + pub fn $name(t: T) -> $ty { + t.$name() + } + + pub trait $tr { + fn $name(self) -> $ty; + } + + impl<'a, T: Copy + $tr> $tr for &'a T { + fn $name(self) -> $ty { + (*self).$name() + } + } + + $( + impl $tr for $tys { + #[inline] + fn $name(self) -> $ty { + self as $ty + } + } + )* + )*) + } + + as_traits! { + (as_i32 AsI32 i32 (char i8 u8 i16 u16 i32 u32)) + (as_i64 AsI64 i64 (i64 u64)) + (as_f32 AsF32 f32 (f32)) + (as_f64 AsF64 f64 (f64)) + } + + #[derive(Default, Debug)] + pub struct IndexSlab { + slab: Slab, + } + + impl IndexSlab { + pub fn insert(&mut self, resource: ResourceIndex) -> u32 { + self.slab.insert(resource) + } + + pub fn get(&self, slab_idx: u32) -> Result { + match self.slab.get(slab_idx) { + Some(idx) => Ok(*idx), + None => Err(Trap::new("invalid index specified for handle")), + } + } + + pub fn remove(&mut self, slab_idx: u32) -> Result { + match self.slab.remove(slab_idx) { + Some(idx) => Ok(idx), + None => Err(Trap::new("invalid index specified for handle")), + } + } + } + + #[derive(Default, Debug)] + pub struct ResourceSlab { + slab: Slab, + } + + #[derive(Debug)] + struct Resource { + wasm: i32, + refcnt: u32, + } + + #[derive(Debug, Copy, Clone)] + pub struct ResourceIndex(u32); + + impl ResourceSlab { + pub fn insert(&mut self, wasm: i32) -> ResourceIndex { + ResourceIndex(self.slab.insert(Resource { wasm, refcnt: 1 })) + } + + pub fn get(&self, idx: ResourceIndex) -> i32 { + self.slab.get(idx.0).unwrap().wasm + } + + pub fn clone(&mut self, idx: ResourceIndex) -> Result<(), Trap> { + let resource = self.slab.get_mut(idx.0).unwrap(); + resource.refcnt = match resource.refcnt.checked_add(1) { + Some(cnt) => cnt, + None => return Err(Trap::new("resource index count overflow")), + }; + Ok(()) + } + + pub fn drop(&mut self, idx: ResourceIndex) -> Option { + let resource = self.slab.get_mut(idx.0).unwrap(); + assert!(resource.refcnt > 0); + resource.refcnt -= 1; + if resource.refcnt != 0 { + return None; + } + let resource = self.slab.remove(idx.0).unwrap(); + Some(resource.wasm) + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/region.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/region.rs new file mode 100644 index 0000000..f284802 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/region.rs @@ -0,0 +1,312 @@ +use crate::rt::RawMem; +use crate::{Endian, GuestError, Le}; +use std::collections::HashSet; +use std::convert::TryInto; +use std::marker; +use std::mem; +use wasmtime::Trap; + +// This is a pretty naive way to account for borrows. This datastructure +// could be made a lot more efficient with some effort. +pub struct BorrowChecker<'a> { + /// Maps from handle to region borrowed. A HashMap is probably not ideal + /// for this but it works. It would be more efficient if we could + /// check `is_borrowed` without an O(n) iteration, by organizing borrows + /// by an ordering of Region. + shared_borrows: HashSet, + mut_borrows: HashSet, + _marker: marker::PhantomData<&'a mut [u8]>, + ptr: *mut u8, + len: usize, +} + +// These are not automatically implemented with our storage of `*mut u8`, so we +// need to manually declare that this type is threadsafe. +unsafe impl Send for BorrowChecker<'_> {} +unsafe impl Sync for BorrowChecker<'_> {} + +fn to_trap(err: impl std::error::Error + Send + Sync + 'static) -> Trap { + Trap::from(Box::new(err) as Box) +} + +impl<'a> BorrowChecker<'a> { + pub fn new(data: &'a mut [u8]) -> BorrowChecker<'a> { + BorrowChecker { + ptr: data.as_mut_ptr(), + len: data.len(), + shared_borrows: Default::default(), + mut_borrows: Default::default(), + _marker: marker::PhantomData, + } + } + + pub fn slice(&mut self, ptr: i32, len: i32) -> Result<&'a [T], Trap> { + let (ret, r) = self.get_slice(ptr, len)?; + // SAFETY: We're promoting the valid lifetime of `ret` from a temporary + // borrow on `self` to `'a` on this `BorrowChecker`. At the same time + // we're recording that this is a persistent shared borrow (until this + // borrow checker is deleted), which disallows future mutable borrows + // of the same data. + let ret = unsafe { &*(ret as *const [T]) }; + self.shared_borrows.insert(r); + Ok(ret) + } + + pub fn slice_mut(&mut self, ptr: i32, len: i32) -> Result<&'a mut [T], Trap> { + let (ret, r) = self.get_slice_mut(ptr, len)?; + // SAFETY: see `slice` for how we're extending the lifetime by + // recording the borrow here. Note that the `mut_borrows` list is + // checked on both shared and mutable borrows in the future since a + // mutable borrow can't alias with anything. + let ret = unsafe { &mut *(ret as *mut [T]) }; + self.mut_borrows.insert(r); + Ok(ret) + } + + fn get_slice(&self, ptr: i32, len: i32) -> Result<(&[T], Region), Trap> { + let r = self.region::(ptr, len)?; + if self.is_mut_borrowed(r) { + Err(to_trap(GuestError::PtrBorrowed(r))) + } else { + Ok(( + // SAFETY: invariants to uphold: + // + // * The lifetime of the input is valid for the lifetime of the + // output. In this case we're threading through the lifetime + // of `&self` to the output. + // * The actual output is valid, which is guaranteed with the + // `AllBytesValid` bound. + // * We uphold Rust's borrowing guarantees, namely that this + // borrow we're returning isn't overlapping with any mutable + // borrows. + // * The region `r` we're returning accurately describes the + // slice we're returning in wasm linear memory. + unsafe { + std::slice::from_raw_parts( + self.ptr.add(r.start as usize) as *const T, + len as usize, + ) + }, + r, + )) + } + } + + fn get_slice_mut(&mut self, ptr: i32, len: i32) -> Result<(&mut [T], Region), Trap> { + let r = self.region::(ptr, len)?; + if self.is_mut_borrowed(r) || self.is_shared_borrowed(r) { + Err(to_trap(GuestError::PtrBorrowed(r))) + } else { + Ok(( + // SAFETY: same as `get_slice`, except for that we're threading + // through `&mut` properties as well. + unsafe { + std::slice::from_raw_parts_mut( + self.ptr.add(r.start as usize) as *mut T, + len as usize, + ) + }, + r, + )) + } + } + + fn region(&self, ptr: i32, len: i32) -> Result { + assert_eq!(std::mem::align_of::(), 1); + let r = Region { + start: ptr as u32, + len: (len as u32) + .checked_mul(mem::size_of::() as u32) + .ok_or_else(|| to_trap(GuestError::PtrOverflow))?, + }; + self.validate_contains(&r)?; + Ok(r) + } + + pub fn slice_str(&mut self, ptr: i32, len: i32) -> Result<&'a str, Trap> { + let bytes = self.slice(ptr, len)?; + std::str::from_utf8(bytes).map_err(to_trap) + } + + fn validate_contains(&self, region: &Region) -> Result<(), Trap> { + let end = region + .start + .checked_add(region.len) + .ok_or_else(|| to_trap(GuestError::PtrOverflow))? as usize; + if end <= self.len { + Ok(()) + } else { + Err(to_trap(GuestError::PtrOutOfBounds(*region))) + } + } + + fn is_shared_borrowed(&self, r: Region) -> bool { + self.shared_borrows.iter().any(|b| b.overlaps(r)) + } + + fn is_mut_borrowed(&self, r: Region) -> bool { + self.mut_borrows.iter().any(|b| b.overlaps(r)) + } + + pub fn raw(&self) -> *mut [u8] { + std::ptr::slice_from_raw_parts_mut(self.ptr, self.len) + } +} + +impl RawMem for BorrowChecker<'_> { + fn store(&mut self, offset: i32, val: T) -> Result<(), Trap> { + let (slice, _) = self.get_slice_mut::>(offset, 1)?; + slice[0].set(val); + Ok(()) + } + + fn store_many(&mut self, offset: i32, val: &[T]) -> Result<(), Trap> { + let (slice, _) = self.get_slice_mut::>( + offset, + val.len() + .try_into() + .map_err(|_| to_trap(GuestError::PtrOverflow))?, + )?; + for (slot, val) in slice.iter_mut().zip(val) { + slot.set(*val); + } + Ok(()) + } + + fn load(&self, offset: i32) -> Result { + let (slice, _) = self.get_slice::>(offset, 1)?; + Ok(slice[0].get()) + } +} + +/// Unsafe trait representing types where every byte pattern is valid for their +/// representation. +/// +/// This is the set of types which wasmtime can have a raw pointer to for +/// values which reside in wasm linear memory. +pub unsafe trait AllBytesValid {} + +unsafe impl AllBytesValid for u8 {} +unsafe impl AllBytesValid for u16 {} +unsafe impl AllBytesValid for u32 {} +unsafe impl AllBytesValid for u64 {} +unsafe impl AllBytesValid for i8 {} +unsafe impl AllBytesValid for i16 {} +unsafe impl AllBytesValid for i32 {} +unsafe impl AllBytesValid for i64 {} +unsafe impl AllBytesValid for f32 {} +unsafe impl AllBytesValid for f64 {} + +macro_rules! tuples { + ($(($($t:ident)*))*) => ($( + unsafe impl <$($t:AllBytesValid,)*> AllBytesValid for ($($t,)*) {} + )*) +} + +tuples! { + () + (T1) + (T1 T2) + (T1 T2 T3) + (T1 T2 T3 T4) + (T1 T2 T3 T4 T5) + (T1 T2 T3 T4 T5 T6) + (T1 T2 T3 T4 T5 T6 T7) + (T1 T2 T3 T4 T5 T6 T7 T8) + (T1 T2 T3 T4 T5 T6 T7 T8 T9) + (T1 T2 T3 T4 T5 T6 T7 T8 T9 T10) +} + +/// Represents a contiguous region in memory. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct Region { + pub start: u32, + pub len: u32, +} + +impl Region { + /// Checks if this `Region` overlaps with `rhs` `Region`. + fn overlaps(&self, rhs: Region) -> bool { + // Zero-length regions can never overlap! + if self.len == 0 || rhs.len == 0 { + return false; + } + + let self_start = self.start as u64; + let self_end = self_start + (self.len - 1) as u64; + + let rhs_start = rhs.start as u64; + let rhs_end = rhs_start + (rhs.len - 1) as u64; + + if self_start <= rhs_start { + self_end >= rhs_start + } else { + rhs_end >= self_start + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn nonoverlapping() { + let mut bytes = [0; 100]; + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(0, 10).unwrap(); + bc.slice::(10, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(10, 10).unwrap(); + bc.slice::(0, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice_mut::(0, 10).unwrap(); + bc.slice_mut::(10, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice_mut::(10, 10).unwrap(); + bc.slice_mut::(0, 10).unwrap(); + } + + #[test] + fn overlapping() { + let mut bytes = [0; 100]; + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(0, 10).unwrap(); + bc.slice_mut::(9, 10).unwrap_err(); + bc.slice::(9, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(0, 10).unwrap(); + bc.slice_mut::(2, 5).unwrap_err(); + bc.slice::(2, 5).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(9, 10).unwrap(); + bc.slice_mut::(0, 10).unwrap_err(); + bc.slice::(0, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(2, 5).unwrap(); + bc.slice_mut::(0, 10).unwrap_err(); + bc.slice::(0, 10).unwrap(); + + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice::(2, 5).unwrap(); + bc.slice::(10, 5).unwrap(); + bc.slice::(15, 5).unwrap(); + bc.slice_mut::(0, 10).unwrap_err(); + bc.slice::(0, 10).unwrap(); + } + + #[test] + fn zero_length() { + let mut bytes = [0; 100]; + let mut bc = BorrowChecker::new(&mut bytes); + bc.slice_mut::(0, 0).unwrap(); + bc.slice_mut::(0, 0).unwrap(); + bc.slice::(0, 1).unwrap(); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/slab.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/slab.rs new file mode 100644 index 0000000..29db0c9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/slab.rs @@ -0,0 +1,72 @@ +use std::fmt; +use std::mem; + +pub struct Slab { + storage: Vec>, + next: usize, +} + +enum Entry { + Full(T), + Empty { next: usize }, +} + +impl Slab { + pub fn insert(&mut self, item: T) -> u32 { + if self.next == self.storage.len() { + self.storage.push(Entry::Empty { + next: self.next + 1, + }); + } + let ret = self.next as u32; + let entry = Entry::Full(item); + self.next = match mem::replace(&mut self.storage[self.next], entry) { + Entry::Empty { next } => next, + _ => unreachable!(), + }; + return ret; + } + + pub fn get(&self, idx: u32) -> Option<&T> { + match self.storage.get(idx as usize)? { + Entry::Full(b) => Some(b), + Entry::Empty { .. } => None, + } + } + + pub fn get_mut(&mut self, idx: u32) -> Option<&mut T> { + match self.storage.get_mut(idx as usize)? { + Entry::Full(b) => Some(b), + Entry::Empty { .. } => None, + } + } + + pub fn remove(&mut self, idx: u32) -> Option { + let slot = self.storage.get_mut(idx as usize)?; + match mem::replace(slot, Entry::Empty { next: self.next }) { + Entry::Full(b) => { + self.next = idx as usize; + Some(b) + } + Entry::Empty { next } => { + *slot = Entry::Empty { next }; + None + } + } + } +} + +impl Default for Slab { + fn default() -> Slab { + Slab { + storage: Vec::new(), + next: 0, + } + } +} + +impl fmt::Debug for Slab { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Slab").finish() + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/table.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/table.rs new file mode 100644 index 0000000..c3f6fa2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wasmtime/src/table.rs @@ -0,0 +1,144 @@ +use std::convert::TryFrom; +use std::fmt; +use std::mem; + +pub struct Table { + elems: Vec>, + next: usize, +} + +#[derive(Debug)] +pub enum RemoveError { + NotAllocated, +} + +enum Slot { + Empty { next_empty: usize }, + Full { item: Box }, +} + +impl Table { + /// Creates a new empty table + pub fn new() -> Table { + Table { + elems: Vec::new(), + next: 0, + } + } + + /// Inserts an item into this table, returning the index that it was + /// inserted at. + pub fn insert(&mut self, item: T) -> u32 { + if self.next == self.elems.len() { + let next_empty = self.next + 1; + self.elems.push(Slot::Empty { next_empty }); + } + let index = self.next; + let ret = u32::try_from(index).unwrap(); + self.next = match &self.elems[index] { + Slot::Empty { next_empty } => *next_empty, + Slot::Full { .. } => unreachable!(), + }; + self.elems[index] = Slot::Full { + item: Box::new(item), + }; + return ret; + } + + /// Borrows an item from this table. + /// + /// Returns `None` if the index is not allocated at this time. Otherwise + /// returns `Some` with a borrow of the item from this table. + pub fn get(&self, item: u32) -> Option<&T> { + let index = usize::try_from(item).unwrap(); + match self.elems.get(index)? { + Slot::Empty { .. } => None, + Slot::Full { item } => Some(item), + } + } + + /// Removes an item from this table. + /// + /// On success it returns back the original item. + pub fn remove(&mut self, item: u32) -> Result { + let index = usize::try_from(item).unwrap(); + let new_empty = Slot::Empty { + next_empty: self.next, + }; + let slot = self.elems.get_mut(index).ok_or(RemoveError::NotAllocated)?; + + // Assume that `item` is valid, and if it is, we can return quickly + match mem::replace(slot, new_empty) { + Slot::Full { item } => { + self.next = index; + Ok(*item) + } + + // Oops `item` wasn't valid, put it back where we found it and then + // figure out why it was invalid + Slot::Empty { next_empty } => { + *slot = Slot::Empty { next_empty }; + Err(RemoveError::NotAllocated) + } + } + } +} + +impl Default for Table { + fn default() -> Table { + Table::new() + } +} + +impl fmt::Debug for Table { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Table") + .field("capacity", &self.elems.capacity()) + .finish() + } +} + +impl fmt::Display for RemoveError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + RemoveError::NotAllocated => f.write_str("invalid handle index"), + } + } +} + +impl std::error::Error for RemoveError {} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn simple() { + let mut table = Table::new(); + assert_eq!(table.insert(0), 0); + assert_eq!(table.insert(100), 1); + assert_eq!(table.insert(200), 2); + + assert_eq!(*table.get(0).unwrap(), 0); + assert_eq!(*table.get(1).unwrap(), 100); + assert_eq!(*table.get(2).unwrap(), 200); + assert!(table.get(100).is_none()); + + assert!(table.remove(0).is_ok()); + assert!(table.get(0).is_none()); + assert_eq!(table.insert(1), 0); + assert!(table.get(0).is_some()); + + table.get(1).unwrap(); + assert!(table.remove(1).is_ok()); + assert!(table.remove(1).is_err()); + + assert!(table.remove(2).is_ok()); + assert!(table.remove(0).is_ok()); + + assert_eq!(table.insert(100), 0); + assert_eq!(table.insert(100), 2); + assert_eq!(table.insert(100), 1); + assert_eq!(table.insert(100), 3); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/Cargo.toml new file mode 100644 index 0000000..119bf32 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "wit-bindgen-demo" +version = "0.1.0" +authors = ["Alex Crichton "] +edition = "2018" +publish = false + +[lib] +crate-type = ['cdylib'] +test = false +doctest = false + +[dependencies] +wit-bindgen-gen-core = { path = '../gen-core' } +wit-bindgen-gen-rust-wasm = { path = '../gen-rust-wasm' } +wit-bindgen-gen-wasmtime = { path = '../gen-wasmtime' } +wit-bindgen-gen-wasmtime-py = { path = '../gen-wasmtime-py' } +wit-bindgen-gen-js = { path = '../gen-js' } +wit-bindgen-gen-c = { path = '../gen-c' } +wit-bindgen-gen-markdown = { path = '../gen-markdown' } +wit-bindgen-gen-spidermonkey = { path = '../gen-spidermonkey' } +wit-bindgen-rust = { path = '../rust-wasm' } +wasmprinter = "0.2.29" diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/browser.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/browser.wit new file mode 100644 index 0000000..0383c15 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/browser.wit @@ -0,0 +1,2 @@ +log: func(msg: string) +error: func(msg: string) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/build.sh b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/build.sh new file mode 100755 index 0000000..912ff0b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/build.sh @@ -0,0 +1,26 @@ +#!/bin/bash + +set -ex + +rm -rf static +mkdir static + +cargo build -p wit-bindgen-demo --target wasm32-unknown-unknown --release +cp target/wasm32-unknown-unknown/release/wit_bindgen_demo.wasm static/demo.wasm + +cargo run js \ + --export crates/wit-bindgen-demo/browser.wit \ + --import crates/wit-bindgen-demo/demo.wit \ + --out-dir static + +cp crates/wit-bindgen-demo/{index.html,main.ts} static/ +(cd crates/wit-bindgen-demo && npx tsc ../../static/main.ts --target es6) + +if [ ! -d ace ]; then + mkdir ace + cd ace + curl -L https://github.com/ajaxorg/ace-builds/archive/refs/tags/v1.4.12.tar.gz | tar xzf - + cd .. +fi + +cp -r ace/ace-builds-1.4.12/src static/ace diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/demo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/demo.wit new file mode 100644 index 0000000..e269914 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/demo.wit @@ -0,0 +1,29 @@ +type files = list> + +variant wasmtime-async { + all, + none, + only(list), +} + +enum lang { + js, + rust, + wasmtime, + wasmtime-py, + c, + markdown, + spidermonkey, +} + + +resource config { + static new: func() -> config + + render: func(lang: lang, wit: string, import: bool) -> expected + + set-rust-unchecked: func(unchecked: bool) + set-wasmtime-tracing: func(unchecked: bool) + set-wasmtime-async: func(val: wasmtime-async) + set-wasmtime-custom-error: func(custom: bool) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/index.html b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/index.html new file mode 100644 index 0000000..de93834 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/index.html @@ -0,0 +1,116 @@ + + + + + + + +
+

Input *.wit

+
+ +
+
+

Generated bindings

+ +
+ + + + + · + + + + + + · + + + + +
+
+
+
+
+
+
+
+
+ · + + + +
+
+ · + + + + + · + + + + + · + + + +
+
+
+
+ +
+
+
+ + + + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/main.ts b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/main.ts new file mode 100644 index 0000000..bc6c0d4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/main.ts @@ -0,0 +1,182 @@ +import { Demo, Config } from './demo.js'; +import * as browser from './browser.js'; + +class Editor { + input: HTMLTextAreaElement; + language: HTMLSelectElement; + mode: HTMLSelectElement; + files: HTMLSelectElement + rustUnchecked: HTMLInputElement; + wasmtimeTracing: HTMLInputElement; + wasmtimeAsync: HTMLInputElement; + wasmtimeCustomError: HTMLInputElement; + generatedFiles: Record; + demo: Demo; + config: Config | null; + rerender: number | null; + inputEditor: AceAjax.Editor; + outputEditor: AceAjax.Editor; + outputHtml: HTMLDivElement; + + constructor() { + this.input = document.getElementById('input-raw') as HTMLTextAreaElement; + this.language = document.getElementById('language-select') as HTMLSelectElement; + this.mode = document.getElementById('mode-select') as HTMLSelectElement; + this.files = document.getElementById('file-select') as HTMLSelectElement; + this.rustUnchecked = document.getElementById('rust-unchecked') as HTMLInputElement; + this.wasmtimeTracing = document.getElementById('wasmtime-tracing') as HTMLInputElement; + this.wasmtimeAsync = document.getElementById('wasmtime-async') as HTMLInputElement; + this.wasmtimeCustomError = document.getElementById('wasmtime-custom-error') as HTMLInputElement; + this.outputHtml = document.getElementById('html-output') as HTMLDivElement; + + this.inputEditor = ace.edit("input"); + this.outputEditor = ace.edit("output"); + this.inputEditor.setValue(this.input.value); + this.inputEditor.clearSelection(); + this.outputEditor.setReadOnly(true); + this.inputEditor.setOption("useWorker", false); + this.outputEditor.setOption("useWorker", false); + + this.generatedFiles = {}; + this.demo = new Demo(); + this.config = null; + this.rerender = null; + } + + async instantiate() { + const imports = {}; + const obj = { + log: console.log, + error: console.error, + }; + browser.addBrowserToImports(imports, obj, name => this.demo.instance.exports[name]); + await this.demo.instantiate(fetch('./demo.wasm'), imports); + this.config = Config.new(this.demo); + this.installListeners(); + this.render(); + } + + installListeners() { + this.inputEditor.on('change', () => { + this.input.value = this.inputEditor.getValue(); + if (this.rerender !== null) + clearTimeout(this.rerender); + this.rerender = setTimeout(() => this.render(), 500); + }); + + this.language.addEventListener('change', () => this.render()); + this.mode.addEventListener('change', () => this.render()); + + this.rustUnchecked.addEventListener('change', () => { + this.config.setRustUnchecked(this.rustUnchecked.checked); + this.render(); + }); + + this.wasmtimeTracing.addEventListener('change', () => { + this.config.setWasmtimeTracing(this.wasmtimeTracing.checked); + this.render(); + }); + this.wasmtimeAsync.addEventListener('change', () => { + let async_; + if (this.wasmtimeAsync.checked) + async_ = { tag: 'all' }; + else + async_ = { tag: 'none' }; + this.config.setWasmtimeAsync(async_); + this.render(); + }); + this.wasmtimeCustomError.addEventListener('change', () => { + this.config.setWasmtimeCustomError(this.wasmtimeCustomError.checked); + this.render(); + }); + this.files.addEventListener('change', () => this.updateSelectedFile()); + } + + + render() { + for (let div of document.querySelectorAll('.lang-configure')) { + (div as HTMLDivElement).style.display = 'none'; + } + + const config = document.getElementById(`configure-${this.language.value}`); + config.style.display = 'inline-block'; + + const wit = this.inputEditor.getValue(); + const is_import = this.mode.value === 'import'; + let lang; + switch (this.language.value) { + case "js": + case "rust": + case "wasmtime": + case "wasmtime-py": + case "c": + case "markdown": + case "spidermonkey": + lang = this.language.value; + break; + default: return; + } + const result = this.config.render(lang, wit, is_import); + if (result.tag === 'err') { + this.outputEditor.setValue(result.val); + this.outputEditor.clearSelection(); + this.showOutputEditor(); + return; + } + this.generatedFiles = {}; + const selectedFile = this.files.value; + this.files.options.length = 0; + for (let i = 0; i < result.val.length; i++) { + const name = result.val[i][0]; + const contents = result.val[i][1]; + this.files.options[i] = new Option(name, name); + this.generatedFiles[name] = contents; + } + if (selectedFile in this.generatedFiles) + this.files.value = selectedFile; + + this.updateSelectedFile(); + } + + showOutputEditor() { + this.outputHtml.style.display = 'none'; + document.getElementById('output').style.display = 'block'; + } + + showOutputHtml() { + this.outputHtml.style.display = 'block'; + document.getElementById('output').style.display = 'none'; + } + + updateSelectedFile() { + if (this.files.value.endsWith('.html')) { + const html = this.generatedFiles[this.files.value]; + this.outputHtml.innerHTML = html; + this.showOutputHtml(); + return; + } + + this.showOutputEditor(); + this.outputEditor.setValue(this.generatedFiles[this.files.value]); + this.outputEditor.clearSelection(); + if (this.files.value.endsWith('.d.ts')) + this.outputEditor.session.setMode("ace/mode/typescript"); + else if (this.files.value.endsWith('.js')) + this.outputEditor.session.setMode("ace/mode/javascript"); + else if (this.files.value.endsWith('.rs')) + this.outputEditor.session.setMode("ace/mode/rust"); + else if (this.files.value.endsWith('.c')) + this.outputEditor.session.setMode("ace/mode/c_cpp"); + else if (this.files.value.endsWith('.h')) + this.outputEditor.session.setMode("ace/mode/c_cpp"); + else if (this.files.value.endsWith('.md')) + this.outputEditor.session.setMode("ace/mode/markdown"); + else if (this.files.value.endsWith('.py')) + this.outputEditor.session.setMode("ace/mode/python"); + else + this.outputEditor.session.setMode(null); + } +} + + +(new Editor()).instantiate() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/package.json b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/package.json new file mode 100644 index 0000000..899e2d3 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/package.json @@ -0,0 +1,6 @@ +{ + "devDependencies": { + "@types/ace": "^0.0.46", + "typescript": "^4.3.4" + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/src/lib.rs new file mode 100644 index 0000000..c7cf963 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-bindgen-demo/src/lib.rs @@ -0,0 +1,101 @@ +use std::cell::RefCell; +use std::sync::Once; +use wit_bindgen_gen_core::wit_parser::Interface; +use wit_bindgen_gen_core::Generator; +use wit_bindgen_rust::Handle; + +wit_bindgen_rust::export!("demo.wit"); +wit_bindgen_rust::import!("browser.wit"); + +struct Demo; + +impl demo::Demo for Demo {} + +#[derive(Default)] +pub struct Config { + js: RefCell, + c: RefCell, + rust: RefCell, + wasmtime: RefCell, + wasmtime_py: RefCell, + markdown: RefCell, + spidermonkey: RefCell, +} + +impl demo::Config for Config { + fn new() -> Handle { + static INIT: Once = Once::new(); + INIT.call_once(|| { + let prev_hook = std::panic::take_hook(); + std::panic::set_hook(Box::new(move |info| { + browser::error(&info.to_string()); + prev_hook(info); + })); + }); + + Config::default().into() + } + + fn render( + &self, + lang: demo::Lang, + wit: String, + import: bool, + ) -> Result, String> { + let mut gen: Box = match lang { + demo::Lang::Rust => Box::new(self.rust.borrow().clone().build()), + demo::Lang::Wasmtime => Box::new(self.wasmtime.borrow().clone().build()), + demo::Lang::WasmtimePy => Box::new(self.wasmtime_py.borrow().clone().build()), + demo::Lang::Js => Box::new(self.js.borrow().clone().build()), + demo::Lang::C => Box::new(self.c.borrow().clone().build()), + demo::Lang::Markdown => Box::new(self.markdown.borrow().clone().build()), + demo::Lang::Spidermonkey => { + let mut opts = self.spidermonkey.borrow_mut(); + opts.import_spidermonkey = true; + opts.js = "foo.js".into(); + let script = "throw new Error('unimplemented');"; + Box::new(opts.clone().build(script)) + } + }; + let iface = Interface::parse("input", &wit).map_err(|e| format!("{:?}", e))?; + let mut files = Default::default(); + let (imports, exports) = if import { + (vec![iface], vec![]) + } else { + (vec![], vec![iface]) + }; + gen.generate_all(&imports, &exports, &mut files); + Ok(files + .iter() + .map(|(name, contents)| { + let contents = if contents.starts_with(b"\0asm") { + wasmprinter::print_bytes(contents).unwrap() + } else { + String::from_utf8_lossy(&contents).into() + }; + (name.to_string(), contents) + }) + .collect()) + } + + fn set_rust_unchecked(&self, unchecked: bool) { + self.rust.borrow_mut().unchecked = unchecked; + } + + fn set_wasmtime_tracing(&self, tracing: bool) { + self.wasmtime.borrow_mut().tracing = tracing; + } + fn set_wasmtime_custom_error(&self, custom_error: bool) { + browser::log("custom error"); + self.wasmtime.borrow_mut().custom_error = custom_error; + } + fn set_wasmtime_async(&self, async_: demo::WasmtimeAsync) { + use wit_bindgen_gen_wasmtime::Async; + + self.wasmtime.borrow_mut().async_ = match async_ { + demo::WasmtimeAsync::All => Async::All, + demo::WasmtimeAsync::None => Async::None, + demo::WasmtimeAsync::Only(list) => Async::Only(list.into_iter().collect()), + }; + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/Cargo.toml b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/Cargo.toml new file mode 100644 index 0000000..912eb56 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/Cargo.toml @@ -0,0 +1,40 @@ +[package] +name = "wit-component" +version = "0.1.0" +authors = ["Peter Huene "] +edition = "2021" + +[[bin]] +name = "wit-component" +path = "src/bin/wit-component.rs" +required-features = ["cli"] + +[[bin]] +name = "wit2wasm" +path = "src/bin/wit2wasm.rs" +required-features = ["cli"] + +[[bin]] +name = "wasm2wit" +path = "src/bin/wasm2wit.rs" +required-features = ["cli"] + +[dependencies] +wasmparser = "0.86.0" +wasm-encoder = "0.13.0" +wat = "1.0.44" +wit-parser = { path = "../parser" } +anyhow = "1.0.55" +indexmap = "1.8.0" +clap = { version = "3.1.0", features = ["derive"], optional = true } +env_logger = { version = "0.9.0", optional = true } +log = { version = "0.4.14", optional = true } + +[dev-dependencies] +wasmprinter = "0.2.36" +glob = "0.3.0" +pretty_assertions = "1.2.0" + +[features] +default = ["cli"] +cli = ["clap", "env_logger", "log"] diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/README.md b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/README.md new file mode 100644 index 0000000..e54d9df --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/README.md @@ -0,0 +1,29 @@ +
+

wit-component

+ +

+ WebAssembly component tooling based on the component model proposal and wit-bindgen. +

+ + A Bytecode Alliance project + +

+ build status + supported rustc stable +

+
+ +# `wit-component` + +`wit-component` is a crate and a set of CLI tools for creating and interacting with WebAssembly components based on the [component model proposal](https://github.com/WebAssembly/component-model/). + +## Tools + +* `wit-component` - creates a WebAssembly component from a core WebAssembly module and a set of + `.wit` files representing the component's imported and exported interfaces. + +* `wit2wasm` - encodes an interface definition (in `wit`) as an "interface-only" WebAssembly component. + A `.wasm` component file will be generated that stores a full description of the original interface. + +* `wasm2wit` - decodes an "interface-only" WebAssembly component to an interface definition (in `wit`). + A `.wit` file will be generated that represents the interface described by the component. diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wasm2wit.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wasm2wit.rs new file mode 100644 index 0000000..5ecdb76 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wasm2wit.rs @@ -0,0 +1,13 @@ +use clap::Parser; +use wit_component::cli::WasmToWitApp; + +fn main() { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_target(false) + .init(); + + if let Err(e) = WasmToWitApp::parse().execute() { + log::error!("{:?}", e); + std::process::exit(1); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wit-component.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wit-component.rs new file mode 100644 index 0000000..e55d83f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wit-component.rs @@ -0,0 +1,13 @@ +use clap::Parser; +use wit_component::cli::WitComponentApp; + +fn main() { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_target(false) + .init(); + + if let Err(e) = WitComponentApp::parse().execute() { + log::error!("{:?}", e); + std::process::exit(1); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wit2wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wit2wasm.rs new file mode 100644 index 0000000..f368c9b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/bin/wit2wasm.rs @@ -0,0 +1,13 @@ +use clap::Parser; +use wit_component::cli::WitToWasmApp; + +fn main() { + env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")) + .format_target(false) + .init(); + + if let Err(e) = WitToWasmApp::parse().execute() { + log::error!("{:?}", e); + std::process::exit(1); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/cli.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/cli.rs new file mode 100644 index 0000000..87b8d0f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/cli.rs @@ -0,0 +1,215 @@ +//! The WebAssembly component tool command line interface. + +#![deny(missing_docs)] + +use crate::{ + decode_interface_component, ComponentEncoder, InterfaceEncoder, InterfacePrinter, + StringEncoding, +}; +use anyhow::{bail, Context, Result}; +use clap::Parser; +use std::path::{Path, PathBuf}; +use wit_parser::Interface; + +fn parse_named_interface(s: &str) -> Result { + let (name, path) = s + .split_once('=') + .ok_or_else(|| anyhow::anyhow!("expected a value with format `NAME=INTERFACE`"))?; + + parse_interface(Some(name.to_string()), Path::new(path)) +} + +fn parse_unnamed_interface(s: &str) -> Result { + parse_interface(None, Path::new(s)) +} + +fn parse_interface(name: Option, path: &Path) -> Result { + if !path.is_file() { + bail!("interface file `{}` does not exist", path.display(),); + } + + let mut interface = Interface::parse_file(&path) + .with_context(|| format!("failed to parse interface file `{}`", path.display()))?; + + interface.name = name.unwrap_or_else(|| "".to_string()); + + Ok(interface) +} + +/// WebAssembly component encoder. +/// +/// Encodes a WebAssembly component from a core WebAssembly module. +#[derive(Debug, Parser)] +#[clap(name = "component-encoder", version = env!("CARGO_PKG_VERSION"))] +pub struct WitComponentApp { + /// The path to an interface definition file the component imports. + #[clap(long = "import", value_name = "NAME=INTERFACE", parse(try_from_str = parse_named_interface))] + pub imports: Vec, + + /// The path to an interface definition file the component exports. + #[clap(long = "export", value_name = "NAME=INTERFACE", parse(try_from_str = parse_named_interface))] + pub exports: Vec, + + /// The path of the output WebAssembly component. + #[clap(long, short = 'o', value_name = "OUTPUT")] + pub output: Option, + + /// The default interface the component exports. + #[clap(long, short = 'i', value_name = "INTERFACE", parse(try_from_str = parse_unnamed_interface))] + pub interface: Option, + + /// Skip validation of the output component. + #[clap(long)] + pub skip_validation: bool, + + /// The expected string encoding format for the component. + /// Supported values are: `utf8` (default), `utf16`, and `compact-utf16`. + #[clap(long, value_name = "ENCODING")] + pub encoding: Option, + + /// Path to the WebAssembly module to encode. + #[clap(index = 1, value_name = "MODULE")] + pub module: PathBuf, +} + +impl WitComponentApp { + /// Executes the application. + pub fn execute(self) -> Result<()> { + if !self.module.is_file() { + bail!( + "module `{}` does not exist as a file", + self.module.display() + ); + } + + let output = self.output.unwrap_or_else(|| { + let mut stem: PathBuf = self.module.file_stem().unwrap().into(); + stem.set_extension("wasm"); + stem + }); + + let module = wat::parse_file(&self.module) + .with_context(|| format!("failed to parse module `{}`", self.module.display()))?; + + let mut encoder = ComponentEncoder::default() + .module(&module) + .imports(&self.imports) + .exports(&self.exports) + .validate(!self.skip_validation); + + if let Some(interface) = &self.interface { + encoder = encoder.interface(interface); + } + + if let Some(encoding) = &self.encoding { + encoder = encoder.encoding(*encoding); + } + + let bytes = encoder.encode().with_context(|| { + format!( + "failed to encode a component from module `{}`", + self.module.display() + ) + })?; + + std::fs::write(&output, bytes) + .with_context(|| format!("failed to write output file `{}`", output.display()))?; + + println!("encoded component `{}`", output.display()); + + Ok(()) + } +} + +/// WebAssembly interface encoder. +/// +/// Encodes a WebAssembly interface as a WebAssembly component. +#[derive(Debug, Parser)] +#[clap(name = "wit2wasm", version = env!("CARGO_PKG_VERSION"))] +pub struct WitToWasmApp { + /// The path of the output WebAssembly component. + #[clap(long, short = 'o', value_name = "OUTPUT")] + pub output: Option, + + /// The path to the WebAssembly interface file to encode. + #[clap(index = 1, value_name = "INTERFACE")] + pub interface: PathBuf, +} + +impl WitToWasmApp { + /// Executes the application. + pub fn execute(self) -> Result<()> { + let output = self.output.unwrap_or_else(|| { + let mut stem: PathBuf = self.interface.file_stem().unwrap().into(); + stem.set_extension("wasm"); + stem + }); + + let interface = parse_interface(None, &self.interface)?; + + let encoder = InterfaceEncoder::new(&interface).validate(true); + + let bytes = encoder.encode().with_context(|| { + format!( + "failed to encode a component from interface `{}`", + self.interface.display() + ) + })?; + + std::fs::write(&output, bytes) + .with_context(|| format!("failed to write output file `{}`", output.display()))?; + + println!("encoded interface as component `{}`", output.display()); + + Ok(()) + } +} + +/// WebAssembly interface decoder. +/// +/// Decodes a WebAssembly interface from a WebAssembly component. +#[derive(Debug, Parser)] +#[clap(name = "wit2wasm", version = env!("CARGO_PKG_VERSION"))] +pub struct WasmToWitApp { + /// The path of the output WebAssembly interface file. + #[clap(long, short = 'o', value_name = "OUTPUT")] + pub output: Option, + + /// The path to the WebAssembly component to decode. + #[clap(index = 1, value_name = "COMPONENT")] + pub component: PathBuf, +} + +impl WasmToWitApp { + /// Executes the application. + pub fn execute(self) -> Result<()> { + let output = self.output.unwrap_or_else(|| { + let mut stem: PathBuf = self.component.file_stem().unwrap().into(); + stem.set_extension("wit"); + stem + }); + + if !self.component.is_file() { + bail!( + "component `{}` does not exist as a file", + self.component.display() + ); + } + + let bytes = wat::parse_file(&self.component) + .with_context(|| format!("failed to parse component `{}`", self.component.display()))?; + + let interface = decode_interface_component(&bytes).with_context(|| { + format!("failed to decode component `{}`", self.component.display()) + })?; + + let mut printer = InterfacePrinter::default(); + + std::fs::write(&output, printer.print(&interface)?) + .with_context(|| format!("failed to write output file `{}`", output.display()))?; + + println!("decoded interface to `{}`", output.display()); + + Ok(()) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/decoding.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/decoding.rs new file mode 100644 index 0000000..b2ec0f4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/decoding.rs @@ -0,0 +1,549 @@ +use anyhow::{anyhow, bail, Context, Result}; +use indexmap::IndexMap; +use wasmparser::{ + types, Chunk, ComponentExternalKind, Encoding, Parser, Payload, PrimitiveValType, Validator, + WasmFeatures, +}; +use wit_parser::*; + +/// Represents information about a decoded WebAssembly component. +pub struct ComponentInfo<'a> { + /// The types defined in the component. + pub types: types::Types, + /// The exported types in the component. + pub exported_types: Vec<(&'a str, u32)>, + /// The exported functions in the component. + pub exported_functions: Vec<(&'a str, u32)>, +} + +impl<'a> ComponentInfo<'a> { + /// Creates a new component info by parsing the given WebAssembly component bytes. + pub fn new(mut bytes: &'a [u8]) -> Result { + let mut parser = Parser::new(0); + let mut parsers = Vec::new(); + let mut validator = Validator::new_with_features(WasmFeatures { + component_model: true, + ..Default::default() + }); + let mut exported_types = Vec::new(); + let mut exported_functions = Vec::new(); + + loop { + match parser.parse(bytes, true)? { + Chunk::Parsed { payload, consumed } => { + bytes = &bytes[consumed..]; + match payload { + Payload::Version { + num, + encoding, + range, + } => { + if parsers.is_empty() && encoding != Encoding::Component { + bail!("file is not a WebAssembly component"); + } + + validator.version(num, encoding, &range)?; + } + + Payload::TypeSection(s) => { + validator.type_section(&s)?; + } + Payload::ImportSection(s) => { + validator.import_section(&s)?; + } + Payload::FunctionSection(s) => { + validator.function_section(&s)?; + } + Payload::TableSection(s) => { + validator.table_section(&s)?; + } + Payload::MemorySection(s) => { + validator.memory_section(&s)?; + } + Payload::TagSection(s) => { + validator.tag_section(&s)?; + } + Payload::GlobalSection(s) => { + validator.global_section(&s)?; + } + Payload::ExportSection(s) => { + validator.export_section(&s)?; + } + Payload::StartSection { func, range } => { + validator.start_section(func, &range)?; + } + Payload::ElementSection(s) => { + validator.element_section(&s)?; + } + Payload::DataCountSection { count, range } => { + validator.data_count_section(count, &range)?; + } + Payload::DataSection(s) => { + validator.data_section(&s)?; + } + Payload::CodeSectionStart { count, range, .. } => { + validator.code_section_start(count, &range)?; + } + Payload::CodeSectionEntry(f) => { + validator.code_section_entry(&f)?; + } + + Payload::ModuleSection { + parser: inner, + range, + } => { + validator.module_section(&range)?; + parsers.push(parser); + parser = inner; + } + Payload::InstanceSection(s) => { + validator.instance_section(&s)?; + } + Payload::AliasSection(s) => { + validator.alias_section(&s)?; + } + Payload::CoreTypeSection(s) => { + validator.core_type_section(&s)?; + } + Payload::ComponentSection { + parser: inner, + range, + } => { + validator.component_section(&range)?; + parsers.push(parser); + parser = inner; + } + Payload::ComponentInstanceSection(s) => { + validator.component_instance_section(&s)?; + } + Payload::ComponentAliasSection(s) => { + validator.component_alias_section(&s)?; + } + Payload::ComponentTypeSection(s) => { + validator.component_type_section(&s)?; + } + Payload::ComponentCanonicalSection(s) => { + validator.component_canonical_section(&s)?; + } + Payload::ComponentStartSection(s) => { + validator.component_start_section(&s)?; + } + Payload::ComponentImportSection(s) => { + validator.component_import_section(&s)?; + } + Payload::ComponentExportSection(s) => { + validator.component_export_section(&s)?; + + if parsers.is_empty() { + for export in s { + let export = export?; + match export.kind { + ComponentExternalKind::Func => { + exported_functions.push((export.name, export.index)); + } + ComponentExternalKind::Type => { + exported_types.push((export.name, export.index)); + } + _ => {} + } + } + } + } + Payload::CustomSection { .. } => { + // Skip custom sections + } + Payload::UnknownSection { id, range, .. } => { + validator.unknown_section(id, &range)?; + } + Payload::End(offset) => { + let types = validator.end(offset)?; + + match parsers.pop() { + Some(parent) => parser = parent, + None => { + return Ok(Self { + types, + exported_types, + exported_functions, + }); + } + } + } + } + } + Chunk::NeedMoreData(_) => unreachable!(), + } + } + } +} + +/// Represents an interface decoder for WebAssembly components. +pub struct InterfaceDecoder<'a> { + info: &'a ComponentInfo<'a>, + interface: Interface, + type_map: IndexMap, + name_map: IndexMap, +} + +impl<'a> InterfaceDecoder<'a> { + /// Creates a new interface decoder for the given component information. + pub fn new(info: &'a ComponentInfo<'a>) -> Self { + Self { + info, + interface: Interface::default(), + name_map: IndexMap::new(), + type_map: IndexMap::new(), + } + } + + /// Consumes the decoder and returns the interface representation. + pub fn decode(mut self) -> Result { + // Populate names in the name map first + for (name, index) in &self.info.exported_types { + if let types::Type::Defined(_) = self.info.types.type_at(*index, false).unwrap() { + self.name_map.insert( + self.info.types.id_from_type_index(*index, false).unwrap(), + name, + ); + } + } + + for (name, index) in &self.info.exported_types { + let ty = match self.info.types.type_at(*index, false).unwrap() { + types::Type::ComponentFunc(ty) => ty, + _ => continue, + }; + + self.add_function(name, ty)?; + } + + for (name, index) in &self.info.exported_functions { + let ty = self.info.types.component_function_at(*index).unwrap(); + self.add_function(name, ty)?; + } + + Ok(self.interface) + } + + fn add_function(&mut self, func_name: &str, ty: &types::ComponentFuncType) -> Result<()> { + validate_id(func_name) + .with_context(|| format!("function name `{}` is not a valid identifier", func_name))?; + + let mut params = Vec::new(); + for (name, ty) in ty.params.iter() { + let name = name + .as_ref() + .ok_or_else(|| anyhow!("function `{}` has a parameter without a name", func_name))? + .clone(); + + validate_id(&name).with_context(|| { + format!( + "function `{}` has a parameter `{}` that is not a valid identifier", + func_name, name + ) + })?; + + params.push((name, self.decode_type(ty)?)); + } + + let result = self.decode_type(&ty.result)?; + + self.interface.functions.push(Function { + is_async: false, + docs: Docs::default(), + name: func_name.to_string(), + kind: FunctionKind::Freestanding, + params, + result, + }); + + Ok(()) + } + + fn decode_type(&mut self, ty: &types::ComponentValType) -> Result { + Ok(match ty { + types::ComponentValType::Primitive(ty) => self.decode_primitive(*ty)?, + types::ComponentValType::Type(id) => { + if let Some(ty) = self.type_map.get(id) { + return Ok(*ty); + } + + let name = self.name_map.get(id).map(ToString::to_string); + + if let Some(name) = name.as_deref() { + validate_id(name).with_context(|| { + format!("type name `{}` is not a valid identifier", name) + })?; + } + + let ty = match &self.info.types.type_from_id(*id).unwrap() { + types::Type::Defined(ty) => match ty { + types::ComponentDefinedType::Primitive(ty) => { + self.decode_named_primitive(name, ty)? + } + types::ComponentDefinedType::Record(r) => { + self.decode_record(name, r.fields.iter())? + } + types::ComponentDefinedType::Variant(v) => { + self.decode_variant(name, v.cases.iter())? + } + types::ComponentDefinedType::List(ty) => { + let inner = self.decode_type(ty)?; + Type::Id(self.alloc_type(name, TypeDefKind::List(inner))) + } + types::ComponentDefinedType::Tuple(t) => { + self.decode_tuple(name, &t.types)? + } + types::ComponentDefinedType::Flags(names) => { + self.decode_flags(name, names.iter())? + } + types::ComponentDefinedType::Enum(names) => { + self.decode_enum(name, names.iter())? + } + types::ComponentDefinedType::Union(u) => { + self.decode_union(name, &u.types)? + } + types::ComponentDefinedType::Option(ty) => self.decode_option(name, ty)?, + types::ComponentDefinedType::Expected(ok, err) => { + self.decode_expected(name, ok, err)? + } + }, + _ => unreachable!(), + }; + + self.type_map.insert(*id, ty); + ty + } + }) + } + + fn decode_named_primitive( + &mut self, + name: Option, + ty: &PrimitiveValType, + ) -> Result { + let mut ty = self.decode_primitive(*ty)?; + if let Some(name) = name { + validate_id(&name) + .with_context(|| format!("type name `{}` is not a valid identifier", name))?; + + ty = Type::Id(self.alloc_type(Some(name), TypeDefKind::Type(ty))); + } + + Ok(ty) + } + + fn decode_primitive(&mut self, ty: PrimitiveValType) -> Result { + Ok(match ty { + PrimitiveValType::Unit => Type::Unit, + PrimitiveValType::Bool => Type::Bool, + PrimitiveValType::S8 => Type::S8, + PrimitiveValType::U8 => Type::U8, + PrimitiveValType::S16 => Type::S16, + PrimitiveValType::U16 => Type::U16, + PrimitiveValType::S32 => Type::S32, + PrimitiveValType::U32 => Type::U32, + PrimitiveValType::S64 => Type::S64, + PrimitiveValType::U64 => Type::U64, + PrimitiveValType::Float32 => Type::Float32, + PrimitiveValType::Float64 => Type::Float64, + PrimitiveValType::Char => Type::Char, + PrimitiveValType::String => Type::String, + }) + } + + fn decode_record( + &mut self, + record_name: Option, + fields: impl ExactSizeIterator, + ) -> Result { + let record_name = + record_name.ok_or_else(|| anyhow!("interface has an unnamed record type"))?; + + let record = Record { + fields: fields + .map(|(name, ty)| { + validate_id(name).with_context(|| { + format!( + "record `{}` has a field `{}` that is not a valid identifier", + record_name, name + ) + })?; + + Ok(Field { + docs: Docs::default(), + name: name.to_string(), + ty: self.decode_type(ty)?, + }) + }) + .collect::>()?, + }; + + Ok(Type::Id(self.alloc_type( + Some(record_name), + TypeDefKind::Record(record), + ))) + } + + fn decode_variant( + &mut self, + variant_name: Option, + cases: impl ExactSizeIterator, + ) -> Result { + let variant_name = + variant_name.ok_or_else(|| anyhow!("interface has an unnamed variant type"))?; + + let variant = Variant { + cases: cases + .map(|(name, case)| { + validate_id(name).with_context(|| { + format!( + "variant `{}` has a case `{}` that is not a valid identifier", + variant_name, name + ) + })?; + + Ok(Case { + docs: Docs::default(), + name: name.to_string(), + ty: self.decode_type(&case.ty)?, + }) + }) + .collect::>()?, + }; + + Ok(Type::Id(self.alloc_type( + Some(variant_name), + TypeDefKind::Variant(variant), + ))) + } + + fn decode_tuple( + &mut self, + name: Option, + tys: &[types::ComponentValType], + ) -> Result { + let tuple = Tuple { + types: tys + .iter() + .map(|ty| self.decode_type(ty)) + .collect::>()?, + }; + + Ok(Type::Id(self.alloc_type(name, TypeDefKind::Tuple(tuple)))) + } + + fn decode_flags( + &mut self, + flags_name: Option, + names: impl ExactSizeIterator, + ) -> Result { + let flags_name = + flags_name.ok_or_else(|| anyhow!("interface has an unnamed flags type"))?; + + let flags = Flags { + flags: names + .map(|name| { + validate_id(name).with_context(|| { + format!( + "flags `{}` has a flag named `{}` that is not a valid identifier", + flags_name, name + ) + })?; + + Ok(Flag { + docs: Docs::default(), + name: name.clone(), + }) + }) + .collect::>()?, + }; + + Ok(Type::Id( + self.alloc_type(Some(flags_name), TypeDefKind::Flags(flags)), + )) + } + + fn decode_enum( + &mut self, + enum_name: Option, + names: impl ExactSizeIterator, + ) -> Result { + let enum_name = enum_name.ok_or_else(|| anyhow!("interface has an unnamed enum type"))?; + let enum_ = Enum { + cases: names + .map(|name| { + validate_id(name).with_context(|| { + format!( + "enum `{}` has a value `{}` that is not a valid identifier", + enum_name, name + ) + })?; + + Ok(EnumCase { + docs: Docs::default(), + name: name.to_string(), + }) + }) + .collect::>()?, + }; + + Ok(Type::Id( + self.alloc_type(Some(enum_name), TypeDefKind::Enum(enum_)), + )) + } + + fn decode_union( + &mut self, + name: Option, + tys: &[types::ComponentValType], + ) -> Result { + let union = Union { + cases: tys + .iter() + .map(|ty| { + Ok(UnionCase { + docs: Docs::default(), + ty: self.decode_type(ty)?, + }) + }) + .collect::>()?, + }; + + Ok(Type::Id(self.alloc_type(name, TypeDefKind::Union(union)))) + } + + fn decode_option( + &mut self, + name: Option, + payload: &types::ComponentValType, + ) -> Result { + let payload = self.decode_type(payload)?; + Ok(Type::Id( + self.alloc_type(name, TypeDefKind::Option(payload)), + )) + } + + fn decode_expected( + &mut self, + name: Option, + ok: &types::ComponentValType, + err: &types::ComponentValType, + ) -> Result { + let ok = self.decode_type(ok)?; + let err = self.decode_type(err)?; + Ok(Type::Id(self.alloc_type( + name, + TypeDefKind::Expected(Expected { ok, err }), + ))) + } + + fn alloc_type(&mut self, name: Option, kind: TypeDefKind) -> TypeId { + self.interface.types.alloc(TypeDef { + docs: Docs::default(), + kind, + name, + foreign_module: None, + }) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/encoding.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/encoding.rs new file mode 100644 index 0000000..d6b493d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/encoding.rs @@ -0,0 +1,1771 @@ +use crate::{ + validation::{expected_export_name, validate_module}, + StringEncoding, +}; +use anyhow::{anyhow, bail, Context, Result}; +use indexmap::{map::Entry, IndexMap, IndexSet}; +use std::{ + hash::{Hash, Hasher}, + ops::{BitOr, BitOrAssign}, +}; +use wasm_encoder::*; +use wasmparser::{Validator, WasmFeatures}; +use wit_parser::{ + abi::{AbiVariant, WasmSignature, WasmType}, + Enum, Expected, Flags, Function, FunctionKind, Interface, Record, Tuple, Type, TypeDef, + TypeDefKind, Union, Variant, +}; + +const INDIRECT_TABLE_NAME: &str = "$imports"; + +fn to_val_type(ty: &WasmType) -> ValType { + match ty { + WasmType::I32 => ValType::I32, + WasmType::I64 => ValType::I64, + WasmType::F32 => ValType::F32, + WasmType::F64 => ValType::F64, + } +} + +struct TypeKey<'a> { + interface: &'a Interface, + ty: Type, +} + +impl Hash for TypeKey<'_> { + fn hash(&self, state: &mut H) { + match self.ty { + Type::Id(id) => TypeDefKey::new(self.interface, &self.interface.types[id]).hash(state), + _ => self.ty.hash(state), + } + } +} + +impl PartialEq for TypeKey<'_> { + fn eq(&self, other: &Self) -> bool { + match (self.ty, other.ty) { + (Type::Id(id), Type::Id(other_id)) => { + TypeDefKey::new(self.interface, &self.interface.types[id]) + == TypeDefKey::new(other.interface, &other.interface.types[other_id]) + } + _ => self.ty.eq(&other.ty), + } + } +} + +impl Eq for TypeKey<'_> {} + +/// Represents a key type for interface type definitions. +pub struct TypeDefKey<'a> { + interface: &'a Interface, + def: &'a TypeDef, +} + +impl<'a> TypeDefKey<'a> { + fn new(interface: &'a Interface, def: &'a TypeDef) -> Self { + Self { interface, def } + } +} + +impl PartialEq for TypeDefKey<'_> { + fn eq(&self, other: &Self) -> bool { + let def = self.def; + let other_def = other.def; + def.name == other_def.name + && match (&def.kind, &other_def.kind) { + (TypeDefKind::Record(r1), TypeDefKind::Record(r2)) => { + if r1.fields.len() != r2.fields.len() { + return false; + } + + r1.fields.iter().zip(r2.fields.iter()).all(|(f1, f2)| { + f1.name == f2.name + && TypeKey { + interface: self.interface, + ty: f1.ty, + } + .eq(&TypeKey { + interface: other.interface, + ty: f2.ty, + }) + }) + } + (TypeDefKind::Tuple(t1), TypeDefKind::Tuple(t2)) => { + if t1.types.len() != t2.types.len() { + return false; + } + + t1.types.iter().zip(t2.types.iter()).all(|(t1, t2)| { + TypeKey { + interface: self.interface, + ty: *t1, + } + .eq(&TypeKey { + interface: other.interface, + ty: *t2, + }) + }) + } + (TypeDefKind::Flags(f1), TypeDefKind::Flags(f2)) => { + if f1.flags.len() != f2.flags.len() { + return false; + } + + f1.flags + .iter() + .zip(f2.flags.iter()) + .all(|(f1, f2)| f1.name == f2.name) + } + (TypeDefKind::Variant(v1), TypeDefKind::Variant(v2)) => { + if v1.cases.len() != v2.cases.len() { + return false; + } + + v1.cases.iter().zip(v2.cases.iter()).all(|(c1, c2)| { + c1.name == c2.name + && TypeKey { + interface: self.interface, + ty: c1.ty, + } == TypeKey { + interface: other.interface, + ty: c2.ty, + } + }) + } + (TypeDefKind::Union(v1), TypeDefKind::Union(v2)) => { + if v1.cases.len() != v2.cases.len() { + return false; + } + + v1.cases.iter().zip(v2.cases.iter()).all(|(c1, c2)| { + TypeKey { + interface: self.interface, + ty: c1.ty, + } == TypeKey { + interface: other.interface, + ty: c2.ty, + } + }) + } + (TypeDefKind::Enum(e1), TypeDefKind::Enum(e2)) => { + if e1.cases.len() != e2.cases.len() { + return false; + } + + e1.cases + .iter() + .zip(e2.cases.iter()) + .all(|(c1, c2)| c1.name == c2.name) + } + (TypeDefKind::List(t1), TypeDefKind::List(t2)) + | (TypeDefKind::Type(t1), TypeDefKind::Type(t2)) + | (TypeDefKind::Option(t1), TypeDefKind::Option(t2)) => TypeKey { + interface: self.interface, + ty: *t1, + } + .eq(&TypeKey { + interface: other.interface, + ty: *t2, + }), + (TypeDefKind::Expected(e1), TypeDefKind::Expected(e2)) => { + TypeKey { + interface: self.interface, + ty: e1.ok, + } == TypeKey { + interface: other.interface, + ty: e2.ok, + } && TypeKey { + interface: self.interface, + ty: e1.err, + } == TypeKey { + interface: other.interface, + ty: e2.err, + } + } + _ => false, + } + } +} + +impl Eq for TypeDefKey<'_> {} + +impl Hash for TypeDefKey<'_> { + fn hash(&self, state: &mut H) { + let def = self.def; + def.name.hash(state); + match &def.kind { + TypeDefKind::Record(r) => { + state.write_u8(0); + for f in &r.fields { + f.name.hash(state); + TypeKey { + interface: self.interface, + ty: f.ty, + } + .hash(state); + } + } + TypeDefKind::Tuple(t) => { + state.write_u8(1); + for ty in &t.types { + TypeKey { + interface: self.interface, + ty: *ty, + } + .hash(state); + } + } + TypeDefKind::Flags(r) => { + state.write_u8(2); + for f in &r.flags { + f.name.hash(state); + } + } + TypeDefKind::Variant(v) => { + state.write_u8(3); + for c in &v.cases { + c.name.hash(state); + TypeKey { + interface: self.interface, + ty: c.ty, + } + .hash(state); + } + } + TypeDefKind::Enum(e) => { + state.write_u8(4); + for c in &e.cases { + c.name.hash(state); + } + } + TypeDefKind::List(ty) => { + state.write_u8(5); + TypeKey { + interface: self.interface, + ty: *ty, + } + .hash(state); + } + TypeDefKind::Type(ty) => { + state.write_u8(6); + TypeKey { + interface: self.interface, + ty: *ty, + } + .hash(state); + } + TypeDefKind::Option(ty) => { + state.write_u8(7); + TypeKey { + interface: self.interface, + ty: *ty, + } + .hash(state); + } + TypeDefKind::Expected(e) => { + state.write_u8(8); + TypeKey { + interface: self.interface, + ty: e.ok, + } + .hash(state); + TypeKey { + interface: self.interface, + ty: e.err, + } + .hash(state); + } + TypeDefKind::Union(u) => { + state.write_u8(9); + u.cases.len().hash(state); + for case in u.cases.iter() { + TypeKey { + interface: self.interface, + ty: case.ty, + } + .hash(state); + } + } + TypeDefKind::Future(_) => todo!("hash for future"), + TypeDefKind::Stream(_) => todo!("hash for stream"), + } + } +} + +/// Represents a key type for interface function definitions. +pub struct FunctionKey<'a> { + interface: &'a Interface, + func: &'a Function, +} + +impl PartialEq for FunctionKey<'_> { + fn eq(&self, other: &Self) -> bool { + if self.func.params.len() != other.func.params.len() { + return false; + } + + self.func + .params + .iter() + .zip(other.func.params.iter()) + .all(|((n1, t1), (n2, t2))| { + n1 == n2 + && TypeKey { + interface: self.interface, + ty: *t1, + } + .eq(&TypeKey { + interface: other.interface, + ty: *t2, + }) + }) + && TypeKey { + interface: self.interface, + ty: self.func.result, + } + .eq(&TypeKey { + interface: other.interface, + ty: other.func.result, + }) + } +} + +impl Eq for FunctionKey<'_> {} + +impl Hash for FunctionKey<'_> { + fn hash(&self, state: &mut H) { + self.func.params.len().hash(state); + for (name, ty) in &self.func.params { + name.hash(state); + TypeKey { + interface: self.interface, + ty: *ty, + } + .hash(state); + } + TypeKey { + interface: self.interface, + ty: self.func.result, + } + .hash(state); + } +} + +#[derive(Default)] +struct InstanceTypeEncoder<'a> { + ty: InstanceType, + aliased_types: IndexMap, + exported_types: IndexMap<&'a str, ComponentTypeRef>, +} + +impl<'a> InstanceTypeEncoder<'a> { + fn export(&mut self, name: &'a str, type_ref: ComponentTypeRef) -> Result<()> { + match self.exported_types.entry(name) { + Entry::Occupied(e) => { + if *e.get() != type_ref { + bail!("duplicate export `{}`", name) + } + } + Entry::Vacant(entry) => { + entry.insert(type_ref); + let alias = self.alias_type(type_ref); + self.ty.export(name, alias); + } + } + + Ok(()) + } + + fn alias_type(&mut self, type_ref: ComponentTypeRef) -> ComponentTypeRef { + match self.aliased_types.entry(type_ref) { + Entry::Occupied(e) => *e.get(), + Entry::Vacant(e) => { + let index = self.ty.type_count(); + let (alias, outer_index) = match type_ref { + ComponentTypeRef::Module(outer) => (ComponentTypeRef::Module(index), outer), + ComponentTypeRef::Func(outer) => (ComponentTypeRef::Func(index), outer), + ComponentTypeRef::Value(ComponentValType::Primitive(_)) => unreachable!(), + ComponentTypeRef::Value(ComponentValType::Type(outer)) => ( + ComponentTypeRef::Value(ComponentValType::Type(index)), + outer, + ), + ComponentTypeRef::Type(bounds, outer) => { + (ComponentTypeRef::Type(bounds, index), outer) + } + ComponentTypeRef::Instance(outer) => (ComponentTypeRef::Instance(index), outer), + ComponentTypeRef::Component(outer) => { + (ComponentTypeRef::Component(index), outer) + } + }; + + self.ty.alias_outer_type(1, outer_index); + e.insert(alias); + alias + } + } + } +} + +#[derive(Default)] +struct TypeEncoder<'a> { + types: ComponentTypeSection, + type_map: IndexMap, u32>, + func_type_map: IndexMap, u32>, + exports: ComponentExportSection, + exported_types: IndexMap<&'a str, ComponentTypeRef>, +} + +impl<'a> TypeEncoder<'a> { + fn finish(&self, component: &mut Component) { + if !self.types.is_empty() { + component.section(&self.types); + } + + if !self.exports.is_empty() { + component.section(&self.exports); + } + } + + fn encode_instance_imports( + &mut self, + interfaces: &'a [Interface], + required_imports: &IndexSet<&'a str>, + imports: &mut ImportEncoder<'a>, + ) -> Result<()> { + for import in interfaces { + if !required_imports.contains(import.name.as_str()) { + continue; + } + + Self::validate_interface(import)?; + + let mut instance = InstanceTypeEncoder::default(); + + for func in &import.functions { + Self::validate_function(func)?; + + let index = self.encode_func_type(import, func, false)?; + instance.export(&func.name, ComponentTypeRef::Func(index))?; + } + + let index = self.encode_instance_type(&instance.ty); + imports.import(import, ComponentTypeRef::Instance(index))?; + } + + Ok(()) + } + + fn encode_func_types( + &mut self, + interfaces: impl Iterator, + export_func_types: bool, + ) -> Result<()> { + for (export, is_default) in interfaces { + Self::validate_interface(export)?; + + // TODO: stick interface documentation in a custom section? + + for func in &export.functions { + Self::validate_function(func)?; + + let index = self.encode_func_type(export, func, is_default)?; + + if export_func_types { + self.export_type(&func.name, ComponentTypeRef::Func(index))?; + } + } + } + + Ok(()) + } + + fn encode_instance_type(&mut self, ty: &InstanceType) -> u32 { + let index = self.types.len(); + self.types.instance(ty); + index + } + + fn encode_func_type( + &mut self, + interface: &'a Interface, + func: &'a Function, + export_named_types: bool, + ) -> Result { + let key = FunctionKey { interface, func }; + if let Some(index) = self.func_type_map.get(&key) { + return Ok(*index); + } + + // Encode all referenced parameter types from this function. + let params: Vec<_> = func + .params + .iter() + .map(|(name, ty)| { + Ok(( + Some(name.as_str()), + self.encode_valtype(interface, ty, export_named_types)?, + )) + }) + .collect::>()?; + let result = self.encode_valtype(interface, &func.result, export_named_types)?; + + // Encode the function type + let index = self.types.len(); + self.types.function(params, result); + self.func_type_map.insert(key, index); + Ok(index) + } + + fn encode_valtype( + &mut self, + interface: &'a Interface, + ty: &Type, + export_named_types: bool, + ) -> Result { + Ok(match ty { + Type::Unit => ComponentValType::Primitive(PrimitiveValType::Unit), + Type::Bool => ComponentValType::Primitive(PrimitiveValType::Bool), + Type::U8 => ComponentValType::Primitive(PrimitiveValType::U8), + Type::U16 => ComponentValType::Primitive(PrimitiveValType::U16), + Type::U32 => ComponentValType::Primitive(PrimitiveValType::U32), + Type::U64 => ComponentValType::Primitive(PrimitiveValType::U64), + Type::S8 => ComponentValType::Primitive(PrimitiveValType::S8), + Type::S16 => ComponentValType::Primitive(PrimitiveValType::S16), + Type::S32 => ComponentValType::Primitive(PrimitiveValType::S32), + Type::S64 => ComponentValType::Primitive(PrimitiveValType::S64), + Type::Float32 => ComponentValType::Primitive(PrimitiveValType::Float32), + Type::Float64 => ComponentValType::Primitive(PrimitiveValType::Float64), + Type::Char => ComponentValType::Primitive(PrimitiveValType::Char), + Type::String => ComponentValType::Primitive(PrimitiveValType::String), + Type::Id(id) => { + let ty = &interface.types[*id]; + let key = TypeDefKey::new(interface, &interface.types[*id]); + let encoded = if let Some(index) = self.type_map.get(&key) { + ComponentValType::Type(*index) + } else { + let mut encoded = match &ty.kind { + TypeDefKind::Record(r) => { + self.encode_record(interface, r, export_named_types)? + } + TypeDefKind::Tuple(t) => { + self.encode_tuple(interface, t, export_named_types)? + } + TypeDefKind::Flags(r) => self.encode_flags(r)?, + TypeDefKind::Variant(v) => { + self.encode_variant(interface, v, export_named_types)? + } + TypeDefKind::Union(u) => { + self.encode_union(interface, u, export_named_types)? + } + TypeDefKind::Option(t) => { + self.encode_option(interface, t, export_named_types)? + } + TypeDefKind::Expected(e) => { + self.encode_expected(interface, e, export_named_types)? + } + TypeDefKind::Enum(e) => self.encode_enum(e)?, + TypeDefKind::List(ty) => { + let ty = self.encode_valtype(interface, ty, export_named_types)?; + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.list(ty); + ComponentValType::Type(index) + } + TypeDefKind::Type(ty) => { + self.encode_valtype(interface, ty, export_named_types)? + } + TypeDefKind::Future(_) => todo!("encoding for future type"), + TypeDefKind::Stream(_) => todo!("encoding for stream type"), + }; + + if ty.name.is_some() { + if let ComponentValType::Primitive(ty) = encoded { + // Named primitive types need entries in the type section, so + // convert this to a type reference + let index = self.types.len(); + self.types.defined_type().primitive(ty); + encoded = ComponentValType::Type(index); + } + } + + if let ComponentValType::Type(index) = encoded { + self.type_map.insert(key, index); + } + + encoded + }; + + if export_named_types { + // Named types need to be exported + if let Some(name) = ty.name.as_deref() { + if let ComponentValType::Type(index) = encoded { + self.export_type(name, ComponentTypeRef::Type(TypeBounds::Eq, index))?; + } + } + } + + encoded + } + Type::Handle(_) => { + bail!("the use of handle types in interfaces is not currently supported") + } + }) + } + + fn encode_record( + &mut self, + interface: &'a Interface, + record: &Record, + export_named_types: bool, + ) -> Result { + let fields = record + .fields + .iter() + .map(|f| { + Ok(( + f.name.as_str(), + self.encode_valtype(interface, &f.ty, export_named_types)?, + )) + }) + .collect::>>()?; + + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.record(fields); + Ok(ComponentValType::Type(index)) + } + + fn encode_tuple( + &mut self, + interface: &'a Interface, + tuple: &Tuple, + export_named_types: bool, + ) -> Result { + let tys = tuple + .types + .iter() + .map(|ty| self.encode_valtype(interface, ty, export_named_types)) + .collect::>>()?; + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.tuple(tys); + Ok(ComponentValType::Type(index)) + } + + fn encode_flags(&mut self, flags: &Flags) -> Result { + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.flags(flags.flags.iter().map(|f| f.name.as_str())); + Ok(ComponentValType::Type(index)) + } + + fn encode_variant( + &mut self, + interface: &'a Interface, + variant: &Variant, + export_named_types: bool, + ) -> Result { + let cases = variant + .cases + .iter() + .map(|c| { + Ok(( + c.name.as_str(), + self.encode_valtype(interface, &c.ty, export_named_types)?, + None, // TODO: support defaulting case values in the future + )) + }) + .collect::>>()?; + + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.variant(cases); + Ok(ComponentValType::Type(index)) + } + + fn encode_union( + &mut self, + interface: &'a Interface, + union: &Union, + export_named_types: bool, + ) -> Result { + let tys = union + .cases + .iter() + .map(|c| self.encode_valtype(interface, &c.ty, export_named_types)) + .collect::>>()?; + + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.union(tys); + Ok(ComponentValType::Type(index)) + } + + fn encode_option( + &mut self, + interface: &'a Interface, + payload: &Type, + export_named_types: bool, + ) -> Result { + let ty = self.encode_valtype(interface, payload, export_named_types)?; + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.option(ty); + Ok(ComponentValType::Type(index)) + } + + fn encode_expected( + &mut self, + interface: &'a Interface, + expected: &Expected, + export_named_types: bool, + ) -> Result { + let ok = self.encode_valtype(interface, &expected.ok, export_named_types)?; + let error = self.encode_valtype(interface, &expected.err, export_named_types)?; + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.expected(ok, error); + Ok(ComponentValType::Type(index)) + } + + fn encode_enum(&mut self, enum_: &Enum) -> Result { + let index = self.types.len(); + let encoder = self.types.defined_type(); + encoder.enum_type(enum_.cases.iter().map(|c| c.name.as_str())); + Ok(ComponentValType::Type(index)) + } + + fn export_type(&mut self, name: &'a str, type_ref: ComponentTypeRef) -> Result<()> { + match self.exported_types.entry(name) { + Entry::Occupied(e) => { + if *e.get() != type_ref { + bail!("duplicate export `{}`", name) + } + } + Entry::Vacant(entry) => { + entry.insert(type_ref); + + let index = match type_ref { + ComponentTypeRef::Module(index) => index, + ComponentTypeRef::Func(index) => index, + ComponentTypeRef::Value(ComponentValType::Primitive(_)) => unreachable!(), + ComponentTypeRef::Value(ComponentValType::Type(index)) => index, + ComponentTypeRef::Type(_, index) => index, + ComponentTypeRef::Instance(index) => index, + ComponentTypeRef::Component(index) => index, + }; + + self.exports.export(name, ComponentExportKind::Type, index); + } + } + + Ok(()) + } + + fn validate_interface(interface: &Interface) -> Result<()> { + if interface.resources.len() != 0 { + bail!("the use of resources in interfaces is not currently not supported"); + } + + Ok(()) + } + + fn validate_function(function: &Function) -> Result<()> { + if function.name.is_empty() { + bail!("interface has an unnamed function"); + } + + if !matches!(function.kind, FunctionKind::Freestanding) { + bail!( + "unsupported function `{}`: only free-standing functions are currently supported", + function.name + ); + } + + if function.is_async { + bail!( + "unsupported function `{}`: only synchronous functions are currently supported", + function.name + ); + } + + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +enum RequiredOptions { + // No required options. + None, + // Only the memory option is required. + Memory, + // The realloc option (and, by extension, the memory option) is required. + Realloc, + // The encoding, realloc, and memory options are required. + All, +} + +impl RequiredOptions { + fn for_types<'a>(interface: &Interface, mut types: impl Iterator) -> Self { + match types.try_fold(Self::None, |mut acc, ty| { + acc |= Self::for_type(interface, ty); + if acc == Self::All { + // If something requires all the options, then we're done searching. + // Returning an error here so that the operation terminates early. + Err(acc) + } else { + Ok(acc) + } + }) { + Ok(o) | Err(o) => o, + } + } + + fn for_type(interface: &Interface, ty: &Type) -> Self { + match ty { + Type::Id(id) => match &interface.types[*id].kind { + TypeDefKind::Record(r) => { + Self::for_types(interface, r.fields.iter().map(|f| &f.ty)) + } + TypeDefKind::Tuple(t) => Self::for_types(interface, t.types.iter()), + TypeDefKind::Flags(_) => Self::None, + TypeDefKind::Option(t) => Self::for_type(interface, t), + TypeDefKind::Expected(e) => { + Self::for_type(interface, &e.ok) | Self::for_type(interface, &e.err) + } + TypeDefKind::Variant(v) => { + Self::for_types(interface, v.cases.iter().map(|c| &c.ty)) + } + TypeDefKind::Union(v) => Self::for_types(interface, v.cases.iter().map(|c| &c.ty)), + TypeDefKind::Enum(_) => Self::None, + TypeDefKind::List(t) => { + // Lists need at least the `realloc` option, but may require + // the encoding option if there's a string somewhere in the + // type. + Self::for_type(interface, t) | Self::Realloc + } + TypeDefKind::Type(t) => Self::for_type(interface, t), + TypeDefKind::Future(_) => todo!("encoding for future"), + TypeDefKind::Stream(_) => todo!("encoding for stream"), + }, + Type::String => Self::All, + _ => Self::None, + } + } + + fn for_function(interface: &Interface, function: &Function) -> Self { + Self::for_types( + interface, + function + .params + .iter() + .map(|(_, ty)| ty) + .chain([&function.result]), + ) + } + + fn into_iter( + self, + encoding: StringEncoding, + memory_index: Option, + realloc_index: Option, + ) -> Result + ExactSizeIterator> { + #[derive(Default)] + struct Iter { + options: [Option; 3], + current: usize, + count: usize, + } + + impl Iter { + fn push(&mut self, option: CanonicalOption) { + assert!(self.count < self.options.len()); + self.options[self.count] = Some(option); + self.count += 1; + } + } + + impl Iterator for Iter { + type Item = CanonicalOption; + + fn next(&mut self) -> Option { + if self.current == self.count { + return None; + } + let option = self.options[self.current]; + self.current += 1; + option + } + + fn size_hint(&self) -> (usize, Option) { + (self.count - self.current, Some(self.count - self.current)) + } + } + + impl ExactSizeIterator for Iter {} + + let mut iter = Iter::default(); + + if self == RequiredOptions::None { + return Ok(iter); + } + + iter.push(CanonicalOption::Memory(memory_index.ok_or_else(|| { + anyhow!("module does not export a memory named `memory`") + })?)); + + if self == RequiredOptions::Memory { + return Ok(iter); + } + + iter.push(CanonicalOption::Realloc(realloc_index.ok_or_else( + || anyhow!("module does not export a function named `canonical_abi_realloc`"), + )?)); + + if self == RequiredOptions::Realloc { + return Ok(iter); + } + + assert_eq!(self, RequiredOptions::All); + + iter.push(encoding.into()); + Ok(iter) + } +} + +impl BitOrAssign for RequiredOptions { + fn bitor_assign(&mut self, rhs: Self) { + *self = *self | rhs; + } +} + +impl BitOr for RequiredOptions { + type Output = RequiredOptions; + + fn bitor(self, rhs: Self) -> Self::Output { + match (self, rhs) { + (Self::All, _) | (_, Self::All) => Self::All, + (Self::Realloc, _) | (_, Self::Realloc) => Self::Realloc, + (Self::Memory, _) | (_, Self::Memory) => Self::Memory, + (Self::None, Self::None) => Self::None, + } + } +} + +/// State relating to encoding a component. +#[derive(Default)] +struct EncodingState { + /// The component being encoded. + component: Component, + /// The various index spaces used in the encoding. + indexes: Indexes, + /// The index into the core module index space for the inner core module. + /// + /// If `None`, the core module has not been encoded. + module_index: Option, + /// The index into the core instance index space for the inner core module. + /// + /// If `None`, the core module has not been instantiated. + instance_index: Option, + /// The index in the core memory index space for the exported memory. + /// + /// If `None`, then the memory has not yet been aliased. + memory_index: Option, + /// The index in the core function index space for the realloc function. + /// + /// If `None`, then the realloc function has not yet been aliased. + realloc_index: Option, + /// The index of the shim instance used for lowering imports into the core instance. + /// + /// If `None`, then the shim instance how not yet been encoded. + shim_instance_index: Option, + /// The index of the fixups module to instantiate to fill in the lowered imports. + /// + /// If `None`, then a fixup module has not yet been encoded. + fixups_module_index: Option, +} + +impl EncodingState { + fn encode_core_module(&mut self, module: &[u8]) -> u32 { + *self.module_index.get_or_insert_with(|| { + self.component.section(&wasm_encoder::RawSection { + id: ComponentSectionId::CoreModule.into(), + data: module, + }); + + self.indexes.alloc_core_module() + }) + } + + fn encode_core_instantiation( + &mut self, + encoding: StringEncoding, + imports: &ImportEncoder, + has_memory: bool, + has_realloc: bool, + ) -> Result<()> { + if imports.map.is_empty() { + self.instantiate_core_module([], has_memory, has_realloc); + return Ok(()); + } + + // Encode a shim instantiation if needed + self.encode_shim_instantiation(imports); + + let mut instances = InstanceSection::new(); + let args: Vec<_> = imports + .map + .iter() + .enumerate() + .map(|(instance_index, (name, import))| { + let mut exports = Vec::with_capacity(import.direct.len() + import.indirect.len()); + + let mut core_aliases = AliasSection::new(); + for lowering in &import.indirect { + let index = self.alias_core_item( + &mut core_aliases, + self.shim_instance_index + .expect("shim should be instantiated"), + ExportKind::Func, + &lowering.export_name, + ); + exports.push((lowering.name, ExportKind::Func, index)); + } + self.component.section(&core_aliases); + + let mut aliases = ComponentAliasSection::new(); + let mut functions = CanonicalFunctionSection::new(); + + for lowering in &import.direct { + let func_index = + self.alias_func(&mut aliases, instance_index as u32, lowering.name); + let core_func_index = self.lower_func(&mut functions, func_index, []); + exports.push((lowering.name, ExportKind::Func, core_func_index)); + } + + self.component.section(&aliases); + self.component.section(&functions); + + let index = self.instantiate_core_exports(&mut instances, exports); + (*name, ModuleArg::Instance(index)) + }) + .collect(); + + self.component.section(&instances); + + self.instantiate_core_module(args, has_memory, has_realloc); + self.encode_indirect_lowerings(encoding, imports) + } + + fn encode_imports(&mut self, imports: &ImportEncoder) { + let mut section = ComponentImportSection::default(); + + for (name, import) in &imports.map { + section.import(name, import.ty); + self.indexes.alloc_instance(); + } + + if !section.is_empty() { + self.component.section(§ion); + } + } + + fn encode_exports<'a>( + &mut self, + encoding: StringEncoding, + exports: impl Iterator, + func_types: &IndexMap, u32>, + ) -> Result<()> { + let core_instance_index = self.instance_index.expect("must be instantiated"); + + let mut section = ComponentExportSection::default(); + let mut instances = ComponentInstanceSection::new(); + + for (export, is_default) in exports { + // Alias the exports from the core module + let mut aliases = AliasSection::new(); + let mut functions = CanonicalFunctionSection::new(); + let mut interface_exports = Vec::new(); + for func in &export.functions { + let name = + expected_export_name((!is_default).then(|| export.name.as_str()), &func.name); + + let core_func_index = self.alias_core_item( + &mut aliases, + core_instance_index, + ExportKind::Func, + name.as_ref(), + ); + + let ty = *func_types + .get(&FunctionKey { + interface: export, + func, + }) + .expect("the type should be encoded"); + + let sig = export.wasm_signature(AbiVariant::GuestExport, func); + let options = RequiredOptions::for_function(export, func) + | (if sig.retptr || sig.indirect_params { + RequiredOptions::Memory + } else { + RequiredOptions::None + }); + + // TODO: support the post-return option somehow (not yet supported in wit-bindgen) + let func_index = self.lift_func( + &mut functions, + core_func_index, + ty, + options.into_iter(encoding, self.memory_index, self.realloc_index)?, + ); + + if is_default { + // Directly export the lifted function + section.export(&func.name, ComponentExportKind::Func, func_index); + } else { + // Otherwise, add it to the list for later instantiation + interface_exports.push(( + func.name.as_str(), + ComponentExportKind::Func, + func_index, + )); + } + } + + self.component.section(&aliases); + self.component.section(&functions); + + if !interface_exports.is_empty() { + if export.name.is_empty() { + bail!("cannot export an unnamed interface"); + } + + let instance_index = self.instantiate_exports(&mut instances, interface_exports); + section.export(&export.name, ComponentExportKind::Instance, instance_index); + } + } + + if !instances.is_empty() { + self.component.section(&instances); + } + + if !section.is_empty() { + self.component.section(§ion); + } + + Ok(()) + } + + fn encode_shim_instantiation(&mut self, imports: &ImportEncoder) { + if imports.indirect_count == 0 { + return; + } + + assert!(self.shim_instance_index.is_none()); + assert!(self.fixups_module_index.is_none()); + + // This function encodes two modules: + // - A shim module that defines a table and exports functions + // that indirectly call through the table. + // - A fixup module that imports that table and a set of functions + // and populates the imported table via active element segments. The + // fixup module is used to populate the shim's table once the + // imported functions have been lowered. + + let mut types = TypeSection::new(); + let mut tables = TableSection::new(); + let mut functions = FunctionSection::new(); + let mut exports = ExportSection::new(); + let mut code = CodeSection::new(); + let mut sigs = IndexMap::new(); + let mut imports_section = ImportSection::new(); + let mut elements = ElementSection::new(); + let mut func_indexes = Vec::new(); + + let mut func_index = 0; + for import in imports.map.values() { + for lowering in &import.indirect { + let type_index = *sigs.entry(&lowering.sig).or_insert_with(|| { + let index = types.len(); + types.function( + lowering.sig.params.iter().map(to_val_type), + lowering.sig.results.iter().map(to_val_type), + ); + index + }); + + functions.function(type_index); + Self::encode_shim_function( + type_index, + func_index, + &mut code, + lowering.sig.params.len() as u32, + ); + exports.export(&lowering.export_name, ExportKind::Func, func_index); + + imports_section.import("", &lowering.export_name, EntityType::Function(type_index)); + func_indexes.push(func_index); + + func_index += 1; + } + } + + let table_type = TableType { + element_type: ValType::FuncRef, + minimum: func_index, + maximum: Some(func_index), + }; + + tables.table(table_type); + + exports.export(INDIRECT_TABLE_NAME, ExportKind::Table, 0); + imports_section.import("", INDIRECT_TABLE_NAME, table_type); + + elements.active( + None, + &Instruction::I32Const(0), + ValType::FuncRef, + Elements::Functions(&func_indexes), + ); + + let mut shim = Module::new(); + shim.section(&types); + shim.section(&functions); + shim.section(&tables); + shim.section(&exports); + shim.section(&code); + + let mut fixups = Module::default(); + fixups.section(&types); + fixups.section(&imports_section); + fixups.section(&elements); + + let shim_module_index = self.indexes.alloc_core_module(); + self.component.section(&ModuleSection(&shim)); + + self.fixups_module_index = Some(self.indexes.alloc_core_module()); + self.component.section(&ModuleSection(&fixups)); + + let mut instances = InstanceSection::default(); + self.shim_instance_index = Some(self.instantiate(&mut instances, shim_module_index, [])); + self.component.section(&instances); + } + + fn encode_shim_function( + type_index: u32, + func_index: u32, + code: &mut CodeSection, + param_count: u32, + ) { + let mut func = wasm_encoder::Function::new(std::iter::empty()); + for i in 0..param_count { + func.instruction(&Instruction::LocalGet(i)); + } + func.instruction(&Instruction::I32Const(func_index as i32)); + func.instruction(&Instruction::CallIndirect { + ty: type_index, + table: 0, + }); + func.instruction(&Instruction::End); + code.function(&func); + } + + fn encode_indirect_lowerings( + &mut self, + encoding: StringEncoding, + imports: &ImportEncoder, + ) -> Result<()> { + if imports.indirect_count == 0 { + return Ok(()); + } + + let shim_instance_index = self + .shim_instance_index + .expect("must have an instantiated shim"); + + let mut core_aliases = AliasSection::new(); + let table_index = self.alias_core_item( + &mut core_aliases, + shim_instance_index, + ExportKind::Table, + INDIRECT_TABLE_NAME, + ); + self.component.section(&core_aliases); + + let mut exports = Vec::with_capacity(imports.indirect_count as usize); + exports.push((INDIRECT_TABLE_NAME, ExportKind::Table, table_index)); + + let mut aliases = ComponentAliasSection::new(); + let mut functions = CanonicalFunctionSection::new(); + for (instance_index, import) in imports.map.values().enumerate() { + for lowering in &import.indirect { + let func_index = + self.alias_func(&mut aliases, instance_index as u32, lowering.name); + + let core_func_index = self.lower_func( + &mut functions, + func_index, + lowering + .options + .into_iter(encoding, self.memory_index, self.realloc_index)?, + ); + + exports.push(( + lowering.export_name.as_str(), + ExportKind::Func, + core_func_index, + )); + } + } + + self.component.section(&aliases); + self.component.section(&functions); + + let mut instances = InstanceSection::new(); + let instance_index = self.instantiate_core_exports(&mut instances, exports); + self.instantiate( + &mut instances, + self.fixups_module_index.expect("must have fixup module"), + [("", ModuleArg::Instance(instance_index))], + ); + self.component.section(&instances); + Ok(()) + } + + fn instantiate_core_module<'a, A>(&mut self, args: A, has_memory: bool, has_realloc: bool) + where + A: IntoIterator, + A::IntoIter: ExactSizeIterator, + { + assert!(self.instance_index.is_none()); + + let mut instances = InstanceSection::new(); + let mut aliases = AliasSection::new(); + + let instance_index = self.instantiate( + &mut instances, + self.module_index.expect("core module encoded"), + args, + ); + + if has_memory { + self.memory_index = Some(self.alias_core_item( + &mut aliases, + instance_index, + ExportKind::Memory, + "memory", + )); + } + + if has_realloc { + self.realloc_index = Some(self.alias_core_item( + &mut aliases, + instance_index, + ExportKind::Func, + "canonical_abi_realloc", + )); + } + + self.component.section(&instances); + self.component.section(&aliases); + + self.instance_index = Some(instance_index); + } + + fn instantiate<'a, A>( + &mut self, + instances: &mut InstanceSection, + module_index: u32, + args: A, + ) -> u32 + where + A: IntoIterator, + A::IntoIter: ExactSizeIterator, + { + instances.instantiate(module_index, args); + self.indexes.alloc_core_instance() + } + + fn alias_core_item( + &mut self, + aliases: &mut AliasSection, + instance: u32, + kind: ExportKind, + name: &str, + ) -> u32 { + aliases.instance_export(instance, kind, name); + match kind { + ExportKind::Func => self.indexes.alloc_core_func(), + ExportKind::Table => self.indexes.alloc_core_table(), + ExportKind::Memory => self.indexes.alloc_core_memory(), + ExportKind::Global | ExportKind::Tag => unreachable!(), + } + } + + fn alias_func( + &mut self, + aliases: &mut ComponentAliasSection, + instance: u32, + name: &str, + ) -> u32 { + aliases.instance_export(instance, ComponentExportKind::Func, name); + self.indexes.alloc_func() + } + + fn lower_func( + &mut self, + functions: &mut CanonicalFunctionSection, + func_index: u32, + options: O, + ) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + functions.lower(func_index, options); + self.indexes.alloc_core_func() + } + + fn lift_func( + &mut self, + functions: &mut CanonicalFunctionSection, + core_func_index: u32, + type_index: u32, + options: O, + ) -> u32 + where + O: IntoIterator, + O::IntoIter: ExactSizeIterator, + { + functions.lift(core_func_index, type_index, options); + self.indexes.alloc_func() + } + + fn instantiate_core_exports<'a, E>( + &mut self, + instances: &mut InstanceSection, + exports: E, + ) -> u32 + where + E: IntoIterator, + E::IntoIter: ExactSizeIterator, + { + instances.export_items(exports); + self.indexes.alloc_core_instance() + } + + fn instantiate_exports<'a, E>( + &mut self, + instances: &mut ComponentInstanceSection, + exports: E, + ) -> u32 + where + E: IntoIterator, + E::IntoIter: ExactSizeIterator, + { + instances.export_items(exports); + self.indexes.alloc_instance() + } +} + +#[derive(Debug)] +struct DirectLowering<'a> { + name: &'a str, +} + +#[derive(Debug)] +struct IndirectLowering<'a> { + name: &'a str, + sig: WasmSignature, + options: RequiredOptions, + export_name: String, +} + +#[derive(Debug)] +struct ImportedInterface<'a> { + ty: ComponentTypeRef, + direct: Vec>, + indirect: Vec>, +} + +/// The import encoder handles indirect lowering of any imports +/// that require canonical options to be specified. +/// +/// Lowering of such imports is done through a shim module that +/// defines a table of functions and exports functions that indirectly +/// call through the table. +/// +/// Another module is responsible for "fixing-up" the table of functions +/// once the functions have been lowered, after the core module is instantiated. +/// +/// If a lowering does not require canonical options, the import is lowered before +/// the core module is instantiated and passed directly as an instantiation argument. +#[derive(Debug, Default)] +struct ImportEncoder<'a> { + map: IndexMap<&'a str, ImportedInterface<'a>>, + direct_count: u32, + indirect_count: u32, +} + +impl<'a> ImportEncoder<'a> { + fn import(&mut self, interface: &'a Interface, ty: ComponentTypeRef) -> Result<()> { + match self.map.entry(&interface.name) { + indexmap::map::Entry::Occupied(e) => { + if e.get().ty != ty { + bail!("duplicate import `{}`", interface.name) + } + } + indexmap::map::Entry::Vacant(e) => { + let mut direct = Vec::new(); + let mut indirect = Vec::new(); + for f in &interface.functions { + let sig = interface.wasm_signature(AbiVariant::GuestImport, f); + let options = RequiredOptions::for_function(interface, f) + | (if sig.retptr || sig.indirect_params { + RequiredOptions::Memory + } else { + RequiredOptions::None + }); + + match options { + RequiredOptions::All + | RequiredOptions::Realloc + | RequiredOptions::Memory => { + let element_index = self.indirect_count; + self.indirect_count += 1; + indirect.push(IndirectLowering { + name: &f.name, + sig, + options, + export_name: element_index.to_string(), + }); + } + RequiredOptions::None => { + self.direct_count += 1; + direct.push(DirectLowering { name: &f.name }); + } + } + } + + e.insert(ImportedInterface { + ty, + direct, + indirect, + }); + } + } + + Ok(()) + } +} + +#[derive(Default)] +struct Indexes { + // Core index spaces + core_modules: u32, + core_funcs: u32, + core_memories: u32, + core_tables: u32, + core_instances: u32, + + // Component index spaces + funcs: u32, + instances: u32, +} + +impl Indexes { + fn alloc_core_module(&mut self) -> u32 { + let index = self.core_modules; + self.core_modules += 1; + index + } + + fn alloc_core_func(&mut self) -> u32 { + let index = self.core_funcs; + self.core_funcs += 1; + index + } + + fn alloc_core_memory(&mut self) -> u32 { + let index = self.core_memories; + self.core_memories += 1; + index + } + + fn alloc_core_table(&mut self) -> u32 { + let index = self.core_tables; + self.core_tables += 1; + index + } + + fn alloc_core_instance(&mut self) -> u32 { + let index = self.core_instances; + self.core_instances += 1; + index + } + + fn alloc_func(&mut self) -> u32 { + let index = self.funcs; + self.funcs += 1; + index + } + + fn alloc_instance(&mut self) -> u32 { + let index = self.instances; + self.instances += 1; + index + } +} + +/// An encoder of components based on `wit` interface definitions. +#[derive(Default)] +pub struct ComponentEncoder<'a> { + module: &'a [u8], + encoding: StringEncoding, + interface: Option<&'a Interface>, + imports: &'a [Interface], + exports: &'a [Interface], + validate: bool, + types_only: bool, +} + +impl<'a> ComponentEncoder<'a> { + /// Set the core module to encode as a component. + pub fn module(mut self, module: &'a [u8]) -> Self { + self.module = module; + self + } + + /// Set the string encoding expected by the core module. + pub fn encoding(mut self, encoding: StringEncoding) -> Self { + self.encoding = encoding; + self + } + + /// Sets whether or not the encoder will validate its output. + pub fn validate(mut self, validate: bool) -> Self { + self.validate = validate; + self + } + + /// Set the default interface exported by the component. + pub fn interface(mut self, interface: &'a Interface) -> Self { + self.interface = Some(interface); + self + } + + /// Set the interfaces the component imports. + pub fn imports(mut self, imports: &'a [Interface]) -> Self { + self.imports = imports; + self + } + + /// Set the interfaces the component exports. + pub fn exports(mut self, exports: &'a [Interface]) -> Self { + self.exports = exports; + self + } + + /// Encode the component and return the bytes. + pub fn encode(&self) -> Result> { + let (required_imports, has_memory, has_realloc) = if !self.module.is_empty() { + validate_module(self.module, &self.interface, self.imports, self.exports)? + } else { + (Default::default(), false, false) + }; + + let exports = self + .interface + .iter() + .copied() + .map(|i| (i, true)) + .chain(self.exports.iter().map(|i| (i, false))); + + let mut state = EncodingState::default(); + let mut types = TypeEncoder::default(); + let mut imports = ImportEncoder::default(); + types.encode_instance_imports(self.imports, &required_imports, &mut imports)?; + types.encode_func_types(exports.clone(), false)?; + types.finish(&mut state.component); + + if self.types_only { + if !self.module.is_empty() { + bail!("a module cannot be specified for a types-only encoding"); + } + } else { + if self.module.is_empty() { + bail!("a module is required when encoding a component"); + } + + state.encode_imports(&imports); + state.encode_core_module(self.module); + state.encode_core_instantiation(self.encoding, &imports, has_memory, has_realloc)?; + state.encode_exports(self.encoding, exports, &types.func_type_map)?; + } + + let bytes = state.component.finish(); + + if self.validate { + let mut validator = Validator::new_with_features(WasmFeatures { + component_model: true, + ..Default::default() + }); + + validator + .validate_all(&bytes) + .context("failed to validate component output")?; + } + + Ok(bytes) + } +} + +/// An encoder for a single interface definition. +/// +/// The resulting component will only encode the type information +/// of the interface. +pub struct InterfaceEncoder<'a> { + interface: &'a Interface, + validate: bool, +} + +impl<'a> InterfaceEncoder<'a> { + /// Create a new encoder for the given interface. + pub fn new(interface: &'a Interface) -> Self { + Self { + interface, + validate: false, + } + } + + /// Sets whether or not the encoder will validate its output. + pub fn validate(mut self, validate: bool) -> Self { + self.validate = validate; + self + } + + /// Encode the interface as a component and return the bytes. + pub fn encode(&self) -> Result> { + let mut component = Component::default(); + + let mut types = TypeEncoder::default(); + types.encode_func_types([(self.interface, true)].into_iter(), true)?; + types.finish(&mut component); + + let bytes = component.finish(); + + if self.validate { + let mut validator = Validator::new_with_features(WasmFeatures { + component_model: true, + ..Default::default() + }); + + validator + .validate_all(&bytes) + .context("failed to validate component output")?; + } + + Ok(bytes) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/lib.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/lib.rs new file mode 100644 index 0000000..2036fab --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/lib.rs @@ -0,0 +1,63 @@ +//! The WebAssembly component tooling. + +#![deny(missing_docs)] + +use anyhow::{bail, Result}; +use std::str::FromStr; +use wasm_encoder::CanonicalOption; +use wit_parser::Interface; + +#[cfg(feature = "cli")] +pub mod cli; +mod decoding; +mod encoding; +mod printing; +mod validation; + +pub use encoding::*; +pub use printing::*; + +/// Supported string encoding formats. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum StringEncoding { + /// Strings are encoded with UTF-8. + UTF8, + /// Strings are encoded with UTF-16. + UTF16, + /// Strings are encoded with compact UTF-16 (i.e. Latin1+UTF-16). + CompactUTF16, +} + +impl Default for StringEncoding { + fn default() -> Self { + StringEncoding::UTF8 + } +} + +impl FromStr for StringEncoding { + type Err = anyhow::Error; + + fn from_str(s: &str) -> Result { + match s { + "utf8" => Ok(StringEncoding::UTF8), + "utf16" => Ok(StringEncoding::UTF16), + "compact-utf16" => Ok(StringEncoding::CompactUTF16), + _ => bail!("unknown string encoding `{}`", s), + } + } +} + +impl From for wasm_encoder::CanonicalOption { + fn from(e: StringEncoding) -> wasm_encoder::CanonicalOption { + match e { + StringEncoding::UTF8 => CanonicalOption::UTF8, + StringEncoding::UTF16 => CanonicalOption::UTF16, + StringEncoding::CompactUTF16 => CanonicalOption::CompactUTF16, + } + } +} + +/// Decode an "interface-only" component to a wit `Interface`. +pub fn decode_interface_component(bytes: &[u8]) -> Result { + decoding::InterfaceDecoder::new(&decoding::ComponentInfo::new(bytes)?).decode() +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/printing.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/printing.rs new file mode 100644 index 0000000..ec98d19 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/printing.rs @@ -0,0 +1,381 @@ +use anyhow::{bail, Result}; +use indexmap::IndexSet; +use std::fmt::Write; +use wit_parser::{ + Enum, Expected, Flags, Interface, Record, Tuple, Type, TypeDefKind, TypeId, Union, Variant, +}; + +/// A utility for printing WebAssembly interface definitions to a string. +#[derive(Default)] +pub struct InterfacePrinter { + output: String, + declared: IndexSet, +} + +impl InterfacePrinter { + /// Print the given WebAssembly interface to a string. + pub fn print(&mut self, interface: &Interface) -> Result { + for func in &interface.functions { + for ty in func.params.iter().map(|p| &p.1).chain([&func.result]) { + self.declare_type(interface, ty)?; + } + } + + for func in &interface.functions { + write!(&mut self.output, "{}: func(", func.name)?; + for (i, (name, ty)) in func.params.iter().enumerate() { + if i > 0 { + self.output.push_str(", "); + } + write!(&mut self.output, "{}: ", name)?; + self.print_type_name(interface, ty)?; + } + self.output.push(')'); + + match &func.result { + Type::Unit => {} + other => { + self.output.push_str(" -> "); + self.print_type_name(interface, other)?; + } + } + self.output.push_str("\n\n"); + } + + self.declared.clear(); + Ok(std::mem::take(&mut self.output)) + } + + fn print_type_name(&mut self, interface: &Interface, ty: &Type) -> Result<()> { + match ty { + Type::Unit => self.output.push_str("unit"), + Type::Bool => self.output.push_str("bool"), + Type::U8 => self.output.push_str("u8"), + Type::U16 => self.output.push_str("u16"), + Type::U32 => self.output.push_str("u32"), + Type::U64 => self.output.push_str("u64"), + Type::S8 => self.output.push_str("s8"), + Type::S16 => self.output.push_str("s16"), + Type::S32 => self.output.push_str("s32"), + Type::S64 => self.output.push_str("s64"), + Type::Float32 => self.output.push_str("float32"), + Type::Float64 => self.output.push_str("float64"), + Type::Char => self.output.push_str("char"), + Type::String => self.output.push_str("string"), + + Type::Id(id) => { + let ty = &interface.types[*id]; + if let Some(name) = &ty.name { + self.output.push_str(name); + return Ok(()); + } + + match &ty.kind { + TypeDefKind::Tuple(t) => { + self.print_tuple_type(interface, t)?; + } + TypeDefKind::Option(t) => { + self.print_option_type(interface, t)?; + } + TypeDefKind::Expected(t) => { + self.print_expected_type(interface, t)?; + } + TypeDefKind::Record(_) => { + bail!("interface has an unnamed record type"); + } + TypeDefKind::Flags(_) => { + bail!("interface has unnamed flags type") + } + TypeDefKind::Enum(_) => { + bail!("interface has unnamed enum type") + } + TypeDefKind::Variant(_) => { + bail!("interface has unnamed variant type") + } + TypeDefKind::Union(_) => { + bail!("interface has unnamed union type") + } + TypeDefKind::List(ty) => { + self.output.push_str("list<"); + self.print_type_name(interface, ty)?; + self.output.push('>'); + } + TypeDefKind::Type(ty) => self.print_type_name(interface, ty)?, + TypeDefKind::Future(_) => { + todo!("interface has an unnamed future type") + } + TypeDefKind::Stream(_) => { + todo!("interface has an unnamed stream type") + } + } + } + + Type::Handle(_) => bail!("interface has unsupported type"), + } + + Ok(()) + } + + fn print_tuple_type(&mut self, interface: &Interface, tuple: &Tuple) -> Result<()> { + self.output.push_str("tuple<"); + for (i, ty) in tuple.types.iter().enumerate() { + if i > 0 { + self.output.push_str(", "); + } + self.print_type_name(interface, ty)?; + } + self.output.push('>'); + + Ok(()) + } + + fn print_option_type(&mut self, interface: &Interface, payload: &Type) -> Result<()> { + self.output.push_str("option<"); + self.print_type_name(interface, payload)?; + self.output.push('>'); + Ok(()) + } + + fn print_expected_type(&mut self, interface: &Interface, expected: &Expected) -> Result<()> { + self.output.push_str("expected<"); + self.print_type_name(interface, &expected.ok)?; + self.output.push_str(", "); + self.print_type_name(interface, &expected.err)?; + self.output.push('>'); + Ok(()) + } + + fn declare_type(&mut self, interface: &Interface, ty: &Type) -> Result<()> { + match ty { + Type::Unit + | Type::Bool + | Type::U8 + | Type::U16 + | Type::U32 + | Type::U64 + | Type::S8 + | Type::S16 + | Type::S32 + | Type::S64 + | Type::Float32 + | Type::Float64 + | Type::Char + | Type::String => return Ok(()), + + Type::Id(id) => { + if !self.declared.insert(*id) { + return Ok(()); + } + + let ty = &interface.types[*id]; + match &ty.kind { + TypeDefKind::Record(r) => { + self.declare_record(interface, ty.name.as_deref(), r)? + } + TypeDefKind::Tuple(t) => { + self.declare_tuple(interface, ty.name.as_deref(), t)? + } + TypeDefKind::Flags(f) => self.declare_flags(ty.name.as_deref(), f)?, + TypeDefKind::Variant(v) => { + self.declare_variant(interface, ty.name.as_deref(), v)? + } + TypeDefKind::Union(u) => { + self.declare_union(interface, ty.name.as_deref(), u)? + } + TypeDefKind::Option(t) => { + self.declare_option(interface, ty.name.as_deref(), t)? + } + TypeDefKind::Expected(e) => { + self.declare_expected(interface, ty.name.as_deref(), e)? + } + TypeDefKind::Enum(e) => self.declare_enum(ty.name.as_deref(), e)?, + TypeDefKind::List(inner) => { + self.declare_list(interface, ty.name.as_deref(), inner)? + } + TypeDefKind::Type(inner) => match ty.name.as_deref() { + Some(name) => { + write!(&mut self.output, "type {} = ", name)?; + self.print_type_name(interface, inner)?; + self.output.push_str("\n\n"); + } + None => bail!("unnamed type in interface"), + }, + TypeDefKind::Future(_) => todo!("declare future"), + TypeDefKind::Stream(_) => todo!("declare stream"), + } + } + + Type::Handle(_) => bail!("interface has unsupported type"), + } + Ok(()) + } + + fn declare_record( + &mut self, + interface: &Interface, + name: Option<&str>, + record: &Record, + ) -> Result<()> { + for field in record.fields.iter() { + self.declare_type(interface, &field.ty)?; + } + + match name { + Some(name) => { + writeln!(&mut self.output, "record {} {{", name)?; + for field in &record.fields { + write!(&mut self.output, " {}: ", field.name)?; + self.declare_type(interface, &field.ty)?; + self.print_type_name(interface, &field.ty)?; + self.output.push_str(",\n"); + } + self.output.push_str("}\n\n"); + Ok(()) + } + None => bail!("interface has unnamed record type"), + } + } + + fn declare_tuple( + &mut self, + interface: &Interface, + name: Option<&str>, + tuple: &Tuple, + ) -> Result<()> { + for ty in tuple.types.iter() { + self.declare_type(interface, ty)?; + } + + if let Some(name) = name { + write!(&mut self.output, "type {} = ", name)?; + self.print_tuple_type(interface, tuple)?; + self.output.push_str("\n\n"); + } + Ok(()) + } + + fn declare_flags(&mut self, name: Option<&str>, flags: &Flags) -> Result<()> { + match name { + Some(name) => { + writeln!(&mut self.output, "flags {} {{", name)?; + for flag in &flags.flags { + writeln!(&mut self.output, " {},", flag.name)?; + } + self.output.push_str("}\n\n"); + } + None => bail!("interface has unnamed flags type"), + } + Ok(()) + } + + fn declare_variant( + &mut self, + interface: &Interface, + name: Option<&str>, + variant: &Variant, + ) -> Result<()> { + for case in variant.cases.iter() { + self.declare_type(interface, &case.ty)?; + } + + let name = match name { + Some(name) => name, + None => bail!("interface has unnamed union type"), + }; + writeln!(&mut self.output, "variant {} {{", name)?; + for case in &variant.cases { + write!(&mut self.output, " {}", case.name)?; + if case.ty != Type::Unit { + self.output.push('('); + self.print_type_name(interface, &case.ty)?; + self.output.push(')'); + } + self.output.push_str(",\n"); + } + self.output.push_str("}\n\n"); + Ok(()) + } + + fn declare_union( + &mut self, + interface: &Interface, + name: Option<&str>, + union: &Union, + ) -> Result<()> { + for case in union.cases.iter() { + self.declare_type(interface, &case.ty)?; + } + + let name = match name { + Some(name) => name, + None => bail!("interface has unnamed union type"), + }; + writeln!(&mut self.output, "union {} {{", name)?; + for case in &union.cases { + self.output.push_str(" "); + self.print_type_name(interface, &case.ty)?; + self.output.push_str(",\n"); + } + self.output.push_str("}\n\n"); + Ok(()) + } + + fn declare_option( + &mut self, + interface: &Interface, + name: Option<&str>, + payload: &Type, + ) -> Result<()> { + self.declare_type(interface, payload)?; + + if let Some(name) = name { + write!(&mut self.output, "type {} = ", name)?; + self.print_option_type(interface, payload)?; + self.output.push_str("\n\n"); + } + Ok(()) + } + + fn declare_expected( + &mut self, + interface: &Interface, + name: Option<&str>, + expected: &Expected, + ) -> Result<()> { + self.declare_type(interface, &expected.ok)?; + self.declare_type(interface, &expected.err)?; + + if let Some(name) = name { + write!(&mut self.output, "type {} = ", name)?; + self.print_expected_type(interface, expected)?; + self.output.push_str("\n\n"); + } + Ok(()) + } + + fn declare_enum(&mut self, name: Option<&str>, enum_: &Enum) -> Result<()> { + let name = match name { + Some(name) => name, + None => bail!("interface has unnamed enum type"), + }; + writeln!(&mut self.output, "enum {} {{", name)?; + for case in &enum_.cases { + writeln!(&mut self.output, " {},", case.name)?; + } + self.output.push_str("}\n\n"); + Ok(()) + } + + fn declare_list(&mut self, interface: &Interface, name: Option<&str>, ty: &Type) -> Result<()> { + self.declare_type(interface, ty)?; + + if let Some(name) = name { + write!(&mut self.output, "type {} = list<", name)?; + self.print_type_name(interface, ty)?; + self.output.push_str(">\n\n"); + return Ok(()); + } + + Ok(()) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/validation.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/validation.rs new file mode 100644 index 0000000..acd4f7c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/src/validation.rs @@ -0,0 +1,254 @@ +use anyhow::{anyhow, bail, Result}; +use indexmap::{map::Entry, IndexMap, IndexSet}; +use std::borrow::Cow; +use wasmparser::{ + types::Types, Encoding, ExternalKind, FuncType, Parser, Payload, TypeRef, ValType, + ValidPayload, Validator, +}; +use wit_parser::{ + abi::{AbiVariant, WasmSignature, WasmType}, + Interface, +}; + +fn is_wasi(name: &str) -> bool { + name == "wasi_unstable" || name == "wasi_snapshot_preview1" +} + +fn is_canonical_function(name: &str) -> bool { + name.starts_with("canonical_abi_") +} + +pub fn expected_export_name<'a>(interface: Option<&str>, func: &'a str) -> Cow<'a, str> { + // TODO: wit-bindgen currently doesn't mangle its export names, so this + // only works with the default (i.e. `None`) interface. + match interface { + Some(interface) => format!("{}#{}", interface, func).into(), + None => func.into(), + } +} + +fn wasm_sig_to_func_type(signature: WasmSignature) -> FuncType { + fn from_wasm_type(ty: &WasmType) -> ValType { + match ty { + WasmType::I32 => ValType::I32, + WasmType::I64 => ValType::I64, + WasmType::F32 => ValType::F32, + WasmType::F64 => ValType::F64, + } + } + + FuncType { + params: signature + .params + .iter() + .map(from_wasm_type) + .collect::>() + .into_boxed_slice(), + returns: signature + .results + .iter() + .map(from_wasm_type) + .collect::>() + .into_boxed_slice(), + } +} + +/// This function validates the following: +/// * The bytes represent a core WebAssembly module. +/// * The module's imports are all satisfied by the given import interfaces. +/// * The given default and exported interfaces are satisfied by the module's exports. +/// +/// Returns a tuple of the set of imported interfaces required by the module, whether +/// the module exports a memory, and whether the module exports a realloc function. +pub fn validate_module<'a>( + bytes: &'a [u8], + interface: &Option<&Interface>, + imports: &[Interface], + exports: &[Interface], +) -> Result<(IndexSet<&'a str>, bool, bool)> { + let imports: IndexMap<&str, &Interface> = + imports.iter().map(|i| (i.name.as_str(), i)).collect(); + let exports: IndexMap<&str, &Interface> = + exports.iter().map(|i| (i.name.as_str(), i)).collect(); + + let mut validator = Validator::new(); + let mut types = None; + let mut import_funcs = IndexMap::new(); + let mut export_funcs = IndexMap::new(); + let mut has_memory = false; + let mut has_realloc = false; + + for payload in Parser::new(0).parse_all(bytes) { + let payload = payload?; + if let ValidPayload::End(tys) = validator.payload(&payload)? { + types = Some(tys); + break; + } + + match payload { + Payload::Version { encoding, .. } if encoding != Encoding::Module => { + bail!("data is not a WebAssembly module"); + } + Payload::ImportSection(s) => { + for import in s { + let import = import?; + if is_wasi(import.module) { + continue; + } + match import.ty { + TypeRef::Func(ty) => { + let map = match import_funcs.entry(import.module) { + Entry::Occupied(e) => e.into_mut(), + Entry::Vacant(e) => e.insert(IndexMap::new()), + }; + + assert!(map.insert(import.name, ty).is_none()); + } + _ => bail!("module is only allowed to import functions"), + } + } + } + Payload::ExportSection(s) => { + for export in s { + let export = export?; + + match export.kind { + ExternalKind::Func => { + if is_canonical_function(export.name) { + if export.name == "canonical_abi_realloc" { + // TODO: validate that the canonical_abi_realloc function is [i32, i32, i32, i32] -> [i32] + has_realloc = true; + } + continue; + } + + assert!(export_funcs.insert(export.name, export.index).is_none()) + } + ExternalKind::Memory => { + if export.name == "memory" { + has_memory = true; + } + } + _ => continue, + } + } + } + _ => continue, + } + } + + let types = types.unwrap(); + + for (name, funcs) in &import_funcs { + if name.is_empty() { + bail!("module imports from an empty module name"); + } + + match imports.get(name) { + Some(interface) => { + validate_imported_interface(interface, name, funcs, &types)?; + } + None => bail!("module requires an import interface named `{}`", name), + } + } + + if let Some(interface) = interface { + validate_exported_interface(interface, None, &export_funcs, &types)?; + } + + for (name, interface) in exports { + if name.is_empty() { + bail!("cannot export an interface with an empty name"); + } + + validate_exported_interface(interface, Some(name), &export_funcs, &types)?; + } + + Ok(( + import_funcs.keys().cloned().collect(), + has_memory, + has_realloc, + )) +} + +fn validate_imported_interface( + interface: &Interface, + name: &str, + imports: &IndexMap<&str, u32>, + types: &Types, +) -> Result<()> { + for (func_name, ty) in imports { + let f = interface + .functions + .iter() + .find(|f| f.name == *func_name) + .ok_or_else(|| { + anyhow!( + "import interface `{}` is missing function `{}` that is required by the module", + name, + func_name, + ) + })?; + + let expected = wasm_sig_to_func_type(interface.wasm_signature(AbiVariant::GuestImport, f)); + let ty = types.func_type_at(*ty).unwrap(); + if ty != &expected { + bail!( + "type mismatch for function `{}` on imported interface `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`", + func_name, + name, + expected.params, + expected.returns, + ty.params, + ty.returns + ); + } + } + + Ok(()) +} + +fn validate_exported_interface( + interface: &Interface, + name: Option<&str>, + exports: &IndexMap<&str, u32>, + types: &Types, +) -> Result<()> { + for f in &interface.functions { + let expected_export = expected_export_name(name, &f.name); + match exports.get(expected_export.as_ref()) { + Some(func_index) => { + let expected_ty = + wasm_sig_to_func_type(interface.wasm_signature(AbiVariant::GuestExport, f)); + let ty = types.function_at(*func_index).unwrap(); + if ty != &expected_ty { + match name { + Some(name) => bail!( + "type mismatch for function `{}` from exported interface `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`", + f.name, + name, + expected_ty.params, + expected_ty.returns, + ty.params, + ty.returns + ), + None => bail!( + "type mismatch for default interface function `{}`: expected `{:?} -> {:?}` but found `{:?} -> {:?}`", + f.name, + expected_ty.params, + expected_ty.returns, + ty.params, + ty.returns + ) + } + } + } + None => bail!( + "module does not export required function `{}`", + expected_export + ), + } + } + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components.rs new file mode 100644 index 0000000..c954293 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components.rs @@ -0,0 +1,123 @@ +use anyhow::{bail, Context, Result}; +use pretty_assertions::assert_eq; +use std::{fs, path::Path}; +use wit_component::ComponentEncoder; +use wit_parser::Interface; + +fn read_interface(path: &Path) -> Result { + wit_parser::Interface::parse_file(&path) + .with_context(|| format!("failed to parse interface file `{}`", path.display())) +} + +fn read_interfaces(dir: &Path, pattern: &str) -> Result> { + glob::glob(dir.join(pattern).to_str().unwrap())? + .map(|p| { + let p = p?; + let mut i = read_interface(&p)?; + i.name = p + .file_stem() + .unwrap() + .to_str() + .unwrap() + .trim_start_matches("import-") + .trim_start_matches("export-") + .to_string(); + Ok(i) + }) + .collect::>() +} + +/// Tests the encoding of components. +/// +/// This test looks in the `components/` directory for test cases. +/// +/// The expected input files for a test case are: +/// +/// * [required] `module.wat` - contains the core module definition to be encoded +/// as a component. +/// * [optional] `default.wit` - represents the component's default interface. +/// * [optional] `export-.wit` - represents an interface exported by the component. +/// * [optional] `import-.wit` - represents an interface imported by the component. +/// +/// And the output files are one of the following: +/// +/// * `component.wat` - the expected encoded component in text format if the encoding +/// is expected to succeed. +/// * `error.txt` - the expected error message if the encoding is expected to fail. +/// +/// The test encodes a component based on the input files. If the encoding succeeds, +/// it expects the output to match `component.wat`. If the encoding fails, it expects +/// the output to match `error.txt`. +/// +/// Run the test with the environment variable `BLESS` set to update +/// either `component.wat` or `error.txt` depending on the outcome of the encoding. +#[test] +fn component_encoding() -> Result<()> { + for entry in fs::read_dir("tests/components")? { + let path = entry?.path(); + if !path.is_dir() { + continue; + } + + let test_case = path.file_stem().unwrap().to_str().unwrap(); + + let module_path = path.join("module.wat"); + let interface_path = path.join("default.wit"); + let component_path = path.join("component.wat"); + let error_path = path.join("error.txt"); + + let module = wat::parse_file(&module_path) + .with_context(|| format!("expected file `{}`", module_path.display()))?; + let interface = interface_path + .is_file() + .then(|| read_interface(&interface_path)) + .transpose()?; + let imports = read_interfaces(&path, "import-*.wit")?; + let exports = read_interfaces(&path, "export-*.wit")?; + + let mut encoder = ComponentEncoder::default() + .module(&module) + .imports(&imports) + .exports(&exports) + .validate(true); + + if let Some(interface) = &interface { + encoder = encoder.interface(interface); + } + + let r = encoder.encode(); + let (output, baseline_path) = if error_path.is_file() { + match r { + Ok(_) => bail!("encoding should fail for test case `{}`", test_case), + Err(e) => (e.to_string(), &error_path), + } + } else { + ( + wasmprinter::print_bytes( + &r.with_context(|| format!("failed to encode for test case `{}`", test_case))?, + ) + .with_context(|| { + format!( + "failed to print component bytes for test case `{}`", + test_case + ) + })?, + &component_path, + ) + }; + + if std::env::var_os("BLESS").is_some() { + fs::write(&baseline_path, output)?; + } else { + assert_eq!( + fs::read_to_string(&baseline_path)?.replace("\r\n", "\n"), + output, + "failed baseline comparison for test case `{}` ({})", + test_case, + baseline_path.display(), + ); + } + } + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/default.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/default.wit new file mode 100644 index 0000000..acfdb10 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/default.wit @@ -0,0 +1 @@ +a: func(x: string) -> string \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt new file mode 100644 index 0000000..fdb207a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/error.txt @@ -0,0 +1 @@ +type mismatch for default interface function `a`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat new file mode 100644 index 0000000..420a370 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/default-export-sig-mismatch/module.wat @@ -0,0 +1,3 @@ +(module + (func (export "a") unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty-module-import/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty-module-import/error.txt new file mode 100644 index 0000000..fd0bd9f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty-module-import/error.txt @@ -0,0 +1 @@ +module imports from an empty module name \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty-module-import/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty-module-import/module.wat new file mode 100644 index 0000000..af6dd6c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty-module-import/module.wat @@ -0,0 +1,3 @@ +(module + (import "" "foo" (func)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty/component.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty/component.wat new file mode 100644 index 0000000..3f194a2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty/component.wat @@ -0,0 +1,4 @@ +(component + (core module (;0;)) + (core instance (;0;) (instantiate 0)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty/module.wat new file mode 100644 index 0000000..800bef5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/empty/module.wat @@ -0,0 +1 @@ +(module) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/error.txt new file mode 100644 index 0000000..724247f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/error.txt @@ -0,0 +1 @@ +type mismatch for function `a` from exported interface `foo`: expected `[I32, I32] -> [I32]` but found `[] -> []` \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/export-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/export-foo.wit new file mode 100644 index 0000000..acfdb10 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/export-foo.wit @@ -0,0 +1 @@ +a: func(x: string) -> string \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/module.wat new file mode 100644 index 0000000..8a8e12b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/export-sig-mismatch/module.wat @@ -0,0 +1,3 @@ +(module + (func (export "foo#a") unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/component.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/component.wat new file mode 100644 index 0000000..75c61b4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/component.wat @@ -0,0 +1,84 @@ +(component + (type (;0;) (func)) + (type (;1;) (func (param "a" s8) (param "b" s16) (param "c" s32) (param "d" s64) (result string))) + (type (;2;) (tuple s8 s16 s32 s64)) + (type (;3;) (func (result 2))) + (type (;4;) (flags "a" "b" "c")) + (type (;5;) (func (param "x" 4))) + (type (;6;) (variant (case $c0 "a" unit) (case $c1 "b" string) (case $c2 "c" s64))) + (type (;7;) (func (param "x" string) (result 6))) + (type (;8;) (func (param "x" 6) (result string))) + (core module (;0;) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (type (;1;) (func)) + (type (;2;) (func (param i32 i32 i32 i64) (result i32))) + (type (;3;) (func (result i32))) + (type (;4;) (func (param i32 i32) (result i32))) + (type (;5;) (func (param i32 i64 i32) (result i32))) + (type (;6;) (func (param i32))) + (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;1;) (type 1) + unreachable + ) + (func (;2;) (type 2) (param i32 i32 i32 i64) (result i32) + unreachable + ) + (func (;3;) (type 3) (result i32) + unreachable + ) + (func (;4;) (type 1) + unreachable + ) + (func (;5;) (type 4) (param i32 i32) (result i32) + unreachable + ) + (func (;6;) (type 5) (param i32 i64 i32) (result i32) + unreachable + ) + (func (;7;) (type 6) (param i32) + unreachable + ) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "canonical_abi_realloc" (func 0)) + (export "a" (func 1)) + (export "b" (func 2)) + (export "c" (func 3)) + (export "foo#a" (func 4)) + (export "foo#b" (func 5)) + (export "foo#c" (func 6)) + (export "bar#a" (func 7)) + ) + (core instance (;0;) (instantiate 0)) + (core alias export 0 "memory" (memory (;0;))) + (core alias export 0 "canonical_abi_realloc" (func (;0;))) + (core alias export 0 "a" (func (;1;))) + (core alias export 0 "b" (func (;2;))) + (core alias export 0 "c" (func (;3;))) + (func (;0;) (type 0) (canon lift (core func 1))) + (func (;1;) (type 1) (canon lift (core func 2) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;2;) (type 3) (canon lift (core func 3) (memory 0))) + (core alias export 0 "bar#a" (func (;4;))) + (func (;3;) (type 5) (canon lift (core func 4))) + (core alias export 0 "foo#a" (func (;5;))) + (core alias export 0 "foo#b" (func (;6;))) + (core alias export 0 "foo#c" (func (;7;))) + (func (;4;) (type 0) (canon lift (core func 5))) + (func (;5;) (type 7) (canon lift (core func 6) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;6;) (type 8) (canon lift (core func 7) (memory 0) (realloc 0) string-encoding=utf8)) + (instance (;0;) + (export "a" (func 3)) + ) + (instance (;1;) + (export "a" (func 4)) + (export "b" (func 5)) + (export "c" (func 6)) + ) + (export "a" (func 0)) + (export "b" (func 1)) + (export "c" (func 2)) + (export "bar" (instance 0)) + (export "foo" (instance 1)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/default.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/default.wit new file mode 100644 index 0000000..2a121df --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/default.wit @@ -0,0 +1,3 @@ +a: func() +b: func(a: s8, b: s16, c: s32, d: s64) -> string +c: func() -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/export-bar.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/export-bar.wit new file mode 100644 index 0000000..c8c694d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/export-bar.wit @@ -0,0 +1,7 @@ +flags x { + a, + b, + c +} + +a: func(x: x) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/export-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/export-foo.wit new file mode 100644 index 0000000..d84336d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/export-foo.wit @@ -0,0 +1,9 @@ +variant x { + a, + b(string), + c(s64) +} + +a: func() +b: func(x: string) -> x +c: func(x: x) -> string diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/module.wat new file mode 100644 index 0000000..e01316a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/exports/module.wat @@ -0,0 +1,11 @@ +(module + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "a") unreachable) + (func (export "b") (param i32 i32 i32 i64) (result i32) unreachable) + (func (export "c") (result i32) unreachable) + (func (export "foo#a") unreachable) + (func (export "foo#b") (param i32 i32) (result i32) unreachable) + (func (export "foo#c") (param i32 i64 i32) (result i32) unreachable) + (func (export "bar#a") (param i32) unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/component.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/component.wat new file mode 100644 index 0000000..b7d14af --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/component.wat @@ -0,0 +1,108 @@ +(component + (type (;0;) (func (param "x" u64) (param "y" string))) + (type (;1;) + (instance + (alias outer 1 0 (type (;0;))) + (export "a" (func (type 0))) + ) + ) + (type (;2;) (list u8)) + (type (;3;) (func (param "x" 2) (result 2))) + (type (;4;) + (instance + (alias outer 1 3 (type (;0;))) + (export "baz" (func (type 0))) + ) + ) + (type (;5;) (func)) + (type (;6;) + (instance + (alias outer 1 5 (type (;0;))) + (export "a" (func (type 0))) + ) + ) + (import "bar" (instance (;0;) (type 1))) + (import "baz" (instance (;1;) (type 4))) + (import "foo" (instance (;2;) (type 6))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param i64 i32 i32))) + (type (;2;) (func (param i32 i32 i32))) + (type (;3;) (func (param i32 i32 i32 i32) (result i32))) + (import "foo" "a" (func (;0;) (type 0))) + (import "bar" "a" (func (;1;) (type 1))) + (import "baz" "baz" (func (;2;) (type 2))) + (func (;3;) (type 3) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "canonical_abi_realloc" (func 3)) + ) + (core module (;1;) + (type (;0;) (func (param i64 i32 i32))) + (type (;1;) (func (param i32 i32 i32))) + (func (;0;) (type 0) (param i64 i32 i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 0 + call_indirect (type 0) + ) + (func (;1;) (type 1) (param i32 i32 i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 1 + call_indirect (type 1) + ) + (table (;0;) 2 2 funcref) + (export "0" (func 0)) + (export "1" (func 1)) + (export "$imports" (table 0)) + ) + (core module (;2;) + (type (;0;) (func (param i64 i32 i32))) + (type (;1;) (func (param i32 i32 i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "1" (func (;1;) (type 1))) + (import "" "$imports" (table (;0;) 2 2 funcref)) + (elem (;0;) (i32.const 0) func 0 1) + ) + (core instance (;0;) (instantiate 1)) + (core alias export 0 "0" (func (;0;))) + (core alias export 0 "1" (func (;1;))) + (alias export 2 "a" (func (;0;))) + (core func (;2;) (canon lower (func 0))) + (core instance (;1;) + (export "a" (func 0)) + ) + (core instance (;2;) + (export "baz" (func 1)) + ) + (core instance (;3;) + (export "a" (func 2)) + ) + (core instance (;4;) (instantiate 0 + (with "bar" (instance 1)) + (with "baz" (instance 2)) + (with "foo" (instance 3)) + ) + ) + (core alias export 4 "memory" (memory (;0;))) + (core alias export 4 "canonical_abi_realloc" (func (;3;))) + (core alias export 0 "$imports" (table (;0;))) + (alias export 0 "a" (func (;1;))) + (alias export 1 "baz" (func (;2;))) + (core func (;4;) (canon lower (func 1) (memory 0) (realloc 3) string-encoding=utf8)) + (core func (;5;) (canon lower (func 2) (memory 0) (realloc 3))) + (core instance (;5;) + (export "$imports" (table 0)) + (export "0" (func 4)) + (export "1" (func 5)) + ) + (core instance (;6;) (instantiate 2 + (with "" (instance 5)) + ) + ) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-bar.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-bar.wit new file mode 100644 index 0000000..574a302 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-bar.wit @@ -0,0 +1 @@ +a: func(x: u64, y: string) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-baz.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-baz.wit new file mode 100644 index 0000000..985d176 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-baz.wit @@ -0,0 +1 @@ +baz: func(x: list) -> list diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-foo.wit new file mode 100644 index 0000000..b7a21e1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/import-foo.wit @@ -0,0 +1 @@ +a: func() \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/module.wat new file mode 100644 index 0000000..f58dc5d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-conflict/module.wat @@ -0,0 +1,7 @@ +(module + (import "foo" "a" (func)) + (import "bar" "a" (func (param i64 i32 i32))) + (import "baz" "baz" (func (param i32 i32 i32))) + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/component.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/component.wat new file mode 100644 index 0000000..be8204b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/component.wat @@ -0,0 +1,90 @@ +(component + (type (;0;) (func (result string))) + (type (;1;) + (instance + (alias outer 1 0 (type (;0;))) + (export "a" (func (type 0))) + ) + ) + (type (;2;) (tuple string u32 string)) + (type (;3;) (func (param "x" string) (result 2))) + (type (;4;) (func)) + (import "foo" (instance (;0;) (type 1))) + (core module (;0;) + (type (;0;) (func (param i32))) + (type (;1;) (func (param i32 i32 i32 i32) (result i32))) + (type (;2;) (func (param i32 i32) (result i32))) + (type (;3;) (func)) + (type (;4;) (func (result i32))) + (import "foo" "a" (func (;0;) (type 0))) + (func (;1;) (type 1) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;2;) (type 2) (param i32 i32) (result i32) + unreachable + ) + (func (;3;) (type 3) + unreachable + ) + (func (;4;) (type 4) (result i32) + unreachable + ) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "canonical_abi_realloc" (func 1)) + (export "a" (func 2)) + (export "bar#a" (func 3)) + (export "bar#b" (func 4)) + ) + (core module (;1;) + (type (;0;) (func (param i32))) + (func (;0;) (type 0) (param i32) + local.get 0 + i32.const 0 + call_indirect (type 0) + ) + (table (;0;) 1 1 funcref) + (export "0" (func 0)) + (export "$imports" (table 0)) + ) + (core module (;2;) + (type (;0;) (func (param i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "$imports" (table (;0;) 1 1 funcref)) + (elem (;0;) (i32.const 0) func 0) + ) + (core instance (;0;) (instantiate 1)) + (core alias export 0 "0" (func (;0;))) + (core instance (;1;) + (export "a" (func 0)) + ) + (core instance (;2;) (instantiate 0 + (with "foo" (instance 1)) + ) + ) + (core alias export 2 "memory" (memory (;0;))) + (core alias export 2 "canonical_abi_realloc" (func (;1;))) + (core alias export 0 "$imports" (table (;0;))) + (alias export 0 "a" (func (;0;))) + (core func (;2;) (canon lower (func 0) (memory 0) (realloc 1) string-encoding=utf8)) + (core instance (;3;) + (export "$imports" (table 0)) + (export "0" (func 2)) + ) + (core instance (;4;) (instantiate 2 + (with "" (instance 3)) + ) + ) + (core alias export 2 "a" (func (;3;))) + (func (;1;) (type 3) (canon lift (core func 3) (memory 0) (realloc 1) string-encoding=utf8)) + (core alias export 2 "bar#a" (func (;4;))) + (core alias export 2 "bar#b" (func (;5;))) + (func (;2;) (type 4) (canon lift (core func 4))) + (func (;3;) (type 0) (canon lift (core func 5) (memory 0) (realloc 1) string-encoding=utf8)) + (instance (;1;) + (export "a" (func 2)) + (export "b" (func 3)) + ) + (export "a" (func 1)) + (export "bar" (instance 1)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/default.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/default.wit new file mode 100644 index 0000000..b239378 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/default.wit @@ -0,0 +1 @@ +a: func(x: string) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/export-bar.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/export-bar.wit new file mode 100644 index 0000000..cb31994 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/export-bar.wit @@ -0,0 +1,2 @@ +a: func() +b: func() -> string diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/import-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/import-foo.wit new file mode 100644 index 0000000..f159259 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/import-foo.wit @@ -0,0 +1 @@ +a: func() -> string diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/module.wat new file mode 100644 index 0000000..1d7c399 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-export/module.wat @@ -0,0 +1,8 @@ +(module + (import "foo" "a" (func (param i32))) + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "a") (param i32 i32) (result i32) unreachable) + (func (export "bar#a") unreachable) + (func (export "bar#b") (result i32) unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/error.txt new file mode 100644 index 0000000..b8d68a1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/error.txt @@ -0,0 +1 @@ +type mismatch for function `bar` on imported interface `foo`: expected `[I32, I32] -> []` but found `[] -> []` \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/import-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/import-foo.wit new file mode 100644 index 0000000..ca78f93 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/import-foo.wit @@ -0,0 +1 @@ +bar: func(s: string) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/module.wat new file mode 100644 index 0000000..96d3790 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/import-sig-mismatch/module.wat @@ -0,0 +1,3 @@ +(module + (import "foo" "bar" (func)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/component.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/component.wat new file mode 100644 index 0000000..fe05fa4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/component.wat @@ -0,0 +1,141 @@ +(component + (type (;0;) (func (param "x" string))) + (type (;1;) (record (field "a" u8))) + (type (;2;) (func (param "x" 1))) + (type (;3;) + (instance + (alias outer 1 0 (type (;0;))) + (export "bar1" (func (type 0))) + (alias outer 1 2 (type (;1;))) + (export "bar2" (func (type 1))) + ) + ) + (type (;4;) (list string)) + (type (;5;) (func (param "x" 4))) + (type (;6;) (func)) + (type (;7;) s8) + (type (;8;) (func (param "x" 7))) + (type (;9;) + (instance + (alias outer 1 5 (type (;0;))) + (export "baz1" (func (type 0))) + (alias outer 1 6 (type (;1;))) + (export "baz2" (func (type 1))) + (alias outer 1 8 (type (;2;))) + (export "baz3" (func (type 2))) + ) + ) + (type (;10;) (func (param "x" u8))) + (type (;11;) (func (param "x" float32))) + (type (;12;) + (instance + (alias outer 1 6 (type (;0;))) + (export "foo1" (func (type 0))) + (alias outer 1 10 (type (;1;))) + (export "foo2" (func (type 1))) + (alias outer 1 11 (type (;2;))) + (export "foo3" (func (type 2))) + ) + ) + (import "bar" (instance (;0;) (type 3))) + (import "baz" (instance (;1;) (type 9))) + (import "foo" (instance (;2;) (type 12))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param i32))) + (type (;2;) (func (param f32))) + (type (;3;) (func (param i32 i32))) + (type (;4;) (func (param i32 i32 i32 i32) (result i32))) + (import "foo" "foo1" (func (;0;) (type 0))) + (import "foo" "foo2" (func (;1;) (type 1))) + (import "foo" "foo3" (func (;2;) (type 2))) + (import "bar" "bar1" (func (;3;) (type 3))) + (import "bar" "bar2" (func (;4;) (type 1))) + (import "baz" "baz1" (func (;5;) (type 3))) + (import "baz" "baz2" (func (;6;) (type 0))) + (import "baz" "baz3" (func (;7;) (type 1))) + (func (;8;) (type 4) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "canonical_abi_realloc" (func 8)) + ) + (core module (;1;) + (type (;0;) (func (param i32 i32))) + (func (;0;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 0 + call_indirect (type 0) + ) + (func (;1;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 1 + call_indirect (type 0) + ) + (table (;0;) 2 2 funcref) + (export "0" (func 0)) + (export "1" (func 1)) + (export "$imports" (table 0)) + ) + (core module (;2;) + (type (;0;) (func (param i32 i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "1" (func (;1;) (type 0))) + (import "" "$imports" (table (;0;) 2 2 funcref)) + (elem (;0;) (i32.const 0) func 0 1) + ) + (core instance (;0;) (instantiate 1)) + (core alias export 0 "0" (func (;0;))) + (alias export 0 "bar2" (func (;0;))) + (core func (;1;) (canon lower (func 0))) + (core alias export 0 "1" (func (;2;))) + (alias export 1 "baz2" (func (;1;))) + (alias export 1 "baz3" (func (;2;))) + (core func (;3;) (canon lower (func 1))) + (core func (;4;) (canon lower (func 2))) + (alias export 2 "foo1" (func (;3;))) + (alias export 2 "foo2" (func (;4;))) + (alias export 2 "foo3" (func (;5;))) + (core func (;5;) (canon lower (func 3))) + (core func (;6;) (canon lower (func 4))) + (core func (;7;) (canon lower (func 5))) + (core instance (;1;) + (export "bar1" (func 0)) + (export "bar2" (func 1)) + ) + (core instance (;2;) + (export "baz1" (func 2)) + (export "baz2" (func 3)) + (export "baz3" (func 4)) + ) + (core instance (;3;) + (export "foo1" (func 5)) + (export "foo2" (func 6)) + (export "foo3" (func 7)) + ) + (core instance (;4;) (instantiate 0 + (with "bar" (instance 1)) + (with "baz" (instance 2)) + (with "foo" (instance 3)) + ) + ) + (core alias export 4 "memory" (memory (;0;))) + (core alias export 4 "canonical_abi_realloc" (func (;8;))) + (core alias export 0 "$imports" (table (;0;))) + (alias export 0 "bar1" (func (;6;))) + (alias export 1 "baz1" (func (;7;))) + (core func (;9;) (canon lower (func 6) (memory 0) (realloc 8) string-encoding=utf8)) + (core func (;10;) (canon lower (func 7) (memory 0) (realloc 8) string-encoding=utf8)) + (core instance (;5;) + (export "$imports" (table 0)) + (export "0" (func 9)) + (export "1" (func 10)) + ) + (core instance (;6;) (instantiate 2 + (with "" (instance 5)) + ) + ) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-bar.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-bar.wit new file mode 100644 index 0000000..10c19d5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-bar.wit @@ -0,0 +1,6 @@ +record x { + a: u8 +} + +bar1: func(x: string) +bar2: func(x: x) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-baz.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-baz.wit new file mode 100644 index 0000000..72fee13 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-baz.wit @@ -0,0 +1,5 @@ +type x = s8 + +baz1: func(x: list) +baz2: func() +baz3: func(x: x) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-foo.wit new file mode 100644 index 0000000..e6104b8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/import-foo.wit @@ -0,0 +1,3 @@ +foo1: func() +foo2: func(x: u8) +foo3: func(x: float32) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/module.wat new file mode 100644 index 0000000..19d3bca --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/imports/module.wat @@ -0,0 +1,12 @@ +(module + (import "foo" "foo1" (func)) + (import "foo" "foo2" (func (param i32))) + (import "foo" "foo3" (func (param f32))) + (import "bar" "bar1" (func (param i32 i32))) + (import "bar" "bar2" (func (param i32))) + (import "baz" "baz1" (func (param i32 i32))) + (import "baz" "baz2" (func)) + (import "baz" "baz3" (func (param i32))) + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/invalid-module-import/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/invalid-module-import/error.txt new file mode 100644 index 0000000..9d9f35c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/invalid-module-import/error.txt @@ -0,0 +1 @@ +module is only allowed to import functions \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/invalid-module-import/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/invalid-module-import/module.wat new file mode 100644 index 0000000..2768b75 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/invalid-module-import/module.wat @@ -0,0 +1,3 @@ +(module + (import "" "" (table 1 funcref)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/component.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/component.wat new file mode 100644 index 0000000..484fe90 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/component.wat @@ -0,0 +1,161 @@ +(component + (type (;0;) (func)) + (type (;1;) (list string)) + (type (;2;) (func (param "x" 1))) + (type (;3;) (record (field "s" string))) + (type (;4;) (func (param "x" 3))) + (type (;5;) (variant (case $c0 "s" string))) + (type (;6;) (func (param "x" 5))) + (type (;7;) (record (field "s" u32))) + (type (;8;) (func (param "x" 7))) + (type (;9;) (variant (case $c0 "s" u32))) + (type (;10;) (func (param "x" 9))) + (type (;11;) (list 3)) + (type (;12;) (func (param "x" 11))) + (type (;13;) (list 5)) + (type (;14;) (func (param "x" 13))) + (type (;15;) (list u32)) + (type (;16;) (func (param "x" 15))) + (type (;17;) (func (param "x" u32))) + (type (;18;) (tuple u32 u32)) + (type (;19;) (func (result 18))) + (type (;20;) (func (result string))) + (type (;21;) (func (result 15))) + (type (;22;) (func (result u32))) + (type (;23;) (func (result 5))) + (type (;24;) (list 9)) + (type (;25;) (func (result 24))) + (export "r" (type 3)) + (export "v" (type 5)) + (export "r-no-string" (type 7)) + (export "v-no-string" (type 9)) + (core module (;0;) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (type (;1;) (func)) + (type (;2;) (func (param i32 i32))) + (type (;3;) (func (param i32 i32 i32))) + (type (;4;) (func (param i32))) + (type (;5;) (func (result i32))) + (func (;0;) (type 0) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func (;1;) (type 1) + unreachable + ) + (func (;2;) (type 2) (param i32 i32) + unreachable + ) + (func (;3;) (type 2) (param i32 i32) + unreachable + ) + (func (;4;) (type 3) (param i32 i32 i32) + unreachable + ) + (func (;5;) (type 4) (param i32) + unreachable + ) + (func (;6;) (type 2) (param i32 i32) + unreachable + ) + (func (;7;) (type 2) (param i32 i32) + unreachable + ) + (func (;8;) (type 2) (param i32 i32) + unreachable + ) + (func (;9;) (type 2) (param i32 i32) + unreachable + ) + (func (;10;) (type 4) (param i32) + unreachable + ) + (func (;11;) (type 5) (result i32) + unreachable + ) + (func (;12;) (type 5) (result i32) + unreachable + ) + (func (;13;) (type 5) (result i32) + unreachable + ) + (func (;14;) (type 5) (result i32) + unreachable + ) + (func (;15;) (type 5) (result i32) + unreachable + ) + (func (;16;) (type 5) (result i32) + unreachable + ) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "canonical_abi_realloc" (func 0)) + (export "a" (func 1)) + (export "b" (func 2)) + (export "c" (func 3)) + (export "d" (func 4)) + (export "e" (func 5)) + (export "f" (func 6)) + (export "g" (func 7)) + (export "h" (func 8)) + (export "i" (func 9)) + (export "j" (func 10)) + (export "k" (func 11)) + (export "l" (func 12)) + (export "m" (func 13)) + (export "n" (func 14)) + (export "o" (func 15)) + (export "p" (func 16)) + ) + (core instance (;0;) (instantiate 0)) + (core alias export 0 "memory" (memory (;0;))) + (core alias export 0 "canonical_abi_realloc" (func (;0;))) + (core alias export 0 "a" (func (;1;))) + (core alias export 0 "b" (func (;2;))) + (core alias export 0 "c" (func (;3;))) + (core alias export 0 "d" (func (;4;))) + (core alias export 0 "e" (func (;5;))) + (core alias export 0 "f" (func (;6;))) + (core alias export 0 "g" (func (;7;))) + (core alias export 0 "h" (func (;8;))) + (core alias export 0 "i" (func (;9;))) + (core alias export 0 "j" (func (;10;))) + (core alias export 0 "k" (func (;11;))) + (core alias export 0 "l" (func (;12;))) + (core alias export 0 "m" (func (;13;))) + (core alias export 0 "n" (func (;14;))) + (core alias export 0 "o" (func (;15;))) + (core alias export 0 "p" (func (;16;))) + (func (;0;) (type 0) (canon lift (core func 1))) + (func (;1;) (type 2) (canon lift (core func 2) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;2;) (type 4) (canon lift (core func 3) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;3;) (type 6) (canon lift (core func 4) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;4;) (type 8) (canon lift (core func 5))) + (func (;5;) (type 10) (canon lift (core func 6))) + (func (;6;) (type 12) (canon lift (core func 7) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;7;) (type 14) (canon lift (core func 8) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;8;) (type 16) (canon lift (core func 9) (memory 0) (realloc 0))) + (func (;9;) (type 17) (canon lift (core func 10))) + (func (;10;) (type 19) (canon lift (core func 11) (memory 0))) + (func (;11;) (type 20) (canon lift (core func 12) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;12;) (type 21) (canon lift (core func 13) (memory 0) (realloc 0))) + (func (;13;) (type 22) (canon lift (core func 14))) + (func (;14;) (type 23) (canon lift (core func 15) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;15;) (type 25) (canon lift (core func 16) (memory 0) (realloc 0))) + (export "a" (func 0)) + (export "b" (func 1)) + (export "c" (func 2)) + (export "d" (func 3)) + (export "e" (func 4)) + (export "f" (func 5)) + (export "g" (func 6)) + (export "h" (func 7)) + (export "i" (func 8)) + (export "j" (func 9)) + (export "k" (func 10)) + (export "l" (func 11)) + (export "m" (func 12)) + (export "n" (func 13)) + (export "o" (func 14)) + (export "p" (func 15)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/default.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/default.wit new file mode 100644 index 0000000..496bf1e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/default.wit @@ -0,0 +1,32 @@ +record r { + s: string +} + +record r-no-string { + s: u32 +} + +variant v { + s(string) +} + +variant v-no-string { + s(u32) +} + +a: func() +b: func(x: list) +c: func(x: r) +d: func(x: v) +e: func(x: r-no-string) +f: func(x: v-no-string) +g: func(x: list) +h: func(x: list) +i: func(x: list) +j: func(x: u32) +k: func() -> tuple +l: func() -> string +m: func() -> list +n: func() -> u32 +o: func() -> v +p: func() -> list diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/module.wat new file mode 100644 index 0000000..77fded3 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lift-options/module.wat @@ -0,0 +1,20 @@ +(module + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func (export "a") unreachable) + (func (export "b") (param i32 i32) unreachable) + (func (export "c") (param i32 i32) unreachable) + (func (export "d") (param i32 i32 i32) unreachable) + (func (export "e") (param i32) unreachable) + (func (export "f") (param i32 i32) unreachable) + (func (export "g") (param i32 i32) unreachable) + (func (export "h") (param i32 i32) unreachable) + (func (export "i") (param i32 i32) unreachable) + (func (export "j") (param i32) unreachable) + (func (export "k") (result i32) unreachable) + (func (export "l") (result i32) unreachable) + (func (export "m") (result i32) unreachable) + (func (export "n") (result i32) unreachable) + (func (export "o") (result i32) unreachable) + (func (export "p") (result i32) unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/component.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/component.wat new file mode 100644 index 0000000..6b8c280 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/component.wat @@ -0,0 +1,280 @@ +(component + (type (;0;) (func)) + (type (;1;) (list string)) + (type (;2;) (func (param "x" 1))) + (type (;3;) (record (field "s" string))) + (type (;4;) (func (param "x" 3))) + (type (;5;) (variant (case $c0 "s" string))) + (type (;6;) (func (param "x" 5))) + (type (;7;) (record (field "s" u32))) + (type (;8;) (func (param "x" 7))) + (type (;9;) (variant (case $c0 "s" u32))) + (type (;10;) (func (param "x" 9))) + (type (;11;) (list 3)) + (type (;12;) (func (param "x" 11))) + (type (;13;) (list 5)) + (type (;14;) (func (param "x" 13))) + (type (;15;) (list u32)) + (type (;16;) (func (param "x" 15))) + (type (;17;) (func (param "x" u32))) + (type (;18;) (tuple u32 u32)) + (type (;19;) (func (result 18))) + (type (;20;) (func (result string))) + (type (;21;) (func (result 15))) + (type (;22;) (func (result u32))) + (type (;23;) (func (result 5))) + (type (;24;) (list 9)) + (type (;25;) (func (result 24))) + (type (;26;) + (instance + (alias outer 1 0 (type (;0;))) + (export "a" (func (type 0))) + (alias outer 1 2 (type (;1;))) + (export "b" (func (type 1))) + (alias outer 1 4 (type (;2;))) + (export "c" (func (type 2))) + (alias outer 1 6 (type (;3;))) + (export "d" (func (type 3))) + (alias outer 1 8 (type (;4;))) + (export "e" (func (type 4))) + (alias outer 1 10 (type (;5;))) + (export "f" (func (type 5))) + (alias outer 1 12 (type (;6;))) + (export "g" (func (type 6))) + (alias outer 1 14 (type (;7;))) + (export "h" (func (type 7))) + (alias outer 1 16 (type (;8;))) + (export "i" (func (type 8))) + (alias outer 1 17 (type (;9;))) + (export "j" (func (type 9))) + (alias outer 1 19 (type (;10;))) + (export "k" (func (type 10))) + (alias outer 1 20 (type (;11;))) + (export "l" (func (type 11))) + (alias outer 1 21 (type (;12;))) + (export "m" (func (type 12))) + (alias outer 1 22 (type (;13;))) + (export "n" (func (type 13))) + (alias outer 1 23 (type (;14;))) + (export "o" (func (type 14))) + (alias outer 1 25 (type (;15;))) + (export "p" (func (type 15))) + ) + ) + (import "foo" (instance (;0;) (type 26))) + (core module (;0;) + (type (;0;) (func)) + (type (;1;) (func (param i32 i32))) + (type (;2;) (func (param i32 i32 i32))) + (type (;3;) (func (param i32))) + (type (;4;) (func (result i32))) + (type (;5;) (func (param i32 i32 i32 i32) (result i32))) + (import "foo" "a" (func (;0;) (type 0))) + (import "foo" "b" (func (;1;) (type 1))) + (import "foo" "c" (func (;2;) (type 1))) + (import "foo" "d" (func (;3;) (type 2))) + (import "foo" "e" (func (;4;) (type 3))) + (import "foo" "f" (func (;5;) (type 1))) + (import "foo" "g" (func (;6;) (type 1))) + (import "foo" "h" (func (;7;) (type 1))) + (import "foo" "i" (func (;8;) (type 1))) + (import "foo" "j" (func (;9;) (type 3))) + (import "foo" "k" (func (;10;) (type 3))) + (import "foo" "l" (func (;11;) (type 3))) + (import "foo" "m" (func (;12;) (type 3))) + (import "foo" "n" (func (;13;) (type 4))) + (import "foo" "o" (func (;14;) (type 3))) + (import "foo" "p" (func (;15;) (type 3))) + (func (;16;) (type 5) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (memory (;0;) 1) + (export "memory" (memory 0)) + (export "canonical_abi_realloc" (func 16)) + ) + (core module (;1;) + (type (;0;) (func (param i32 i32))) + (type (;1;) (func (param i32 i32 i32))) + (type (;2;) (func (param i32))) + (func (;0;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 0 + call_indirect (type 0) + ) + (func (;1;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 1 + call_indirect (type 0) + ) + (func (;2;) (type 1) (param i32 i32 i32) + local.get 0 + local.get 1 + local.get 2 + i32.const 2 + call_indirect (type 1) + ) + (func (;3;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 3 + call_indirect (type 0) + ) + (func (;4;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 4 + call_indirect (type 0) + ) + (func (;5;) (type 0) (param i32 i32) + local.get 0 + local.get 1 + i32.const 5 + call_indirect (type 0) + ) + (func (;6;) (type 2) (param i32) + local.get 0 + i32.const 6 + call_indirect (type 2) + ) + (func (;7;) (type 2) (param i32) + local.get 0 + i32.const 7 + call_indirect (type 2) + ) + (func (;8;) (type 2) (param i32) + local.get 0 + i32.const 8 + call_indirect (type 2) + ) + (func (;9;) (type 2) (param i32) + local.get 0 + i32.const 9 + call_indirect (type 2) + ) + (func (;10;) (type 2) (param i32) + local.get 0 + i32.const 10 + call_indirect (type 2) + ) + (table (;0;) 11 11 funcref) + (export "0" (func 0)) + (export "1" (func 1)) + (export "2" (func 2)) + (export "3" (func 3)) + (export "4" (func 4)) + (export "5" (func 5)) + (export "6" (func 6)) + (export "7" (func 7)) + (export "8" (func 8)) + (export "9" (func 9)) + (export "10" (func 10)) + (export "$imports" (table 0)) + ) + (core module (;2;) + (type (;0;) (func (param i32 i32))) + (type (;1;) (func (param i32 i32 i32))) + (type (;2;) (func (param i32))) + (import "" "0" (func (;0;) (type 0))) + (import "" "1" (func (;1;) (type 0))) + (import "" "2" (func (;2;) (type 1))) + (import "" "3" (func (;3;) (type 0))) + (import "" "4" (func (;4;) (type 0))) + (import "" "5" (func (;5;) (type 0))) + (import "" "6" (func (;6;) (type 2))) + (import "" "7" (func (;7;) (type 2))) + (import "" "8" (func (;8;) (type 2))) + (import "" "9" (func (;9;) (type 2))) + (import "" "10" (func (;10;) (type 2))) + (import "" "$imports" (table (;0;) 11 11 funcref)) + (elem (;0;) (i32.const 0) func 0 1 2 3 4 5 6 7 8 9 10) + ) + (core instance (;0;) (instantiate 1)) + (core alias export 0 "0" (func (;0;))) + (core alias export 0 "1" (func (;1;))) + (core alias export 0 "2" (func (;2;))) + (core alias export 0 "3" (func (;3;))) + (core alias export 0 "4" (func (;4;))) + (core alias export 0 "5" (func (;5;))) + (core alias export 0 "6" (func (;6;))) + (core alias export 0 "7" (func (;7;))) + (core alias export 0 "8" (func (;8;))) + (core alias export 0 "9" (func (;9;))) + (core alias export 0 "10" (func (;10;))) + (alias export 0 "a" (func (;0;))) + (alias export 0 "e" (func (;1;))) + (alias export 0 "f" (func (;2;))) + (alias export 0 "j" (func (;3;))) + (alias export 0 "n" (func (;4;))) + (core func (;11;) (canon lower (func 0))) + (core func (;12;) (canon lower (func 1))) + (core func (;13;) (canon lower (func 2))) + (core func (;14;) (canon lower (func 3))) + (core func (;15;) (canon lower (func 4))) + (core instance (;1;) + (export "b" (func 0)) + (export "c" (func 1)) + (export "d" (func 2)) + (export "g" (func 3)) + (export "h" (func 4)) + (export "i" (func 5)) + (export "k" (func 6)) + (export "l" (func 7)) + (export "m" (func 8)) + (export "o" (func 9)) + (export "p" (func 10)) + (export "a" (func 11)) + (export "e" (func 12)) + (export "f" (func 13)) + (export "j" (func 14)) + (export "n" (func 15)) + ) + (core instance (;2;) (instantiate 0 + (with "foo" (instance 1)) + ) + ) + (core alias export 2 "memory" (memory (;0;))) + (core alias export 2 "canonical_abi_realloc" (func (;16;))) + (core alias export 0 "$imports" (table (;0;))) + (alias export 0 "b" (func (;5;))) + (alias export 0 "c" (func (;6;))) + (alias export 0 "d" (func (;7;))) + (alias export 0 "g" (func (;8;))) + (alias export 0 "h" (func (;9;))) + (alias export 0 "i" (func (;10;))) + (alias export 0 "k" (func (;11;))) + (alias export 0 "l" (func (;12;))) + (alias export 0 "m" (func (;13;))) + (alias export 0 "o" (func (;14;))) + (alias export 0 "p" (func (;15;))) + (core func (;17;) (canon lower (func 5) (memory 0) (realloc 16) string-encoding=utf8)) + (core func (;18;) (canon lower (func 6) (memory 0) (realloc 16) string-encoding=utf8)) + (core func (;19;) (canon lower (func 7) (memory 0) (realloc 16) string-encoding=utf8)) + (core func (;20;) (canon lower (func 8) (memory 0) (realloc 16) string-encoding=utf8)) + (core func (;21;) (canon lower (func 9) (memory 0) (realloc 16) string-encoding=utf8)) + (core func (;22;) (canon lower (func 10) (memory 0) (realloc 16))) + (core func (;23;) (canon lower (func 11) (memory 0))) + (core func (;24;) (canon lower (func 12) (memory 0) (realloc 16) string-encoding=utf8)) + (core func (;25;) (canon lower (func 13) (memory 0) (realloc 16))) + (core func (;26;) (canon lower (func 14) (memory 0) (realloc 16) string-encoding=utf8)) + (core func (;27;) (canon lower (func 15) (memory 0) (realloc 16))) + (core instance (;3;) + (export "$imports" (table 0)) + (export "0" (func 17)) + (export "1" (func 18)) + (export "2" (func 19)) + (export "3" (func 20)) + (export "4" (func 21)) + (export "5" (func 22)) + (export "6" (func 23)) + (export "7" (func 24)) + (export "8" (func 25)) + (export "9" (func 26)) + (export "10" (func 27)) + ) + (core instance (;4;) (instantiate 2 + (with "" (instance 3)) + ) + ) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/import-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/import-foo.wit new file mode 100644 index 0000000..496bf1e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/import-foo.wit @@ -0,0 +1,32 @@ +record r { + s: string +} + +record r-no-string { + s: u32 +} + +variant v { + s(string) +} + +variant v-no-string { + s(u32) +} + +a: func() +b: func(x: list) +c: func(x: r) +d: func(x: v) +e: func(x: r-no-string) +f: func(x: v-no-string) +g: func(x: list) +h: func(x: list) +i: func(x: list) +j: func(x: u32) +k: func() -> tuple +l: func() -> string +m: func() -> list +n: func() -> u32 +o: func() -> v +p: func() -> list diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/module.wat new file mode 100644 index 0000000..7950a63 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/lower-options/module.wat @@ -0,0 +1,20 @@ +(module + (import "foo" "a" (func)) + (import "foo" "b" (func (param i32 i32))) + (import "foo" "c" (func (param i32 i32))) + (import "foo" "d" (func (param i32 i32 i32))) + (import "foo" "e" (func (param i32))) + (import "foo" "f" (func (param i32 i32))) + (import "foo" "g" (func (param i32 i32))) + (import "foo" "h" (func (param i32 i32))) + (import "foo" "i" (func (param i32 i32))) + (import "foo" "j" (func (param i32))) + (import "foo" "k" (func (param i32))) + (import "foo" "l" (func (param i32))) + (import "foo" "m" (func (param i32))) + (import "foo" "n" (func (result i32))) + (import "foo" "o" (func (param i32))) + (import "foo" "p" (func (param i32))) + (memory (export "memory") 1) + (func (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/default.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/default.wit new file mode 100644 index 0000000..b7a21e1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/default.wit @@ -0,0 +1 @@ +a: func() \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/error.txt new file mode 100644 index 0000000..8368ada --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/error.txt @@ -0,0 +1 @@ +module does not export required function `a` \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/module.wat new file mode 100644 index 0000000..800bef5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-default-export/module.wat @@ -0,0 +1 @@ +(module) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/error.txt new file mode 100644 index 0000000..821563e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/error.txt @@ -0,0 +1 @@ +module does not export required function `foo#a` \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/export-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/export-foo.wit new file mode 100644 index 0000000..b7a21e1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/export-foo.wit @@ -0,0 +1 @@ +a: func() \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/module.wat new file mode 100644 index 0000000..800bef5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-export/module.wat @@ -0,0 +1 @@ +(module) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/error.txt new file mode 100644 index 0000000..a79587d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/error.txt @@ -0,0 +1 @@ +import interface `foo` is missing function `bar` that is required by the module \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/import-foo.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/import-foo.wit new file mode 100644 index 0000000..b7a21e1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/import-foo.wit @@ -0,0 +1 @@ +a: func() \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/module.wat new file mode 100644 index 0000000..96d3790 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import-func/module.wat @@ -0,0 +1,3 @@ +(module + (import "foo" "bar" (func)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import/error.txt b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import/error.txt new file mode 100644 index 0000000..b4d2327 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import/error.txt @@ -0,0 +1 @@ +module requires an import interface named `foo` \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import/module.wat new file mode 100644 index 0000000..96d3790 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/missing-import/module.wat @@ -0,0 +1,3 @@ +(module + (import "foo" "bar" (func)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/component.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/component.wat new file mode 100644 index 0000000..f2d6dfb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/component.wat @@ -0,0 +1,52 @@ +(component + (type (;0;) (func)) + (type (;1;) (func (result string))) + (type (;2;) (func (param "x" string) (result string))) + (type (;3;) (list string)) + (type (;4;) (func (param "x" 3))) + (export "x" (type 3)) + (core module (;0;) + (type (;0;) (func (param i32 i32 i32 i32) (result i32))) + (type (;1;) (func)) + (type (;2;) (func (result i32))) + (type (;3;) (func (param i32 i32) (result i32))) + (type (;4;) (func (param i32 i32))) + (func $canonical_abi_realloc (;0;) (type 0) (param i32 i32 i32 i32) (result i32) + unreachable + ) + (func $a (;1;) (type 1) + unreachable + ) + (func $b (;2;) (type 2) (result i32) + unreachable + ) + (func $c (;3;) (type 3) (param i32 i32) (result i32) + unreachable + ) + (func $d (;4;) (type 4) (param i32 i32) + unreachable + ) + (memory $memory (;0;) 1) + (export "memory" (memory $memory)) + (export "canonical_abi_realloc" (func $canonical_abi_realloc)) + (export "a" (func $a)) + (export "b" (func $b)) + (export "c" (func $c)) + (export "d" (func $d)) + ) + (core instance (;0;) (instantiate 0)) + (core alias export 0 "memory" (memory (;0;))) + (core alias export 0 "canonical_abi_realloc" (func (;0;))) + (core alias export 0 "a" (func (;1;))) + (core alias export 0 "b" (func (;2;))) + (core alias export 0 "c" (func (;3;))) + (core alias export 0 "d" (func (;4;))) + (func (;0;) (type 0) (canon lift (core func 1))) + (func (;1;) (type 1) (canon lift (core func 2) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;2;) (type 2) (canon lift (core func 3) (memory 0) (realloc 0) string-encoding=utf8)) + (func (;3;) (type 4) (canon lift (core func 4) (memory 0) (realloc 0) string-encoding=utf8)) + (export "a" (func 0)) + (export "b" (func 1)) + (export "c" (func 2)) + (export "d" (func 3)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/default.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/default.wit new file mode 100644 index 0000000..c85db88 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/default.wit @@ -0,0 +1,6 @@ +type x = list + +a: func() +b: func() -> string +c: func(x: string) -> string +d: func(x: x) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/module.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/module.wat new file mode 100644 index 0000000..a7aebe3 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/components/simple/module.wat @@ -0,0 +1,8 @@ +(module + (memory $memory (export "memory") 1) + (func $canonical_abi_realloc (export "canonical_abi_realloc") (param i32 i32 i32 i32) (result i32) unreachable) + (func $a (export "a") unreachable) + (func $b (export "b") (result i32) unreachable) + (func $c (export "c") (param i32 i32) (result i32) unreachable) + (func $d (export "d") (param i32 i32) unreachable) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces.rs new file mode 100644 index 0000000..e2f320d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces.rs @@ -0,0 +1,60 @@ +use anyhow::{Context, Result}; +use pretty_assertions::assert_eq; +use std::fs; +use wit_component::InterfaceEncoder; +use wit_parser::Interface; + +/// Tests the encoding of individual interface files. +/// +/// This test looks in the `interfaces/` directory for test cases. +/// +/// Each test case is a directory containing a `.wit` file +/// and an expected `.wat` file. +/// +/// The test encodes the wit file, prints the resulting component, and +/// compares the output to the wat file. +/// +/// Run the test with the environment variable `BLESS` set to update +/// the wat baseline file. +#[test] +fn interface_encoding() -> Result<()> { + for entry in fs::read_dir("tests/interfaces")? { + let path = entry?.path(); + if !path.is_dir() { + continue; + } + + let test_case = path.file_stem().unwrap().to_str().unwrap(); + let wit_path = path.join(test_case).with_extension("wit"); + + let interface = Interface::parse_file(&wit_path)?; + + let encoder = InterfaceEncoder::new(&interface).validate(true); + + let bytes = encoder.encode().with_context(|| { + format!( + "failed to encode a component from interface `{}` for test case `{}`", + wit_path.display(), + test_case, + ) + })?; + + let output = wasmprinter::print_bytes(&bytes)?; + let wat_path = wit_path.with_extension("wat"); + + if std::env::var_os("BLESS").is_some() { + fs::write(&wat_path, output)?; + } else { + assert_eq!( + fs::read_to_string(&wat_path)?.replace("\r\n", "\n"), + output, + "encoding of wit file `{}` did not match the expected wat file `{}` for test case `{}`", + wit_path.display(), + wat_path.display(), + test_case + ); + } + } + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/empty/empty.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/empty/empty.wat new file mode 100644 index 0000000..0474395 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/empty/empty.wat @@ -0,0 +1 @@ +(component) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/empty/empty.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/empty/empty.wit new file mode 100644 index 0000000..e69de29 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/flags/flags.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/flags/flags.wat new file mode 100644 index 0000000..795d8b0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/flags/flags.wat @@ -0,0 +1,30 @@ +(component + (type (;0;) (flags "b0")) + (type (;1;) (func (param "x" 0) (result 0))) + (type (;2;) (flags "b0" "b1")) + (type (;3;) (func (param "x" 2) (result 2))) + (type (;4;) (flags "b0" "b1" "b2" "b3")) + (type (;5;) (func (param "x" 4) (result 4))) + (type (;6;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7")) + (type (;7;) (func (param "x" 6) (result 6))) + (type (;8;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15")) + (type (;9;) (func (param "x" 8) (result 8))) + (type (;10;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31")) + (type (;11;) (func (param "x" 10) (result 10))) + (type (;12;) (flags "b0" "b1" "b2" "b3" "b4" "b5" "b6" "b7" "b8" "b9" "b10" "b11" "b12" "b13" "b14" "b15" "b16" "b17" "b18" "b19" "b20" "b21" "b22" "b23" "b24" "b25" "b26" "b27" "b28" "b29" "b30" "b31" "b32" "b33" "b34" "b35" "b36" "b37" "b38" "b39" "b40" "b41" "b42" "b43" "b44" "b45" "b46" "b47" "b48" "b49" "b50" "b51" "b52" "b53" "b54" "b55" "b56" "b57" "b58" "b59" "b60" "b61" "b62" "b63")) + (type (;13;) (func (param "x" 12) (result 12))) + (export "flag1" (type 0)) + (export "roundtrip-flag1" (type 1)) + (export "flag2" (type 2)) + (export "roundtrip-flag2" (type 3)) + (export "flag4" (type 4)) + (export "roundtrip-flag4" (type 5)) + (export "flag8" (type 6)) + (export "roundtrip-flag8" (type 7)) + (export "flag16" (type 8)) + (export "roundtrip-flag16" (type 9)) + (export "flag32" (type 10)) + (export "roundtrip-flag32" (type 11)) + (export "flag64" (type 12)) + (export "roundtrip-flag64" (type 13)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/flags/flags.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/flags/flags.wit new file mode 100644 index 0000000..ea6b8d8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/flags/flags.wit @@ -0,0 +1,162 @@ +flags flag1 { + b0, +} + +flags flag2 { + b0, + b1, +} + +flags flag4 { + b0, + b1, + b2, + b3, +} + +flags flag8 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, +} + +flags flag16 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, +} + +flags flag32 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, + b16, + b17, + b18, + b19, + b20, + b21, + b22, + b23, + b24, + b25, + b26, + b27, + b28, + b29, + b30, + b31, +} + +flags flag64 { + b0, + b1, + b2, + b3, + b4, + b5, + b6, + b7, + b8, + b9, + b10, + b11, + b12, + b13, + b14, + b15, + b16, + b17, + b18, + b19, + b20, + b21, + b22, + b23, + b24, + b25, + b26, + b27, + b28, + b29, + b30, + b31, + b32, + b33, + b34, + b35, + b36, + b37, + b38, + b39, + b40, + b41, + b42, + b43, + b44, + b45, + b46, + b47, + b48, + b49, + b50, + b51, + b52, + b53, + b54, + b55, + b56, + b57, + b58, + b59, + b60, + b61, + b62, + b63, +} + +roundtrip-flag1: func(x: flag1) -> flag1 + +roundtrip-flag2: func(x: flag2) -> flag2 + +roundtrip-flag4: func(x: flag4) -> flag4 + +roundtrip-flag8: func(x: flag8) -> flag8 + +roundtrip-flag16: func(x: flag16) -> flag16 + +roundtrip-flag32: func(x: flag32) -> flag32 + +roundtrip-flag64: func(x: flag64) -> flag64 + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/floats/floats.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/floats/floats.wat new file mode 100644 index 0000000..41f9ccb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/floats/floats.wat @@ -0,0 +1,10 @@ +(component + (type (;0;) (func (param "x" float32))) + (type (;1;) (func (param "x" float64))) + (type (;2;) (func (result float32))) + (type (;3;) (func (result float64))) + (export "float32-param" (type 0)) + (export "float64-param" (type 1)) + (export "float32-result" (type 2)) + (export "float64-result" (type 3)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/floats/floats.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/floats/floats.wit new file mode 100644 index 0000000..5d20651 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/floats/floats.wit @@ -0,0 +1,8 @@ +float32-param: func(x: float32) + +float64-param: func(x: float64) + +float32-result: func() -> float32 + +float64-result: func() -> float64 + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/integers/integers.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/integers/integers.wat new file mode 100644 index 0000000..f8b61b6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/integers/integers.wat @@ -0,0 +1,40 @@ +(component + (type (;0;) (func (param "x" u8))) + (type (;1;) (func (param "x" s8))) + (type (;2;) (func (param "x" u16))) + (type (;3;) (func (param "x" s16))) + (type (;4;) (func (param "x" u32))) + (type (;5;) (func (param "x" s32))) + (type (;6;) (func (param "x" u64))) + (type (;7;) (func (param "x" s64))) + (type (;8;) (func (param "p1" u8) (param "p2" s8) (param "p3" u16) (param "p4" s16) (param "p5" u32) (param "p6" s32) (param "p7" u64) (param "p8" s64))) + (type (;9;) (func (result u8))) + (type (;10;) (func (result s8))) + (type (;11;) (func (result u16))) + (type (;12;) (func (result s16))) + (type (;13;) (func (result u32))) + (type (;14;) (func (result s32))) + (type (;15;) (func (result u64))) + (type (;16;) (func (result s64))) + (type (;17;) (tuple s64 u8)) + (type (;18;) (func (result 17))) + (export "a1" (type 0)) + (export "a2" (type 1)) + (export "a3" (type 2)) + (export "a4" (type 3)) + (export "a5" (type 4)) + (export "a6" (type 5)) + (export "a7" (type 6)) + (export "a8" (type 7)) + (export "a9" (type 8)) + (export "r1" (type 9)) + (export "r2" (type 10)) + (export "r3" (type 11)) + (export "r4" (type 12)) + (export "r5" (type 13)) + (export "r6" (type 14)) + (export "r7" (type 15)) + (export "r8" (type 16)) + (export "pair-ret" (type 18)) + (export "multi-ret" (type 18)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/integers/integers.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/integers/integers.wit new file mode 100644 index 0000000..dedd40e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/integers/integers.wit @@ -0,0 +1,38 @@ +a1: func(x: u8) + +a2: func(x: s8) + +a3: func(x: u16) + +a4: func(x: s16) + +a5: func(x: u32) + +a6: func(x: s32) + +a7: func(x: u64) + +a8: func(x: s64) + +a9: func(p1: u8, p2: s8, p3: u16, p4: s16, p5: u32, p6: s32, p7: u64, p8: s64) + +r1: func() -> u8 + +r2: func() -> s8 + +r3: func() -> u16 + +r4: func() -> s16 + +r5: func() -> u32 + +r6: func() -> s32 + +r7: func() -> u64 + +r8: func() -> s64 + +pair-ret: func() -> tuple + +multi-ret: func() -> tuple + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/lists/lists.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/lists/lists.wat new file mode 100644 index 0000000..eade245 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/lists/lists.wat @@ -0,0 +1,92 @@ +(component + (type (;0;) (list u8)) + (type (;1;) (func (param "x" 0))) + (type (;2;) (list u16)) + (type (;3;) (func (param "x" 2))) + (type (;4;) (list u32)) + (type (;5;) (func (param "x" 4))) + (type (;6;) (list u64)) + (type (;7;) (func (param "x" 6))) + (type (;8;) (list s8)) + (type (;9;) (func (param "x" 8))) + (type (;10;) (list s16)) + (type (;11;) (func (param "x" 10))) + (type (;12;) (list s32)) + (type (;13;) (func (param "x" 12))) + (type (;14;) (list s64)) + (type (;15;) (func (param "x" 14))) + (type (;16;) (list float32)) + (type (;17;) (func (param "x" 16))) + (type (;18;) (list float64)) + (type (;19;) (func (param "x" 18))) + (type (;20;) (func (result 0))) + (type (;21;) (func (result 2))) + (type (;22;) (func (result 4))) + (type (;23;) (func (result 6))) + (type (;24;) (func (result 8))) + (type (;25;) (func (result 10))) + (type (;26;) (func (result 12))) + (type (;27;) (func (result 14))) + (type (;28;) (func (result 16))) + (type (;29;) (func (result 18))) + (type (;30;) (tuple u8 s8)) + (type (;31;) (list 30)) + (type (;32;) (tuple s64 u32)) + (type (;33;) (list 32)) + (type (;34;) (func (param "x" 31) (result 33))) + (type (;35;) (list string)) + (type (;36;) (func (param "a" 35))) + (type (;37;) (func (result 35))) + (type (;38;) (tuple u8 string)) + (type (;39;) (list 38)) + (type (;40;) (tuple string u8)) + (type (;41;) (list 40)) + (type (;42;) (func (param "x" 39) (result 41))) + (type (;43;) (func (param "x" 35) (result 35))) + (type (;44;) (record (field "a1" u32) (field "a2" u64) (field "a3" s32) (field "a4" s64) (field "b" string) (field "c" 0))) + (type (;45;) (record (field "x" string) (field "y" 44) (field "c1" u32) (field "c2" u64) (field "c3" s32) (field "c4" s64))) + (type (;46;) (list 45)) + (type (;47;) (list 44)) + (type (;48;) (func (param "x" 46) (result 47))) + (type (;49;) (variant (case $c0 "a" unit) (case $c1 "b" u32) (case $c2 "c" string))) + (type (;50;) (list 49)) + (type (;51;) (variant (case $c0 "a" string) (case $c1 "b" unit) (case $c2 "c" u32) (case $c3 "d" 50))) + (type (;52;) (list 51)) + (type (;53;) (func (param "x" 52) (result 50))) + (type (;54;) (tuple string u8 s8 u16 s16 u32 s32 u64 s64 float32 float64 char)) + (type (;55;) (list 54)) + (type (;56;) (func (param "a" 55) (result 55))) + (export "list-u8-param" (type 1)) + (export "list-u16-param" (type 3)) + (export "list-u32-param" (type 5)) + (export "list-u64-param" (type 7)) + (export "list-s8-param" (type 9)) + (export "list-s16-param" (type 11)) + (export "list-s32-param" (type 13)) + (export "list-s64-param" (type 15)) + (export "list-float32-param" (type 17)) + (export "list-float64-param" (type 19)) + (export "list-u8-ret" (type 20)) + (export "list-u16-ret" (type 21)) + (export "list-u32-ret" (type 22)) + (export "list-u64-ret" (type 23)) + (export "list-s8-ret" (type 24)) + (export "list-s16-ret" (type 25)) + (export "list-s32-ret" (type 26)) + (export "list-s64-ret" (type 27)) + (export "list-float32-ret" (type 28)) + (export "list-float64-ret" (type 29)) + (export "tuple-list" (type 34)) + (export "string-list-arg" (type 36)) + (export "string-list-ret" (type 37)) + (export "tuple-string-list" (type 42)) + (export "string-list" (type 43)) + (export "other-record" (type 44)) + (export "some-record" (type 45)) + (export "record-list" (type 48)) + (export "other-variant" (type 49)) + (export "some-variant" (type 51)) + (export "variant-list" (type 53)) + (export "load-store-all-sizes" (type 55)) + (export "load-store-everything" (type 56)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/lists/lists.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/lists/lists.wit new file mode 100644 index 0000000..1f31804 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/lists/lists.wit @@ -0,0 +1,89 @@ +record other-record { + a1: u32, + a2: u64, + a3: s32, + a4: s64, + b: string, + c: list, +} + +record some-record { + x: string, + y: other-record, + c1: u32, + c2: u64, + c3: s32, + c4: s64, +} + +variant other-variant { + a, + b(u32), + c(string), +} + +variant some-variant { + a(string), + b, + c(u32), + d(list), +} + +type load-store-all-sizes = list> + +list-u8-param: func(x: list) + +list-u16-param: func(x: list) + +list-u32-param: func(x: list) + +list-u64-param: func(x: list) + +list-s8-param: func(x: list) + +list-s16-param: func(x: list) + +list-s32-param: func(x: list) + +list-s64-param: func(x: list) + +list-float32-param: func(x: list) + +list-float64-param: func(x: list) + +list-u8-ret: func() -> list + +list-u16-ret: func() -> list + +list-u32-ret: func() -> list + +list-u64-ret: func() -> list + +list-s8-ret: func() -> list + +list-s16-ret: func() -> list + +list-s32-ret: func() -> list + +list-s64-ret: func() -> list + +list-float32-ret: func() -> list + +list-float64-ret: func() -> list + +tuple-list: func(x: list>) -> list> + +string-list-arg: func(a: list) + +string-list-ret: func() -> list + +tuple-string-list: func(x: list>) -> list> + +string-list: func(x: list) -> list + +record-list: func(x: list) -> list + +variant-list: func(x: list) -> list + +load-store-everything: func(a: load-store-all-sizes) -> load-store-all-sizes + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/records/records.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/records/records.wat new file mode 100644 index 0000000..a15375a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/records/records.wat @@ -0,0 +1,37 @@ +(component + (type (;0;) (tuple char u32)) + (type (;1;) (func (param "x" 0))) + (type (;2;) (func (result 0))) + (type (;3;) (record)) + (type (;4;) (func (param "x" 3))) + (type (;5;) (func (result 3))) + (type (;6;) (record (field "a" u32) (field "b" u32))) + (type (;7;) (func (param "x" 6))) + (type (;8;) (func (result 6))) + (type (;9;) (flags "a" "b" "c" "d" "e" "f" "g" "h" "i")) + (type (;10;) (func (param "x" 9))) + (type (;11;) (func (result 9))) + (type (;12;) (record (field "a" 6) (field "b" u32) (field "c" 3) (field "d" string) (field "e" 9))) + (type (;13;) (func (param "x" 12))) + (type (;14;) (func (result 12))) + (type (;15;) s32) + (type (;16;) (tuple 15)) + (type (;17;) (func (param "e" 16) (result s32))) + (export "tuple-arg" (type 1)) + (export "tuple-result" (type 2)) + (export "empty" (type 3)) + (export "empty-arg" (type 4)) + (export "empty-result" (type 5)) + (export "scalars" (type 6)) + (export "scalar-arg" (type 7)) + (export "scalar-result" (type 8)) + (export "really-flags" (type 9)) + (export "flags-arg" (type 10)) + (export "flags-result" (type 11)) + (export "aggregates" (type 12)) + (export "aggregate-arg" (type 13)) + (export "aggregate-result" (type 14)) + (export "int-typedef" (type 15)) + (export "tuple-typedef2" (type 16)) + (export "typedef-inout" (type 17)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/records/records.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/records/records.wit new file mode 100644 index 0000000..87302bf --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/records/records.wit @@ -0,0 +1,54 @@ +record empty { +} + +record scalars { + a: u32, + b: u32, +} + +flags really-flags { + a, + b, + c, + d, + e, + f, + g, + h, + i, +} + +record aggregates { + a: scalars, + b: u32, + c: empty, + d: string, + e: really-flags, +} + +type int-typedef = s32 + +type tuple-typedef2 = tuple + +tuple-arg: func(x: tuple) + +tuple-result: func() -> tuple + +empty-arg: func(x: empty) + +empty-result: func() -> empty + +scalar-arg: func(x: scalars) + +scalar-result: func() -> scalars + +flags-arg: func(x: really-flags) + +flags-result: func() -> really-flags + +aggregate-arg: func(x: aggregates) + +aggregate-result: func() -> aggregates + +typedef-inout: func(e: tuple-typedef2) -> s32 + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/variants/variants.wat b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/variants/variants.wat new file mode 100644 index 0000000..bcb5456 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/variants/variants.wat @@ -0,0 +1,92 @@ +(component + (type (;0;) (enum "a")) + (type (;1;) (func (param "x" 0))) + (type (;2;) (func (result 0))) + (type (;3;) (union u32 float32)) + (type (;4;) (func (param "x" 3))) + (type (;5;) (func (result 3))) + (type (;6;) (record)) + (type (;7;) (variant (case $c0 "a" unit) (case $c1 "b" 3) (case $c2 "c" 0) (case $c3 "d" string) (case $c4 "e" 6) (case $c5 "f" unit) (case $c6 "g" u32))) + (type (;8;) (func (param "x" 7))) + (type (;9;) (func (result 7))) + (type (;10;) (func (param "x" bool))) + (type (;11;) (func (result bool))) + (type (;12;) (option bool)) + (type (;13;) (tuple)) + (type (;14;) (option 13)) + (type (;15;) (option u32)) + (type (;16;) (option 0)) + (type (;17;) (option float32)) + (type (;18;) (option 3)) + (type (;19;) (option 12)) + (type (;20;) (func (param "a" 12) (param "b" 14) (param "c" 15) (param "d" 16) (param "e" 17) (param "f" 18) (param "g" 19))) + (type (;21;) (tuple 12 14 15 16 17 18 19)) + (type (;22;) (func (result 21))) + (type (;23;) (variant (case $c0 "a" s32) (case $c1 "b" float32))) + (type (;24;) (variant (case $c0 "a" float64) (case $c1 "b" float32))) + (type (;25;) (variant (case $c0 "a" float64) (case $c1 "b" u64))) + (type (;26;) (variant (case $c0 "a" u32) (case $c1 "b" s64))) + (type (;27;) (variant (case $c0 "a" float32) (case $c1 "b" s64))) + (type (;28;) (tuple float32 u32)) + (type (;29;) (tuple u32 u32)) + (type (;30;) (variant (case $c0 "a" 28) (case $c1 "b" 29))) + (type (;31;) (tuple 23 24 25 26 27 30)) + (type (;32;) (func (param "a" 23) (param "b" 24) (param "c" 25) (param "d" 26) (param "e" 27) (param "f" 30) (result 31))) + (type (;33;) (expected unit unit)) + (type (;34;) (expected unit 0)) + (type (;35;) (expected 0 unit)) + (type (;36;) (expected 13 13)) + (type (;37;) (expected u32 7)) + (type (;38;) (list u8)) + (type (;39;) (expected string 38)) + (type (;40;) (func (param "a" 33) (param "b" 34) (param "c" 35) (param "d" 36) (param "e" 37) (param "f" 39))) + (type (;41;) (tuple 33 34 35 36 37 39)) + (type (;42;) (func (result 41))) + (type (;43;) (enum "bad1" "bad2")) + (type (;44;) (expected s32 43)) + (type (;45;) (func (result 44))) + (type (;46;) (expected unit 43)) + (type (;47;) (func (result 46))) + (type (;48;) (expected 43 43)) + (type (;49;) (func (result 48))) + (type (;50;) (tuple s32 u32)) + (type (;51;) (expected 50 43)) + (type (;52;) (func (result 51))) + (type (;53;) (option s32)) + (type (;54;) (func (result 53))) + (type (;55;) (option 43)) + (type (;56;) (func (result 55))) + (type (;57;) (expected u32 s32)) + (type (;58;) (func (result 57))) + (export "e1" (type 0)) + (export "e1-arg" (type 1)) + (export "e1-result" (type 2)) + (export "u1" (type 3)) + (export "u1-arg" (type 4)) + (export "u1-result" (type 5)) + (export "empty" (type 6)) + (export "v1" (type 7)) + (export "v1-arg" (type 8)) + (export "v1-result" (type 9)) + (export "bool-arg" (type 10)) + (export "bool-result" (type 11)) + (export "option-arg" (type 20)) + (export "option-result" (type 22)) + (export "casts1" (type 23)) + (export "casts2" (type 24)) + (export "casts3" (type 25)) + (export "casts4" (type 26)) + (export "casts5" (type 27)) + (export "casts6" (type 30)) + (export "casts" (type 32)) + (export "expected-arg" (type 40)) + (export "expected-result" (type 42)) + (export "my-errno" (type 43)) + (export "return-expected-sugar" (type 45)) + (export "return-expected-sugar2" (type 47)) + (export "return-expected-sugar3" (type 49)) + (export "return-expected-sugar4" (type 52)) + (export "return-option-sugar" (type 54)) + (export "return-option-sugar2" (type 56)) + (export "expected-simple" (type 58)) +) \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/variants/variants.wit b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/variants/variants.wit new file mode 100644 index 0000000..4bace14 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/interfaces/variants/variants.wit @@ -0,0 +1,97 @@ +enum e1 { + a, +} + +union u1 { + u32, + float32, +} + +record empty { +} + +variant v1 { + a, + b(u1), + c(e1), + d(string), + e(empty), + f, + g(u32), +} + +variant casts1 { + a(s32), + b(float32), +} + +variant casts2 { + a(float64), + b(float32), +} + +variant casts3 { + a(float64), + b(u64), +} + +variant casts4 { + a(u32), + b(s64), +} + +variant casts5 { + a(float32), + b(s64), +} + +variant casts6 { + a(tuple), + b(tuple), +} + +enum my-errno { + bad1, + bad2, +} + +e1-arg: func(x: e1) + +e1-result: func() -> e1 + +u1-arg: func(x: u1) + +u1-result: func() -> u1 + +v1-arg: func(x: v1) + +v1-result: func() -> v1 + +bool-arg: func(x: bool) + +bool-result: func() -> bool + +option-arg: func(a: option, b: option>, c: option, d: option, e: option, f: option, g: option>) + +option-result: func() -> tuple, option>, option, option, option, option, option>> + +casts: func(a: casts1, b: casts2, c: casts3, d: casts4, e: casts5, f: casts6) -> tuple + +expected-arg: func(a: expected, b: expected, c: expected, d: expected, tuple<>>, e: expected, f: expected>) + +expected-result: func() -> tuple, expected, expected, expected, tuple<>>, expected, expected>> + +return-expected-sugar: func() -> expected + +return-expected-sugar2: func() -> expected + +return-expected-sugar3: func() -> expected + +return-expected-sugar4: func() -> expected, my-errno> + +return-option-sugar: func() -> option + +return-option-sugar2: func() -> option + +expected-simple: func() -> expected + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/roundtrip.rs b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/roundtrip.rs new file mode 100644 index 0000000..fd0ea94 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/crates/wit-component/tests/roundtrip.rs @@ -0,0 +1,63 @@ +use anyhow::{Context, Result}; +use pretty_assertions::assert_eq; +use std::fs; +use wit_component::{decode_interface_component, InterfaceEncoder, InterfacePrinter}; +use wit_parser::Interface; + +/// Tests the the roundtrip encoding of individual interface files. +/// +/// This test looks in the `interfaces/` directory for test cases. +/// +/// Each test case is a directory containing a `.wit` file. +/// +/// The test encodes the wit file, decodes the resulting bytes, and +/// compares the generated interface definition to the original interface +/// definition. +/// +/// Run the test with the environment variable `BLESS` set to update +/// the wit file based on the decoded output. +#[test] +fn roundtrip_interfaces() -> Result<()> { + for entry in fs::read_dir("tests/interfaces")? { + let path = entry?.path(); + if !path.is_dir() { + continue; + } + + let test_case = path.file_stem().unwrap().to_str().unwrap(); + let wit_path = path.join(test_case).with_extension("wit"); + + let interface = Interface::parse_file(&wit_path).context("failed to parse `wit` file")?; + + let encoder = InterfaceEncoder::new(&interface).validate(true); + + let bytes = encoder.encode().with_context(|| { + format!( + "failed to encode a component from interface `{}` for test case `{}`", + path.display(), + test_case, + ) + })?; + + let interface = decode_interface_component(&bytes).context("failed to decode bytes")?; + + let mut printer = InterfacePrinter::default(); + let output = printer + .print(&interface) + .context("failed to print interface")?; + + if std::env::var_os("BLESS").is_some() { + fs::write(&wit_path, output)?; + } else { + assert_eq!( + fs::read_to_string(&wit_path)?.replace("\r\n", "\n"), + output, + "encoding of wit file `{}` did not match the the decoded interface for test case `{}`", + wit_path.display(), + test_case, + ); + } + } + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/src/bin/wit-bindgen.rs b/__wasm/wit-bindgen-sample/wit-bindgen/src/bin/wit-bindgen.rs new file mode 100644 index 0000000..836ea06 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/src/bin/wit-bindgen.rs @@ -0,0 +1,121 @@ +use anyhow::{Context, Result}; +use std::path::PathBuf; +use structopt::StructOpt; +use wit_bindgen_gen_core::{wit_parser, Files, Generator}; +use wit_parser::Interface; + +#[derive(Debug, StructOpt)] +struct Opt { + #[structopt(subcommand)] + command: Command, +} + +#[derive(Debug, StructOpt)] +enum Command { + RustWasm { + #[structopt(flatten)] + opts: wit_bindgen_gen_rust_wasm::Opts, + #[structopt(flatten)] + common: Common, + }, + Wasmtime { + #[structopt(flatten)] + opts: wit_bindgen_gen_wasmtime::Opts, + #[structopt(flatten)] + common: Common, + }, + WasmtimePy { + #[structopt(flatten)] + opts: wit_bindgen_gen_wasmtime_py::Opts, + #[structopt(flatten)] + common: Common, + }, + Js { + #[structopt(flatten)] + opts: wit_bindgen_gen_js::Opts, + #[structopt(flatten)] + common: Common, + }, + C { + #[structopt(flatten)] + opts: wit_bindgen_gen_c::Opts, + #[structopt(flatten)] + common: Common, + }, + Markdown { + #[structopt(flatten)] + opts: wit_bindgen_gen_markdown::Opts, + #[structopt(flatten)] + common: Common, + }, + #[structopt(name = "spidermonkey")] + SpiderMonkey { + #[structopt(flatten)] + opts: wit_bindgen_gen_spidermonkey::Opts, + #[structopt(flatten)] + common: Common, + }, +} + +#[derive(Debug, StructOpt)] +struct Common { + /// Where to place output files + #[structopt(long = "out-dir")] + out_dir: Option, + + /// Generate import bindings for the given `*.wit` interface. Can be + /// specified multiple times. + #[structopt(long = "import", short)] + imports: Vec, + + /// Generate export bindings for the given `*.wit` interface. Can be + /// specified multiple times. + #[structopt(long = "export", short)] + exports: Vec, +} + +fn main() -> Result<()> { + let opt = Opt::from_args(); + let (mut generator, common): (Box, _) = match opt.command { + Command::RustWasm { opts, common } => (Box::new(opts.build()), common), + Command::Wasmtime { opts, common } => (Box::new(opts.build()), common), + Command::WasmtimePy { opts, common } => (Box::new(opts.build()), common), + Command::Js { opts, common } => (Box::new(opts.build()), common), + Command::C { opts, common } => (Box::new(opts.build()), common), + Command::Markdown { opts, common } => (Box::new(opts.build()), common), + Command::SpiderMonkey { opts, common } => { + let js_source = std::fs::read_to_string(&opts.js) + .with_context(|| format!("failed to read {}", opts.js.display()))?; + (Box::new(opts.build(js_source)), common) + } + }; + + let imports = common + .imports + .iter() + .map(|wit| Interface::parse_file(wit)) + .collect::>>()?; + let exports = common + .exports + .iter() + .map(|wit| Interface::parse_file(wit)) + .collect::>>()?; + + let mut files = Files::default(); + generator.generate_all(&imports, &exports, &mut files); + + for (name, contents) in files.iter() { + let dst = match &common.out_dir { + Some(path) => path.join(name), + None => name.into(), + }; + println!("Generating {:?}", dst); + if let Some(parent) = dst.parent() { + std::fs::create_dir_all(parent) + .with_context(|| format!("failed to create {:?}", parent))?; + } + std::fs::write(&dst, contents).with_context(|| format!("failed to write {:?}", dst))?; + } + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/README.md b/__wasm/wit-bindgen-sample/wit-bindgen/tests/README.md new file mode 100644 index 0000000..bd6836b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/README.md @@ -0,0 +1,72 @@ +# Testing wit-bindgen - `codegen + +Any tests placed into the `tests/codegen` directory should be raw `*.wit` +files. These files will be executed in all code generators by default most +likely, and the purpose of these files is to execute language-specific +validation for each bindings generator. Basically if there's a bug where +something generates invalid code then this is probably where the test should go. +Note that this directory can have whatever it wants since nothing implements the +interfaces or tries to call them. + +# Testing wit-bindgen - `runtime` + +Otherwise tests are organized in `tests/runtime/*`. Inside this directory is a +directory-per-test. These tests are somewhat heavyweight so you may want to +extend existing tests, but it's also fine to add new tests at any time. + +The purpose of this directory is to contain code that's actually compiled to +wasm and executed on hosts. The code compiled-to-wasm can be one of: + +* `wasm.rs` - compiled with Rust to WebAssembly +* `wasm.c` - compiled with Clang + +Existence of these files indicates that the language should be supported for the +test, and if a file is missing then it's skipped when running other tests. Each +`wasm.*` file is run inside each of the host files: + +* `host.rs` - executes wasms with Wasmtime +* `host.js` - executes WebAssembly with node.js +* `host.py` - executes with `wasmtime`'s PyPI package. + +Each of these hosts can also be omitted if the host doesn't implement the test +or something like that. Otherwise for each host that exists when the host's +crate generator crate is tested it will run all these tests. + +# Testing Layout + +If you're adding a test, all you should generally have to do is edit files in +`tests/runtime/*`. If you're adding a new test it *should* be along the lines of +just dropping some files in there, but currently if you're adding a +Rust-compiled-to-wasm binary you'll need to edit +`crates/test-rust-wasm/Cargo.toml` and add a corresponding binary to +`crates/test-rust-wasm/src/bin/*.rs` (in the same manner as the other tests). +Other than this though all other generators should automatically pick up new +tests. + +The actual way tests are hooked up looks roughly like: + +* All generator crates have a `codegen.rs` and a `runtime.rs` integration test + file (typically defined in the crate's own `tests/*.rs` directory). +* All generator crates depend on `crates/test-helpers`. This crate will walk the + appropriate directory in the top-level `tests/*` directory. +* The `test-helpers` crate will generate appropriate `#[test]` functions to + execute tests. For example the JS generator will run `eslint` or `tsc`. Rust + tests for `codegen` are simply that they compile. +* The `test-helpers` crate also builds wasm files at build time, both the Rust + and C versions. These are then encoded into the generated `#[test]` functions + to get executed when testing. + +The general layout is then that if you want to run the JS host tests you run: + +``` +$ cargo test -p wit-bindgen-gen-js +``` + +and if you want to run all tests you can execute: + +``` +$ cargo test --workspace +``` + +It's all a bit convoluted so feel free to ask questions on Zulip or open an +issue if you're lost. diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/async-functions.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/async-functions.wit new file mode 100644 index 0000000..81d02f6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/async-functions.wit @@ -0,0 +1,7 @@ +async-no-args: async func() +async-args: async func(a: u32, b: string, c: list) +async-results: async func() -> tuple> + +resource async-resource { + frob: async func() +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/char.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/char.wit new file mode 100644 index 0000000..24dd4cc --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/char.wit @@ -0,0 +1,4 @@ +/// A function that accepts a character +take-char: func(x: char) +/// A function that returns a character +return-char: func() -> char diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/conventions.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/conventions.wit new file mode 100644 index 0000000..014b712 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/conventions.wit @@ -0,0 +1,28 @@ +// hello 🐱 world + +kebab-case: func() + +record ludicrous-speed { + how-fast-are-you-going: u32, + i-am-going-extremely-slow: u64, +} + +foo: func(x: ludicrous-speed) +%function-with-dashes: func() +%function-with-no-weird-characters: func() + +apple: func() +apple-pear: func() +apple-pear-grape: func() +garçon: func() +hühnervögel: func() +москва: func() +東-京: func() +garçon-hühnervögel-москва-東-京: func() +a0: func() + +%explicit: func() +%explicit-kebab: func() + +// Identifiers with the same name as keywords are quoted. +%bool: func() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/empty.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/empty.wit new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/empty.wit @@ -0,0 +1 @@ + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/flags.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/flags.wit new file mode 100644 index 0000000..343cc90 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/flags.wit @@ -0,0 +1,46 @@ +flags flag1 { + b0, +} + +flags flag2 { + b0, b1, +} + +flags flag4 { + b0, b1, b2, b3, +} + +flags flag8 { + b0, b1, b2, b3, b4, b5, b6, b7, +} + +flags flag16 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, +} + +flags flag32 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, +} + +flags flag64 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, + b32, b33, b34, b35, b36, b37, b38, b39, + b40, b41, b42, b43, b44, b45, b46, b47, + b48, b49, b50, b51, b52, b53, b54, b55, + b56, b57, b58, b59, b60, b61, b62, b63, +} + +roundtrip-flag1: func(x: flag1) -> flag1 +roundtrip-flag2: func(x: flag2) -> flag2 +roundtrip-flag4: func(x: flag4) -> flag4 +roundtrip-flag8: func(x: flag8) -> flag8 +roundtrip-flag16: func(x: flag16) -> flag16 +roundtrip-flag32: func(x: flag32) -> flag32 +roundtrip-flag64: func(x: flag64) -> flag64 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/floats.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/floats.wit new file mode 100644 index 0000000..c3866d9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/floats.wit @@ -0,0 +1,4 @@ +float32-param: func(x: float32) +float64-param: func(x: float64) +float32-result: func() -> float32 +float64-result: func() -> float64 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/integers.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/integers.wit new file mode 100644 index 0000000..07fcf02 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/integers.wit @@ -0,0 +1,31 @@ +a1: func(x: u8) +a2: func(x: s8) +a3: func(x: u16) +a4: func(x: s16) +a5: func(x: u32) +a6: func(x: s32) +a7: func(x: u64) +a8: func(x: s64) + +a9: func( + p1: u8, + p2: s8, + p3: u16, + p4: s16, + p5: u32, + p6: s32, + p7: u64, + p8: s64, +) + + +r1: func() -> u8 +r2: func() -> s8 +r3: func() -> u16 +r4: func() -> s16 +r5: func() -> u32 +r6: func() -> s32 +r7: func() -> u64 +r8: func() -> s64 + +pair-ret: func() -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/lists.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/lists.wit new file mode 100644 index 0000000..2c6b526 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/lists.wit @@ -0,0 +1,76 @@ +list-u8-param: func(x: list) +list-u16-param: func(x: list) +list-u32-param: func(x: list) +list-u64-param: func(x: list) +list-s8-param: func(x: list) +list-s16-param: func(x: list) +list-s32-param: func(x: list) +list-s64-param: func(x: list) +list-float32-param: func(x: list) +list-float64-param: func(x: list) + +list-u8-ret: func() -> list +list-u16-ret: func() -> list +list-u32-ret: func() -> list +list-u64-ret: func() -> list +list-s8-ret: func() -> list +list-s16-ret: func() -> list +list-s32-ret: func() -> list +list-s64-ret: func() -> list +list-float32-ret: func() -> list +list-float64-ret: func() -> list + +tuple-list: func(x: list>) -> list> +string-list-arg: func(a: list) +string-list-ret: func() -> list +tuple-string-list: func(x: list>) -> list> +string-list: func(x: list) -> list + +record some-record { + x: string, + y: other-record, + z: list, + c1: u32, + c2: u64, + c3: s32, + c4: s64, +} +record other-record { + a1: u32, + a2: u64, + a3: s32, + a4: s64, + b: string, + c: list, +} +record-list: func(x: list) -> list +record-list-reverse: func(x: list) -> list + +variant some-variant { + a(string), + b, + c(u32), + d(list), +} +variant other-variant { + a, + b(u32), + c(string), +} +variant-list: func(x: list) -> list + +type load-store-all-sizes = list> +load-store-everything: func(a: load-store-all-sizes) -> load-store-all-sizes diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/many-arguments.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/many-arguments.wit new file mode 100644 index 0000000..745b7ce --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/many-arguments.wit @@ -0,0 +1,47 @@ +many-args: func( + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + a8: u64, + a9: u64, + a10: u64, + a11: u64, + a12: u64, + a13: u64, + a14: u64, + a15: u64, + a16: u64, + a17: u64, + a18: u64, + a19: u64, + a20: u64, +) + +record big-struct { + a1: string, + a2: string, + a3: string, + a4: string, + a5: string, + a6: string, + a7: string, + a8: string, + a9: string, + a10: string, + a11: string, + a12: string, + a13: string, + a14: string, + a15: string, + a16: string, + a17: string, + a18: string, + a19: string, + a20: string, +} + +big-argument: func(x: big-struct) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/records.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/records.wit new file mode 100644 index 0000000..34a8971 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/records.wit @@ -0,0 +1,52 @@ +tuple-arg: func(x: tuple) +tuple-result: func() -> tuple + +record empty {} + +empty-arg: func(x: empty) +empty-result: func() -> empty + +/// A record containing two scalar fields +/// that both have the same type +record scalars { + /// The first field, named a + a: u32, + /// The second field, named b + b: u32, +} + +scalar-arg: func(x: scalars) +scalar-result: func() -> scalars + +/// A record that is really just flags +/// All of the fields are bool +record really-flags { + a: bool, + b: bool, + c: bool, + d: bool, + e: bool, + f: bool, + g: bool, + h: bool, + i: bool, +} + +flags-arg: func(x: really-flags) +flags-result: func() -> really-flags + +record aggregates { + a: scalars, + b: u32, + c: empty, + d: string, + e: really-flags, +} + +aggregate-arg: func(x: aggregates) +aggregate-result: func() -> aggregates + +type tuple-typedef = tuple +type int-typedef = s32 +type tuple-typedef2 = tuple +typedef-inout: func(e: tuple-typedef2) -> s32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/resource.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/resource.wit new file mode 100644 index 0000000..3c87154 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/resource.wit @@ -0,0 +1,11 @@ +resource x + +acquire-an-x: func() -> x +receive-an-x: func(val: x) + +resource y { + static some-constructor: func() -> y + method-on-y: func() + method-with-param: func(x: u32) + method-with-result: func() -> string +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/simple-functions.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/simple-functions.wit new file mode 100644 index 0000000..196ec3a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/simple-functions.wit @@ -0,0 +1,9 @@ +f1: func() +f2: func(a: u32) +f3: func(a: u32, b: u32) + +f4: func() -> u32 +// TODO: reenable this when smw implements this +//f5: func() -> tuple +// +//f6: func(a: u32, b: u32, c: u32) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/simple-lists.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/simple-lists.wit new file mode 100644 index 0000000..f215806 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/simple-lists.wit @@ -0,0 +1,5 @@ +simple-list1: func(l: list) +simple-list2: func() -> list +// TODO: reenable this when smw implements this +// simple-list3: func(a: list, b: list) -> tuple, list> +simple-list4: func(l: list>) -> list> diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/small-anonymous.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/small-anonymous.wit new file mode 100644 index 0000000..71f9cb4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/small-anonymous.wit @@ -0,0 +1,6 @@ +enum error { + success, + failure, +} + +option-test: func() -> expected, error> diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/smoke.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/smoke.wit new file mode 100644 index 0000000..aade610 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/smoke.wit @@ -0,0 +1 @@ +y: func() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/strings.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/strings.wit new file mode 100644 index 0000000..fccaae1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/strings.wit @@ -0,0 +1,3 @@ +a: func(x: string) +b: func() -> string +c: func(a: string, b: string) -> string diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/unions.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/unions.wit new file mode 100644 index 0000000..c7d3422 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/unions.wit @@ -0,0 +1,57 @@ +/// A union of all of the integral types +union all-integers { + /// Bool is equivalent to a 1 bit integer + /// and is treated that way in some languages + bool, + u8, u16, u32, u64, + s8, s16, s32, s64 +} +union all-floats { + float32, float64 +} +union all-text { + char, string +} + +// Returns the same case as the input but with 1 added +add-one-integer: func(num: all-integers) -> all-integers +// Returns the same case as the input but with 1 added +add-one-float: func(num: all-floats) -> all-floats +// Returns the same case as the input but with the first character replaced +replace-first-char: func(text: all-text, letter: char) -> all-text + +// Returns the index of the case provided +identify-integer: func(num: all-integers) -> u8 +// Returns the index of the case provided +identify-float: func(num: all-floats) -> u8 +// Returns the index of the case provided +identify-text: func(text: all-text) -> u8 + +union duplicated-s32 { + /// The first s32 + s32, + /// The second s32 + s32, + /// The third s32 + s32 +} + +// Returns the same case as the input but with 1 added +add-one-duplicated: func(num: duplicated-s32) -> duplicated-s32 + +// Returns the index of the case provided +identify-duplicated: func(num: duplicated-s32) -> u8 + +/// A type containing numeric types that are distinct in most languages +union distinguishable-num { + /// A Floating Point Number + float64, + /// A Signed Integer + s64 +} + +// Returns the same case as the input but with 1 added +add-one-distinguishable-num: func(num: distinguishable-num) -> distinguishable-num + +// Returns the index of the case provided +identify-distinguishable-num: func(num: distinguishable-num) -> u8 \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/variants.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/variants.wit new file mode 100644 index 0000000..c32c2eb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/codegen/variants.wit @@ -0,0 +1,135 @@ +enum e1 { + a, +} + +e1-arg: func(x: e1) +e1-result: func() -> e1 + +union u1 { + u32, + float32, +} + +u1-arg: func(x: u1) +u1-result: func() -> u1 + +record empty {} + +variant v1 { + a, + b(u1), + c(e1), + d(string), + e(empty), + f, + g(u32), +} + +v1-arg: func(x: v1) +v1-result: func() -> v1 + +bool-arg: func(x: bool) +bool-result: func() -> bool + +option-arg: func( + a: option, + b: option>, + c: option, + d: option, + e: option, + f: option, + g: option>, +) +option-result: func() -> tuple< + option, + option>, + option, + option, + option, + option, + option>, +> + +variant casts1 { + a(s32), + b(float32), +} + +variant casts2 { + a(float64), + b(float32), +} + +variant casts3 { + a(float64), + b(u64), +} + +variant casts4 { + a(u32), + b(s64), +} + +variant casts5 { + a(float32), + b(s64), +} + +variant casts6 { + a(tuple), + b(tuple), +} + +casts: func( + a: casts1, + b: casts2, + c: casts3, + d: casts4, + e: casts5, + f: casts6, +) -> tuple< + casts1, + casts2, + casts3, + casts4, + casts5, + casts6, +> + +expected-arg: func( + a: expected, + b: expected, + c: expected, + d: expected, tuple<>>, + e: expected, + f: expected>, +) +expected-result: func() -> tuple< + expected, + expected, + expected, + expected, tuple<>>, + expected, + expected>, +> + +enum my-errno { + bad1, + bad2, +} + +return-expected-sugar: func() -> expected +return-expected-sugar2: func() -> expected +return-expected-sugar3: func() -> expected +return-expected-sugar4: func() -> expected, my-errno> +return-option-sugar: func() -> option +return-option-sugar2: func() -> option + +expected-simple: func() -> expected + +record is-clone { + v1: v1, +} + +is-clone-arg: func(a: is-clone) +is-clone-return: func() -> is-clone diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/exports.wit new file mode 100644 index 0000000..c1de3e3 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/exports.wit @@ -0,0 +1,4 @@ +thunk: async func() +allocated-bytes: func() -> u32 + +test-concurrent: async func() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/host.ts new file mode 100644 index 0000000..4381c7f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/host.ts @@ -0,0 +1,106 @@ +import { addImportsToImports, Imports } from "./imports.js"; +import { Exports } from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; +// @ts-ignore +import * as assert from 'assert'; + +function promiseChannel(): [Promise, () => void] { + let resolveCallback = null; + const promise = new Promise((resolve, reject) => resolveCallback = resolve); + // @ts-ignore + return [promise, resolveCallback]; +} + +async function run() { + const importObj = {}; + let hit = false; + + const [concurrentPromise, resolveConcurrent] = promiseChannel(); + const [unblockConcurrent1, resolveUnblockConcurrent1] = promiseChannel(); + const [unblockConcurrent2, resolveUnblockConcurrent2] = promiseChannel(); + const [unblockConcurrent3, resolveUnblockConcurrent3] = promiseChannel(); + + const imports: Imports = { + async thunk() { + if (hit) { + console.log('second time in thunk, throwing an error'); + throw new Error('catch me'); + } else { + console.log('first time in thunk'); + await some_helper(); + console.log('waited on the helper, returning from host thunk'); + hit = true; + } + }, + + async concurrent1(val) { + console.log('wasm called concurrent1'); + assert.equal(val, 1); + resolveUnblockConcurrent1(); + console.log('concurrent1 to reenter back into the host'); + await concurrentPromise; + console.log('concurrent1 returning to wasm'); + return 11; + }, + async concurrent2(val) { + console.log('wasm called concurrent2'); + assert.equal(val, 2); + resolveUnblockConcurrent2(); + console.log('concurrent2 to reenter back into the host'); + await concurrentPromise; + console.log('concurrent2 returning to wasm'); + return 12; + }, + async concurrent3(val) { + console.log('wasm called concurrent3'); + assert.equal(val, 3); + resolveUnblockConcurrent3(); + console.log('concurrent3 to reenter back into the host'); + await concurrentPromise; + console.log('concurrent3 returning to wasm'); + return 13; + }, + }; + let instance: WebAssembly.Instance; + addImportsToImports(importObj, imports, name => instance.exports[name]); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + instance = wasm.instance; + + const initBytes = wasm.allocatedBytes(); + console.log("calling initial async function"); + await wasm.thunk(); + assert.ok(hit, "import not called"); + assert.equal(initBytes, wasm.allocatedBytes()); + + // Make sure that exceptions on the host make their way back to whomever's + // doing the actual `await` + try { + console.log('executing thunk export a second time'); + await wasm.thunk(); + throw new Error('expected an error to get thrown'); + } catch (e) { + const err = e as Error; + console.log('caught error with', err.message); + assert.equal(err.message, 'catch me'); + } + + console.log('entering wasm'); + const concurrentWasm = wasm.testConcurrent(); + console.log('waiting for wasm to enter the host'); + await unblockConcurrent1; + await unblockConcurrent2; + await unblockConcurrent3; + console.log('allowing host functions to finish'); + resolveConcurrent(); + console.log('waiting on host functions'); + await concurrentWasm; + console.log('concurrent wasm finished'); +} + +async function some_helper() {} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/imports.wit new file mode 100644 index 0000000..aefd084 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/imports.wit @@ -0,0 +1,5 @@ +thunk: async func() + +concurrent1: async func(a: u32) -> u32 +concurrent2: async func(a: u32) -> u32 +concurrent3: async func(a: u32) -> u32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/wasm.rs new file mode 100644 index 0000000..26f9dba --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/async_functions/wasm.rs @@ -0,0 +1,23 @@ +wit_bindgen_rust::import!("../../tests/runtime/async_functions/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/async_functions/exports.wit"); + +struct Exports; + +#[wit_bindgen_rust::async_trait(?Send)] +impl exports::Exports for Exports { + fn allocated_bytes() -> u32 { + test_rust_wasm::get() as u32 + } + + async fn thunk() { + imports::thunk().await; + } + + async fn test_concurrent() { + let a1 = imports::concurrent1(1); + let a2 = imports::concurrent2(2); + let a3 = imports::concurrent3(3); + + assert_eq!(futures_util::join!(a2, a3, a1), (12, 13, 11)); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/exports.wit new file mode 100644 index 0000000..bc751b9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/exports.wit @@ -0,0 +1,31 @@ +test-imports: func() + +record list-in-record1 { a: string } +record list-in-record2 { a: string } +record list-in-record3 { a: string } +record list-in-record4 { a: string } +type list-in-alias = list-in-record4 + +list-in-record1: func(a: list-in-record1) +list-in-record2: func() -> list-in-record2 +list-in-record3: func(a: list-in-record3) -> list-in-record3 +list-in-record4: func(a: list-in-alias) -> list-in-alias + +type list-in-variant1-v1 = option +type list-in-variant1-v2 = expected +union list-in-variant1-v3 { string, float32 } +list-in-variant1: func(a: list-in-variant1-v1, b: list-in-variant1-v2, c: list-in-variant1-v3) + +type list-in-variant2 = option +list-in-variant2: func() -> list-in-variant2 + +type list-in-variant3 = option +list-in-variant3: func(a: list-in-variant3) -> list-in-variant3 + +enum my-errno { success, a, b } +errno-result: func() -> expected + +type list-typedef = string +type list-typedef2 = list +type list-typedef3 = list +list-typedefs: func(a: list-typedef, c: list-typedef3) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.py new file mode 100644 index 0000000..6fa7afe --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.py @@ -0,0 +1,86 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +from typing import Tuple, List +import exports.bindings as e +import imports.bindings as i +import sys +import wasmtime + +class MyImports: + def list_in_record1(self, a: i.ListInRecord1) -> None: + pass + + def list_in_record2(self) -> i.ListInRecord2: + return i.ListInRecord2('list_in_record2') + + def list_in_record3(self, a: i.ListInRecord3) -> i.ListInRecord3: + assert(a.a == 'list_in_record3 input') + return i.ListInRecord3('list_in_record3 output') + + def list_in_record4(self, a: i.ListInAlias) -> i.ListInAlias: + assert(a.a == 'input4') + return i.ListInRecord4('result4') + + def list_in_variant1(self, a: i.ListInVariant1V1, b: i.ListInVariant1V2, c: i.ListInVariant1V3) -> None: + assert(a == 'foo') + assert(b == i.Err('bar')) + assert(c == 'baz') + + def list_in_variant2(self) -> i.ListInVariant2: + return 'list_in_variant2' + + def list_in_variant3(self, a: i.ListInVariant3) -> i.ListInVariant3: + assert(a == 'input3') + return 'output3' + + def errno_result(self) -> i.Expected[None, i.MyErrno]: + return i.Err(i.MyErrno.B) + + def list_typedefs(self, a: i.ListTypedef, c: i.ListTypedef3) -> Tuple[i.ListTypedef2, i.ListTypedef3]: + assert(a == 'typedef1') + assert(c == ['typedef2']) + return (b'typedef3', ['typedef4']) + + def list_of_variants(self, a: List[bool], b: List[i.Expected[None, None]], c: List[i.MyErrno]) -> Tuple[List[bool], List[i.Expected[None, None]], List[i.MyErrno]]: + assert(a == [True, False]) + assert(b == [i.Ok(None), i.Err(None)]) + assert(c == [i.MyErrno.SUCCESS, i.MyErrno.A]) + return ( + [False, True], + [i.Err(None), i.Ok(None)], + [i.MyErrno.A, i.MyErrno.B], + ) + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + wasm.test_imports(store) + wasm.list_in_record1(store, e.ListInRecord1("list_in_record1")) + assert(wasm.list_in_record2(store) == e.ListInRecord2(a="list_in_record2")) + + assert(wasm.list_in_record3(store, e.ListInRecord3("list_in_record3 input")).a == "list_in_record3 output") + assert(wasm.list_in_record4(store, e.ListInRecord4("input4")).a == "result4") + + wasm.list_in_variant1(store, "foo", e.Err("bar"), 'baz') + assert(wasm.list_in_variant2(store) == "list_in_variant2") + assert(wasm.list_in_variant3(store, "input3") == "output3") + + assert(isinstance(wasm.errno_result(store), e.Err)) + + r1, r2 = wasm.list_typedefs(store, "typedef1", ["typedef2"]) + assert(r1 == b'typedef3') + assert(r2 == ['typedef4']) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.rs new file mode 100644 index 0000000..8b953e9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.rs @@ -0,0 +1,160 @@ +use anyhow::Result; + +wit_bindgen_wasmtime::export!("../../tests/runtime/flavorful/imports.wit"); + +use imports::*; + +#[derive(Default)] +pub struct MyImports; + +impl Imports for MyImports { + fn list_in_record1(&mut self, ty: ListInRecord1<'_>) { + assert_eq!(ty.a, "list_in_record1"); + } + + fn list_in_record2(&mut self) -> ListInRecord2 { + ListInRecord2 { + a: "list_in_record2".to_string(), + } + } + + fn list_in_record3(&mut self, a: ListInRecord3Param<'_>) -> ListInRecord3Result { + assert_eq!(a.a, "list_in_record3 input"); + ListInRecord3Result { + a: "list_in_record3 output".to_string(), + } + } + + fn list_in_record4(&mut self, a: ListInAliasParam<'_>) -> ListInAliasResult { + assert_eq!(a.a, "input4"); + ListInRecord4Result { + a: "result4".to_string(), + } + } + + fn list_in_variant1( + &mut self, + a: ListInVariant1V1<'_>, + b: ListInVariant1V2<'_>, + c: ListInVariant1V3<'_>, + ) { + assert_eq!(a.unwrap(), "foo"); + assert_eq!(b.unwrap_err(), "bar"); + match c { + ListInVariant1V3::String(s) => assert_eq!(s, "baz"), + ListInVariant1V3::F32(_) => panic!(), + } + } + + fn list_in_variant2(&mut self) -> Option { + Some("list_in_variant2".to_string()) + } + + fn list_in_variant3(&mut self, a: ListInVariant3Param<'_>) -> Option { + assert_eq!(a.unwrap(), "input3"); + Some("output3".to_string()) + } + + fn errno_result(&mut self) -> Result<(), MyErrno> { + MyErrno::A.to_string(); + format!("{:?}", MyErrno::A); + fn assert_error() {} + assert_error::(); + Err(MyErrno::B) + } + + fn list_typedefs( + &mut self, + a: ListTypedef<'_>, + b: ListTypedef3Param<'_>, + ) -> (ListTypedef2, ListTypedef3Result) { + assert_eq!(a, "typedef1"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef2"); + (b"typedef3".to_vec(), vec!["typedef4".to_string()]) + } + + fn list_of_variants( + &mut self, + bools: Vec, + results: Vec>, + enums: Vec, + ) -> (Vec, Vec>, Vec) { + assert_eq!(bools, [true, false]); + assert_eq!(results, [Ok(()), Err(())]); + assert_eq!(enums, [MyErrno::Success, MyErrno::A]); + ( + vec![false, true], + vec![Err(()), Ok(())], + vec![MyErrno::A, MyErrno::B], + ) + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/flavorful/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let (exports, mut store) = crate::instantiate( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut MyImports { &mut cx.imports }), + |store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports), + )?; + + exports.test_imports(&mut store)?; + + exports.list_in_record1( + &mut store, + ListInRecord1 { + a: "list_in_record1", + }, + )?; + assert_eq!(exports.list_in_record2(&mut store)?.a, "list_in_record2"); + + assert_eq!( + exports + .list_in_record3( + &mut store, + ListInRecord3Param { + a: "list_in_record3 input" + } + )? + .a, + "list_in_record3 output" + ); + + assert_eq!( + exports + .list_in_record4(&mut store, ListInAliasParam { a: "input4" })? + .a, + "result4" + ); + + exports.list_in_variant1( + &mut store, + Some("foo"), + Err("bar"), + ListInVariant1V3::String("baz"), + )?; + assert_eq!( + exports.list_in_variant2(&mut store)?, + Some("list_in_variant2".to_string()) + ); + assert_eq!( + exports.list_in_variant3(&mut store, Some("input3"))?, + Some("output3".to_string()) + ); + + assert!(exports.errno_result(&mut store)?.is_err()); + MyErrno::A.to_string(); + format!("{:?}", MyErrno::A); + fn assert_error() {} + assert_error::(); + + let (a, b) = exports.list_typedefs(&mut store, "typedef1", &["typedef2"])?; + assert_eq!(a, b"typedef3"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef4"); + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.ts new file mode 100644 index 0000000..1548505 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/host.ts @@ -0,0 +1,85 @@ +import { addImportsToImports, Imports, MyErrno } from "./imports.js"; +import { Exports } from "./exports.js"; +import * as exports from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; +// @ts-ignore +import * as assert from 'assert'; + +async function run() { + const importObj = {}; + const imports: Imports = { + listInRecord1(x) {}, + listInRecord2() { return { a: 'list_in_record2' }; }, + listInRecord3(x) { + assert.strictEqual(x.a, 'list_in_record3 input'); + return { a: 'list_in_record3 output' }; + }, + listInRecord4(x) { + assert.strictEqual(x.a, 'input4'); + return { a: 'result4' }; + }, + listInVariant1(a, b, c) { + assert.strictEqual(a, 'foo'); + assert.deepStrictEqual(b, { tag: 'err', val: 'bar' }); + assert.deepStrictEqual(c, { tag: 0, val: 'baz' }); + }, + listInVariant2() { return 'list_in_variant2'; }, + listInVariant3(x) { + assert.strictEqual(x, 'input3'); + return 'output3'; + }, + + errnoResult() { return { tag: 'err', val: "b" }; }, + listTypedefs(x, y) { + assert.strictEqual(x, 'typedef1'); + assert.deepStrictEqual(y, ['typedef2']); + return [(new TextEncoder).encode('typedef3'), ['typedef4']]; + }, + + listOfVariants(bools, results, enums) { + assert.deepStrictEqual(bools, [true, false]); + assert.deepStrictEqual(results, [{ tag: 'ok', val: undefined }, { tag: 'err', val: undefined }]); + assert.deepStrictEqual(enums, ["success", "a"]); + return [ + [false, true], + [{ tag: 'err', val: undefined }, { tag: 'ok', val: undefined }], + ["a", "b"], + ]; + }, + }; + let instance: WebAssembly.Instance; + addImportsToImports(importObj, imports, name => instance.exports[name]); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + instance = wasm.instance; + + wasm.testImports(); + wasm.listInRecord1({ a: "list_in_record1" }); + assert.deepStrictEqual(wasm.listInRecord2(), { a: "list_in_record2" }); + + assert.deepStrictEqual( + wasm.listInRecord3({ a: "list_in_record3 input" }), + { a: "list_in_record3 output" }, + ); + + assert.deepStrictEqual( + wasm.listInRecord4({ a: "input4" }), + { a: "result4" }, + ); + + wasm.listInVariant1("foo", { tag: 'err', val: 'bar' }, { tag: 0, val: 'baz' }); + + assert.deepStrictEqual(wasm.listInVariant2(), "list_in_variant2"); + assert.deepStrictEqual(wasm.listInVariant3("input3"), "output3"); + + assert.deepStrictEqual(wasm.errnoResult().tag, 'err'); + + const [r1, r2] = wasm.listTypedefs("typedef1", ["typedef2"]); + assert.deepStrictEqual(r1, (new TextEncoder()).encode('typedef3')); + assert.deepStrictEqual(r2, ['typedef4']); +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/imports.wit new file mode 100644 index 0000000..a3deecf --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/imports.wit @@ -0,0 +1,31 @@ +record list-in-record1 { a: string } +record list-in-record2 { a: string } +record list-in-record3 { a: string } +record list-in-record4 { a: string } +type list-in-alias = list-in-record4 + +list-in-record1: func(a: list-in-record1) +list-in-record2: func() -> list-in-record2 +list-in-record3: func(a: list-in-record3) -> list-in-record3 +list-in-record4: func(a: list-in-alias) -> list-in-alias + +type list-in-variant1-v1 = option +type list-in-variant1-v2 = expected +union list-in-variant1-v3 { string, float32 } +list-in-variant1: func(a: list-in-variant1-v1, b: list-in-variant1-v2, c: list-in-variant1-v3) + +type list-in-variant2 = option +list-in-variant2: func() -> list-in-variant2 + +type list-in-variant3 = option +list-in-variant3: func(a: list-in-variant3) -> list-in-variant3 + +enum my-errno { success, a, b } +errno-result: func() -> expected + +type list-typedef = string +type list-typedef2 = list +type list-typedef3 = list +list-typedefs: func(a: list-typedef, c: list-typedef3) -> tuple + +list-of-variants: func(a: list, b: list>, c: list) -> tuple, list>, list> diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/wasm.c new file mode 100644 index 0000000..522c6ca --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/wasm.c @@ -0,0 +1,197 @@ +#include +#include +#include +#include +#include + +void exports_test_imports() { + { + imports_list_in_record1_t a; + imports_string_set(&a.a, "list_in_record1"); + imports_list_in_record1(&a); + + imports_list_in_record2_t b; + imports_list_in_record2(&b); + assert(memcmp(b.a.ptr, "list_in_record2", b.a.len) == 0); + imports_list_in_record2_free(&b); + } + + { + imports_list_in_record3_t a, b; + imports_string_set(&a.a, "list_in_record3 input"); + imports_list_in_record3(&a, &b); + assert(memcmp(b.a.ptr, "list_in_record3 output", b.a.len) == 0); + imports_list_in_record3_free(&b); + } + + { + imports_list_in_record4_t a, b; + imports_string_set(&a.a, "input4"); + imports_list_in_record4(&a, &b); + assert(memcmp(b.a.ptr, "result4", b.a.len) == 0); + imports_list_in_record4_free(&b); + } + + { + imports_list_in_variant1_v1_t a; + imports_list_in_variant1_v2_t b; + imports_list_in_variant1_v3_t c; + a.is_some = true; + imports_string_set(&a.val, "foo"); + b.is_err = true; + imports_string_set(&b.val.err, "bar"); + c.tag = 0; + imports_string_set(&c.val.f0, "baz"); + imports_list_in_variant1(&a, &b, &c); + } + + { + imports_string_t a; + assert(imports_list_in_variant2(&a)); + assert(memcmp(a.ptr, "list_in_variant2", a.len) == 0); + imports_string_free(&a); + } + + { + imports_list_in_variant3_t a; + a.is_some = true; + imports_string_set(&a.val, "input3"); + imports_string_t b; + assert(imports_list_in_variant3(&a, &b)); + assert(memcmp(b.ptr, "output3", b.len) == 0); + imports_string_free(&b); + } + + assert(imports_errno_result() == IMPORTS_MY_ERRNO_B); + + { + imports_string_t a; + imports_string_set(&a, "typedef1"); + imports_string_t b_str; + imports_string_set(&b_str, "typedef2"); + imports_list_typedef3_t b; + b.ptr = &b_str; + b.len = 1; + imports_list_typedef2_t c; + imports_list_typedef3_t d; + imports_list_typedefs(&a, &b, &c, &d); + + assert(memcmp(c.ptr, "typedef3", c.len) == 0); + assert(d.len == 1); + assert(memcmp(d.ptr[0].ptr, "typedef4", d.ptr[0].len) == 0); + + imports_list_typedef2_free(&c); + imports_list_typedef3_free(&d); + } + + { + imports_list_bool_t a; + bool a_val[] = {true, false}; + a.ptr = a_val; + a.len = 2; + + imports_list_expected_unit_unit_t b; + imports_expected_unit_unit_t b_val[2]; + b_val[0].is_err = false; + b_val[1].is_err = true; + b.ptr = b_val; + b.len = 2; + + imports_list_my_errno_t c; + imports_my_errno_t c_val[2]; + c_val[0] = IMPORTS_MY_ERRNO_SUCCESS; + c_val[1] = IMPORTS_MY_ERRNO_A; + c.ptr = c_val; + c.len = 2; + + imports_list_bool_t d; + imports_list_expected_unit_unit_t e; + imports_list_my_errno_t f; + imports_list_of_variants(&a, &b, &c, &d, &e, &f); + + assert(d.len == 2); + assert(d.ptr[0] == false); + assert(d.ptr[1] == true); + + assert(e.len == 2); + assert(e.ptr[0].is_err == true); + assert(e.ptr[1].is_err == false); + + assert(f.len == 2); + assert(f.ptr[0] == IMPORTS_MY_ERRNO_A); + assert(f.ptr[1] == IMPORTS_MY_ERRNO_B); + + imports_list_bool_free(&d); + imports_list_expected_unit_unit_free(&e); + imports_list_my_errno_free(&f); + } +} + +void exports_list_in_record1(exports_list_in_record1_t *a) { + assert(memcmp(a->a.ptr, "list_in_record1", a->a.len) == 0); + exports_list_in_record1_free(a); +} + +void exports_list_in_record2(exports_list_in_record2_t *ret0) { + exports_string_dup(&ret0->a, "list_in_record2"); +} + +void exports_list_in_record3(exports_list_in_record3_t *a, exports_list_in_record3_t *ret0) { + assert(memcmp(a->a.ptr, "list_in_record3 input", a->a.len) == 0); + exports_list_in_record3_free(a); + exports_string_dup(&ret0->a, "list_in_record3 output"); +} + +void exports_list_in_record4(exports_list_in_alias_t *a, exports_list_in_alias_t *ret0) { + assert(memcmp(a->a.ptr, "input4", a->a.len) == 0); + exports_list_in_alias_free(a); + exports_string_dup(&ret0->a, "result4"); +} + +void exports_list_in_variant1(exports_list_in_variant1_v1_t *a, exports_list_in_variant1_v2_t *b, exports_list_in_variant1_v3_t *c) { + assert(a->is_some); + assert(memcmp(a->val.ptr, "foo", a->val.len) == 0); + exports_list_in_variant1_v1_free(a); + + assert(b->is_err); + assert(memcmp(b->val.err.ptr, "bar", b->val.err.len) == 0); + exports_list_in_variant1_v2_free(b); + + assert(c->tag == 0); + assert(memcmp(c->val.f0.ptr, "baz", c->val.f0.len) == 0); + exports_list_in_variant1_v3_free(c); +} + +bool exports_list_in_variant2(exports_string_t *ret0) { + exports_string_dup(ret0, "list_in_variant2"); + return true; +} + +bool exports_list_in_variant3(exports_list_in_variant3_t *a, exports_string_t *ret0) { + assert(a->is_some); + assert(memcmp(a->val.ptr, "input3", a->val.len) == 0); + exports_list_in_variant3_free(a); + exports_string_dup(ret0, "output3"); + return true; +} + +exports_my_errno_t exports_errno_result(void) { + return EXPORTS_MY_ERRNO_B; +} + +void exports_list_typedefs(exports_list_typedef_t *a, exports_list_typedef3_t *c, exports_list_typedef2_t *ret0, exports_list_typedef3_t *ret1) { + assert(memcmp(a->ptr, "typedef1", a->len) == 0); + exports_list_typedef_free(a); + + assert(c->len == 1); + assert(memcmp(c->ptr[0].ptr, "typedef2", c->ptr[0].len) == 0); + exports_list_typedef3_free(c); + + ret0->ptr = malloc(8); + ret0->len = 8; + memcpy(ret0->ptr, "typedef3", 8); + + ret1->ptr = malloc(sizeof(exports_string_t)); + ret1->len = 1; + exports_string_dup(&ret1->ptr[0], "typedef4"); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/wasm.rs new file mode 100644 index 0000000..36d3bc8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/flavorful/wasm.rs @@ -0,0 +1,116 @@ +wit_bindgen_rust::import!("../../tests/runtime/flavorful/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/flavorful/exports.wit"); + +use exports::*; + +struct Exports; + +impl exports::Exports for Exports { + fn test_imports() { + use imports::*; + + let _guard = test_rust_wasm::guard(); + + list_in_record1(ListInRecord1 { + a: "list_in_record1", + }); + assert_eq!(list_in_record2().a, "list_in_record2"); + + assert_eq!( + list_in_record3(ListInRecord3Param { + a: "list_in_record3 input" + }) + .a, + "list_in_record3 output" + ); + + assert_eq!( + list_in_record4(ListInAliasParam { a: "input4" }).a, + "result4" + ); + + list_in_variant1(Some("foo"), Err("bar"), ListInVariant1V3::String("baz")); + assert_eq!(list_in_variant2(), Some("list_in_variant2".to_string())); + assert_eq!( + list_in_variant3(Some("input3")), + Some("output3".to_string()) + ); + + assert!(errno_result().is_err()); + MyErrno::A.to_string(); + format!("{:?}", MyErrno::A); + fn assert_error() {} + assert_error::(); + + let (a, b) = list_typedefs("typedef1", &["typedef2"]); + assert_eq!(a, b"typedef3"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef4"); + + let (a, b, c) = list_of_variants( + &[true, false], + &[Ok(()), Err(())], + &[MyErrno::Success, MyErrno::A], + ); + assert_eq!(a, [false, true]); + assert_eq!(b, [Err(()), Ok(())]); + assert_eq!(c, [MyErrno::A, MyErrno::B]); + } + + fn list_in_record1(ty: ListInRecord1) { + assert_eq!(ty.a, "list_in_record1"); + } + + fn list_in_record2() -> ListInRecord2 { + ListInRecord2 { + a: "list_in_record2".to_string(), + } + } + + fn list_in_record3(a: ListInRecord3) -> ListInRecord3 { + assert_eq!(a.a, "list_in_record3 input"); + ListInRecord3 { + a: "list_in_record3 output".to_string(), + } + } + + fn list_in_record4(a: ListInAlias) -> ListInAlias { + assert_eq!(a.a, "input4"); + ListInRecord4 { + a: "result4".to_string(), + } + } + + fn list_in_variant1(a: ListInVariant1V1, b: ListInVariant1V2, c: ListInVariant1V3) { + assert_eq!(a.unwrap(), "foo"); + assert_eq!(b.unwrap_err(), "bar"); + match c { + ListInVariant1V3::String(s) => assert_eq!(s, "baz"), + ListInVariant1V3::F32(_) => panic!(), + } + } + + fn list_in_variant2() -> Option { + Some("list_in_variant2".to_string()) + } + + fn list_in_variant3(a: ListInVariant3) -> Option { + assert_eq!(a.unwrap(), "input3"); + Some("output3".to_string()) + } + + fn errno_result() -> Result<(), MyErrno> { + MyErrno::A.to_string(); + format!("{:?}", MyErrno::A); + fn assert_error() {} + assert_error::(); + Err(MyErrno::B) + } + + fn list_typedefs(a: ListTypedef, b: ListTypedef3) -> (ListTypedef2, ListTypedef3) { + assert_eq!(a, "typedef1"); + assert_eq!(b.len(), 1); + assert_eq!(b[0], "typedef2"); + (b"typedef3".to_vec(), vec!["typedef4".to_string()]) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/exports.wit new file mode 100644 index 0000000..2a8602d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/exports.wit @@ -0,0 +1,53 @@ +test-imports: func() + +resource wasm-state +resource wasm-state2 + +wasm-state-create: func() -> wasm-state +wasm-state-get-val: func(a: wasm-state) -> u32 + +wasm-state2-create: func() -> wasm-state2 +wasm-state2-saw-close: func() -> bool +two-wasm-states: func(a: wasm-state, b: wasm-state2) -> tuple + +record wasm-state-param-record { a: wasm-state2 } +wasm-state2-param-record: func(a: wasm-state-param-record) + +type wasm-state-param-tuple = tuple +wasm-state2-param-tuple: func(a: wasm-state-param-tuple) + +type wasm-state-param-option = option +wasm-state2-param-option: func(a: wasm-state-param-option) + +type wasm-state-param-result = expected +wasm-state2-param-result: func(a: wasm-state-param-result) + +union wasm-state-param-variant { wasm-state2, u32 } +wasm-state2-param-variant: func(a: wasm-state-param-variant) + +wasm-state2-param-list: func(a: list) + + +record wasm-state-result-record { a: wasm-state2 } +wasm-state2-result-record: func() -> wasm-state-result-record + +type wasm-state-result-tuple = tuple +wasm-state2-result-tuple: func() -> wasm-state-result-tuple + +type wasm-state-result-option = option +wasm-state2-result-option: func() -> wasm-state-result-option + +type wasm-state-result-result = expected +wasm-state2-result-result: func() -> wasm-state-result-result + +union wasm-state-result-variant { wasm-state2, u32 } +wasm-state2-result-variant: func() -> wasm-state-result-variant + +wasm-state2-result-list: func() -> list + +resource markdown { + static create: func() -> option + append: func(buf: string) + render: func() -> string +} + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.py new file mode 100644 index 0000000..ebd7912 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.py @@ -0,0 +1,180 @@ +from dataclasses import dataclass +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +from typing import Tuple, List +import exports.bindings as e +import imports.bindings as i +import sys +import wasmtime + +@dataclass +class HostState(i.HostState): + val: int + + def __init__(self, val: int) -> None: + self.val = val + + +HOST_STATE2_CLOSED = False + + +@dataclass +class HostState2(i.HostState2): + val: int + + def __init__(self, val: int) -> None: + self.val = val + + def drop(self) -> None: + global HOST_STATE2_CLOSED + HOST_STATE2_CLOSED = True + + +@dataclass +class Markdown(i.Markdown2): + buf: str = '' + + def append(self, data: str) -> None: + self.buf += data + + def render(self) -> str: + return self.buf.replace('red', 'green') + + +class OddName(i.OddName): + def frob_the_odd(self) -> None: + pass + + +class MyImports: + def host_state_create(self) -> i.HostState: + return HostState(100) + + def host_state_get(self, a: i.HostState) -> int: + assert(isinstance(a, HostState)) + return a.val + + def host_state2_create(self) -> i.HostState2: + return HostState2(101) + + def host_state2_saw_close(self) -> bool: + return HOST_STATE2_CLOSED + + def two_host_states(self, a: i.HostState, b: i.HostState2) -> Tuple[i.HostState, i.HostState2]: + return (b, a) + + def host_state2_param_record(self, a: i.HostStateParamRecord) -> None: + pass + + def host_state2_param_tuple(self, a: i.HostStateParamTuple) -> None: + pass + + def host_state2_param_option(self, a: i.HostStateParamOption) -> None: + pass + + def host_state2_param_result(self, a: i.HostStateParamResult) -> None: + pass + + def host_state2_param_variant(self, a: i.HostStateParamVariant) -> None: + pass + + def host_state2_param_list(self, a: List[i.HostState2]) -> None: + pass + + def host_state2_result_record(self) -> i.HostStateResultRecord: + return i.HostStateResultRecord(HostState(2)) + + def host_state2_result_tuple(self) -> i.HostStateResultTuple: + return (HostState(2),) + + def host_state2_result_option(self) -> i.HostStateResultOption: + return HostState(2) + + def host_state2_result_result(self) -> i.HostStateResultResult: + return i.Ok(HostState2(2)) + + def host_state2_result_variant(self) -> i.HostStateResultVariant: + return HostState2(2) + + def host_state2_result_list(self) -> List[i.HostState2]: + return [HostState2(2), HostState2(5)] + + def markdown2_create(self) -> i.Markdown2: + return Markdown() + + def odd_name_create(self) -> i.OddName: + return OddName() + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + wasm.test_imports(store) + + # Param/result of a handle works in a simple fashion + s: e.WasmState = wasm.wasm_state_create(store) + assert(wasm.wasm_state_get_val(store, s) == 100) + + # Deterministic destruction is possible + assert(wasm.wasm_state2_saw_close(store) == False) + s2: e.WasmState2 = wasm.wasm_state2_create(store) + assert(wasm.wasm_state2_saw_close(store) == False) + s2.drop(store) + assert(wasm.wasm_state2_saw_close(store) == True) + + arg1 = wasm.wasm_state_create(store) + arg2 = wasm.wasm_state2_create(store) + c, d = wasm.two_wasm_states(store, arg1, arg2) + arg1.drop(store) + arg2.drop(store) + + wasm.wasm_state2_param_record(store, e.WasmStateParamRecord(d)) + wasm.wasm_state2_param_tuple(store, (d,)) + wasm.wasm_state2_param_option(store, d) + wasm.wasm_state2_param_option(store, None) + wasm.wasm_state2_param_result(store, e.Ok(d)) + wasm.wasm_state2_param_result(store, e.Err(2)) + wasm.wasm_state2_param_variant(store, d) + wasm.wasm_state2_param_variant(store, 2) + wasm.wasm_state2_param_list(store, []) + wasm.wasm_state2_param_list(store, [d]) + wasm.wasm_state2_param_list(store, [d, d]) + + c.drop(store) + d.drop(store) + + wasm.wasm_state2_result_record(store).a.drop(store) + wasm.wasm_state2_result_tuple(store)[0].drop(store) + opt = wasm.wasm_state2_result_option(store) + assert(opt is not None) + opt.drop(store) + result = wasm.wasm_state2_result_result(store) + assert(isinstance(result, e.Ok)) + result.value.drop(store) + variant = wasm.wasm_state2_result_variant(store) + print(variant) + assert(isinstance(variant, e.WasmState2)) + variant.drop(store) + for val in wasm.wasm_state2_result_list(store): + val.drop(store) + + s.drop(store) + + md = e.Markdown.create(store, wasm) + if md: + md.append(store, "red is the best color") + assert(md.render(store) == "green is the best color") + md.drop(store) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.rs new file mode 100644 index 0000000..a5313fb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.rs @@ -0,0 +1,156 @@ +wit_bindgen_wasmtime::export!("../../tests/runtime/handles/imports.wit"); + +use anyhow::Result; +use imports::*; +use std::cell::RefCell; + +#[derive(Default)] +pub struct MyImports { + host_state2_closed: bool, +} + +#[derive(Debug)] +pub struct SuchState(u32); + +#[derive(Default, Debug)] +pub struct Markdown { + buf: RefCell, +} + +impl Imports for MyImports { + type HostState = SuchState; + type HostState2 = (); + type Markdown2 = Markdown; + type OddName = (); + + fn host_state_create(&mut self) -> SuchState { + SuchState(100) + } + + fn host_state_get(&mut self, state: &SuchState) -> u32 { + state.0 + } + + fn host_state2_create(&mut self) {} + + fn host_state2_saw_close(&mut self) -> bool { + self.host_state2_closed + } + + fn drop_host_state2(&mut self, _state: ()) { + self.host_state2_closed = true; + } + + fn two_host_states(&mut self, _a: &SuchState, _b: &()) -> (SuchState, ()) { + (SuchState(2), ()) + } + + fn host_state2_param_record(&mut self, _a: HostStateParamRecord<'_, Self>) {} + fn host_state2_param_tuple(&mut self, _a: (&'_ (),)) {} + fn host_state2_param_option(&mut self, _a: Option<&'_ ()>) {} + fn host_state2_param_result(&mut self, _a: Result<&'_ (), u32>) {} + fn host_state2_param_variant(&mut self, _a: HostStateParamVariant<'_, Self>) {} + fn host_state2_param_list(&mut self, _a: Vec<&()>) {} + + fn host_state2_result_record(&mut self) -> HostStateResultRecord { + HostStateResultRecord { a: () } + } + fn host_state2_result_tuple(&mut self) -> ((),) { + ((),) + } + fn host_state2_result_option(&mut self) -> Option<()> { + Some(()) + } + fn host_state2_result_result(&mut self) -> Result<(), u32> { + Ok(()) + } + fn host_state2_result_variant(&mut self) -> HostStateResultVariant { + HostStateResultVariant::HostState2(()) + } + fn host_state2_result_list(&mut self) -> Vec<()> { + vec![(), ()] + } + + fn markdown2_create(&mut self) -> Markdown { + Markdown::default() + } + + fn markdown2_append(&mut self, md: &Markdown, buf: &str) { + md.buf.borrow_mut().push_str(buf); + } + + fn markdown2_render(&mut self, md: &Markdown) -> String { + md.buf.borrow().replace("red", "green") + } + + fn odd_name_create(&mut self) {} + fn odd_name_frob_the_odd(&mut self, _: &()) {} +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/handles/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let (exports, mut store) = crate::instantiate( + wasm, + |linker| { + imports::add_to_linker( + linker, + |cx: &mut crate::Context<(MyImports, imports::ImportsTables), _>| { + (&mut cx.imports.0, &mut cx.imports.1) + }, + ) + }, + |store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports), + )?; + + exports.test_imports(&mut store)?; + + let s: WasmState = exports.wasm_state_create(&mut store)?; + assert_eq!(exports.wasm_state_get_val(&mut store, &s)?, 100); + exports.drop_wasm_state(&mut store, s)?; + + assert_eq!(exports.wasm_state2_saw_close(&mut store)?, false); + let s: WasmState2 = exports.wasm_state2_create(&mut store)?; + assert_eq!(exports.wasm_state2_saw_close(&mut store)?, false); + exports.drop_wasm_state2(&mut store, s)?; + assert_eq!(exports.wasm_state2_saw_close(&mut store)?, true); + + let a = exports.wasm_state_create(&mut store)?; + let b = exports.wasm_state2_create(&mut store)?; + let (s1, s2) = exports.two_wasm_states(&mut store, &a, &b)?; + exports.drop_wasm_state(&mut store, a)?; + exports.drop_wasm_state(&mut store, s1)?; + exports.drop_wasm_state2(&mut store, b)?; + + exports.wasm_state2_param_record(&mut store, WasmStateParamRecord { a: &s2 })?; + exports.wasm_state2_param_tuple(&mut store, (&s2,))?; + exports.wasm_state2_param_option(&mut store, Some(&s2))?; + exports.wasm_state2_param_option(&mut store, None)?; + exports.wasm_state2_param_result(&mut store, Ok(&s2))?; + exports.wasm_state2_param_result(&mut store, Err(2))?; + exports.wasm_state2_param_variant(&mut store, WasmStateParamVariant::WasmState2(&s2))?; + exports.wasm_state2_param_variant(&mut store, WasmStateParamVariant::U32(2))?; + exports.wasm_state2_param_list(&mut store, &[])?; + exports.wasm_state2_param_list(&mut store, &[&s2])?; + exports.wasm_state2_param_list(&mut store, &[&s2, &s2])?; + exports.drop_wasm_state2(&mut store, s2)?; + + let s = exports.wasm_state2_result_record(&mut store)?.a; + exports.drop_wasm_state2(&mut store, s)?; + let s = exports.wasm_state2_result_tuple(&mut store)?.0; + exports.drop_wasm_state2(&mut store, s)?; + let s = exports.wasm_state2_result_option(&mut store)?.unwrap(); + exports.drop_wasm_state2(&mut store, s)?; + let s = exports.wasm_state2_result_result(&mut store)?.unwrap(); + match exports.wasm_state2_result_variant(&mut store)? { + WasmStateResultVariant::WasmState2(s) => exports.drop_wasm_state2(&mut store, s)?, + WasmStateResultVariant::U32(_) => panic!(), + } + exports.drop_wasm_state2(&mut store, s)?; + for s in exports.wasm_state2_result_list(&mut store)? { + exports.drop_wasm_state2(&mut store, s)?; + } + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.ts new file mode 100644 index 0000000..e611fc9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/host.ts @@ -0,0 +1,127 @@ +import { addImportsToImports, Imports } from "./imports.js"; +import { Exports } from "./exports.js"; +import * as exports from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; +// @ts-ignore +import * as assert from 'assert'; + +async function run() { + const importObj = {}; + let sawClose = false; + const imports: Imports = { + hostStateCreate() { return 100; }, + hostStateGet(x) { return x as number; }, + hostState2Create() { return 101; }, + hostState2SawClose() { return sawClose; }, + dropHostState2(state) { sawClose = true; }, + twoHostStates(a, b) { return [b, a]; }, + hostState2ParamRecord(x) {}, + hostState2ParamTuple(x) {}, + hostState2ParamOption(x) {}, + hostState2ParamResult(x) {}, + hostState2ParamVariant(x) {}, + hostState2ParamList(x) {}, + + hostState2ResultRecord() { return { a: {} }; }, + hostState2ResultTuple() { return [{}]; }, + hostState2ResultOption() { return 102; }, + hostState2ResultResult() { return { tag: 'ok', val: {} }; }, + hostState2ResultVariant() { return { tag: 0, val: {} }; }, + hostState2ResultList() { return [{}, 3]; }, + + markdown2Create() { + class Markdown { + buf: string; + + constructor() { + this.buf = ''; + } + append(extra: string) { + this.buf += extra; + } + render() { + return this.buf.replace('red', 'green'); + } + } + + return new Markdown(); + }, + + oddNameCreate() { + class OddName { + frobTheOdd() {} + } + return new OddName(); + } + }; + let instance: WebAssembly.Instance; + addImportsToImports(importObj, imports, name => instance.exports[name]); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + instance = wasm.instance; + + wasm.testImports(); + + // Param/result of a handle works in a simple fashion + const s: exports.WasmState = wasm.wasmStateCreate(); + assert.strictEqual(wasm.wasmStateGetVal(s), 100); + + // Deterministic destruction is possible + assert.strictEqual(wasm.wasmState2SawClose(), false); + const s2: exports.WasmState2 = wasm.wasmState2Create(); + assert.strictEqual(wasm.wasmState2SawClose(), false); + s2.drop(); + assert.strictEqual(wasm.wasmState2SawClose(), true); + + const arg1 = wasm.wasmStateCreate(); + const arg2 = wasm.wasmState2Create(); + const [c, d] = wasm.twoWasmStates(arg1, arg2); + arg1.drop(); + arg2.drop(); + + wasm.wasmState2ParamRecord({ a: d }); + wasm.wasmState2ParamTuple([d]); + wasm.wasmState2ParamOption(d); + wasm.wasmState2ParamOption(null); + wasm.wasmState2ParamResult({ tag: 'ok', val: d }); + wasm.wasmState2ParamResult({ tag: 'err', val: 2 }); + wasm.wasmState2ParamVariant({ tag: 0, val: d }); + wasm.wasmState2ParamVariant({ tag: 1, val: 2 }); + wasm.wasmState2ParamList([]); + wasm.wasmState2ParamList([d]); + wasm.wasmState2ParamList([d, d]); + + c.drop(); + d.drop(); + + wasm.wasmState2ResultRecord().a?.drop(); + wasm.wasmState2ResultTuple()[0].drop(); + const opt = wasm.wasmState2ResultOption(); + if (opt === null) + throw new Error('should be some'); + opt.drop(); + const result = wasm.wasmState2ResultResult(); + if (result.tag === 'err') + throw new Error('should be ok'); + result.val.drop(); + const variant = wasm.wasmState2ResultVariant(); + if (variant.tag === 1) + throw new Error('should be 0'); + variant.val.drop(); + for (let val of wasm.wasmState2ResultList()) + val.drop(); + + s.drop(); + + const md = exports.Markdown.create(wasm); + if (md) { + md.append("red is the best color"); + assert.strictEqual(md.render(), "green is the best color"); + md.drop(); + } +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/imports.wit new file mode 100644 index 0000000..1c5b600 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/imports.wit @@ -0,0 +1,55 @@ +resource host-state +resource host-state2 + +host-state-create: func() -> host-state +host-state-get: func(a: host-state) -> u32 + +host-state2-create: func() -> host-state2 +host-state2-saw-close: func() -> bool +two-host-states: func(a: host-state, b: host-state2) -> tuple + +record host-state-param-record { a: host-state2 } +host-state2-param-record: func(a: host-state-param-record) + +type host-state-param-tuple = tuple +host-state2-param-tuple: func(a: host-state-param-tuple) + +type host-state-param-option = option +host-state2-param-option: func(a: host-state-param-option) + +type host-state-param-result = expected +host-state2-param-result: func(a: host-state-param-result) + +union host-state-param-variant { host-state2, u32 } +host-state2-param-variant: func(a: host-state-param-variant) + +host-state2-param-list: func(a: list) + + +record host-state-result-record { a: host-state2 } +host-state2-result-record: func() -> host-state-result-record + +type host-state-result-tuple = tuple +host-state2-result-tuple: func() -> host-state-result-tuple + +type host-state-result-option = option +host-state2-result-option: func() -> host-state-result-option + +type host-state-result-result = expected +host-state2-result-result: func() -> host-state-result-result + +union host-state-result-variant { host-state2, u32 } +host-state2-result-variant: func() -> host-state-result-variant + +host-state2-result-list: func() -> list + +resource markdown2 { + static create: func() -> markdown2 + append: func(buf: string) + render: func() -> string +} + +resource %odd-name { + static create: func() -> %odd-name + %frob-the-odd: func() +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/wasm.c new file mode 100644 index 0000000..850a481 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/wasm.c @@ -0,0 +1,223 @@ +#include +#include +#include +#include +#include + +void exports_test_imports() { + imports_host_state_t s = imports_host_state_create(); + assert(imports_host_state_get(s) == 100); + imports_host_state_free(&s); + + assert(imports_host_state2_saw_close() == false); + imports_host_state2_t s2 = imports_host_state2_create(); + assert(imports_host_state2_saw_close() == false); + imports_host_state2_free(&s2); + assert(imports_host_state2_saw_close() == true); + + { + imports_host_state_t a, b; + imports_host_state2_t c, d; + + a = imports_host_state_create(); + c = imports_host_state2_create(); + imports_two_host_states(a, c, &b, &d); + imports_host_state_free(&a); + imports_host_state_free(&b); + imports_host_state2_free(&c); + + { + imports_host_state_param_record_t a; + a.a = d; + imports_host_state2_param_record(&a); + } + { + imports_host_state_param_tuple_t a; + a.f0 = d; + imports_host_state2_param_tuple(&a); + } + { + imports_host_state_param_option_t a; + a.is_some = true; + a.val = d; + imports_host_state2_param_option(&a); + } + { + imports_host_state_param_result_t a; + a.is_err = false; + a.val.ok = d; + imports_host_state2_param_result(&a); + a.is_err = true; + a.val.err = 2; + imports_host_state2_param_result(&a); + } + { + imports_host_state_param_variant_t a; + a.tag = 0; + a.val.f0 = d; + imports_host_state2_param_variant(&a); + a.tag = 1; + a.val.f1 = 2; + imports_host_state2_param_variant(&a); + } + { + imports_host_state2_t arr[2]; + arr[0] = d; + arr[1] = d; + imports_list_host_state2_t list; + list.len = 0; + list.ptr = arr; + imports_host_state2_param_list(&list); + list.len = 1; + imports_host_state2_param_list(&list); + list.len = 2; + imports_host_state2_param_list(&list); + } + + imports_host_state2_free(&d); + } + + { + imports_host_state_result_record_t a; + imports_host_state2_result_record(&a); + imports_host_state2_free(&a.a); + } + { + imports_host_state2_t a; + imports_host_state2_result_tuple(&a); + imports_host_state2_free(&a); + } + { + imports_host_state2_t a; + assert(imports_host_state2_result_option(&a)); + imports_host_state2_free(&a); + } + { + imports_host_state_result_result_t a; + imports_host_state2_result_result(&a); + assert(!a.is_err); + imports_host_state2_free(&a.val.ok); + } + { + imports_host_state_result_variant_t a; + imports_host_state2_result_variant(&a); + assert(a.tag == 0); + imports_host_state2_free(&a.val.f0); + } + { + imports_list_host_state2_t a; + imports_host_state2_result_list(&a); + imports_list_host_state2_free(&a); + } + { + imports_markdown2_t a = imports_markdown2_create(); + imports_string_t s; + imports_string_set(&s, "red is the best color"); + imports_markdown2_append(a, &s); + imports_markdown2_render(a, &s); + + const char *expected = "green is the best color"; + assert(s.len == strlen(expected)); + assert(memcmp(s.ptr, expected, s.len) == 0); + imports_string_free(&s); + imports_markdown2_free(&a); + } +} + +exports_wasm_state_t exports_wasm_state_create(void) { + return exports_wasm_state_new((void*) 100); +} + +uint32_t exports_wasm_state_get_val(exports_wasm_state_t a) { + uint32_t ret = (uint32_t) exports_wasm_state_get(&a); + exports_wasm_state_free(&a); + return ret; +} + +exports_wasm_state2_t exports_wasm_state2_create(void) { + return exports_wasm_state2_new((void*) 33); +} + +static bool WASM_STATE2_CLOSED = false; + +bool exports_wasm_state2_saw_close(void) { + return WASM_STATE2_CLOSED; +} + +void exports_wasm_state2_dtor(void *data) { + WASM_STATE2_CLOSED = true; +} + +void exports_two_wasm_states(exports_wasm_state_t a, exports_wasm_state2_t b, exports_wasm_state_t *ret0, exports_wasm_state2_t *ret1) { + exports_wasm_state_free(&a); + exports_wasm_state2_free(&b); + + *ret0 = exports_wasm_state_new((void*) 101); + *ret1 = exports_wasm_state2_new((void*) 102); +} + +void exports_wasm_state2_param_record(exports_wasm_state_param_record_t *a) { + exports_wasm_state_param_record_free(a); +} + +void exports_wasm_state2_param_tuple(exports_wasm_state_param_tuple_t *a) { + exports_wasm_state_param_tuple_free(a); +} + +void exports_wasm_state2_param_option(exports_wasm_state_param_option_t *a) { + exports_wasm_state_param_option_free(a); +} + +void exports_wasm_state2_param_result(exports_wasm_state_param_result_t *a) { + exports_wasm_state_param_result_free(a); +} + +void exports_wasm_state2_param_variant(exports_wasm_state_param_variant_t *a) { + exports_wasm_state_param_variant_free(a); +} + +void exports_wasm_state2_param_list(exports_list_wasm_state2_t *a) { + exports_list_wasm_state2_free(a); +} + +void exports_wasm_state2_result_record(exports_wasm_state_result_record_t *ret0) { + ret0->a = exports_wasm_state2_new((void*) 222); +} + +void exports_wasm_state2_result_tuple(exports_wasm_state2_t *ret0) { + *ret0 = exports_wasm_state2_new((void*) 333); +} + +bool exports_wasm_state2_result_option(exports_wasm_state2_t *ret0) { + *ret0 = exports_wasm_state2_new((void*) 444); + return true; +} + +void exports_wasm_state2_result_result(exports_wasm_state_result_result_t *ret0) { + ret0->is_err = false; + ret0->val.ok = exports_wasm_state2_new((void*) 555); +} + +void exports_wasm_state2_result_variant(exports_wasm_state_result_variant_t *ret0) { + ret0->tag = 0; + ret0->val.f0 = exports_wasm_state2_new((void*) 666); +} + +void exports_wasm_state2_result_list(exports_list_wasm_state2_t *ret0) { + ret0->len = 2; + ret0->ptr = malloc(2 * sizeof(exports_wasm_state2_t)); + ret0->ptr[0] = exports_wasm_state2_new((void*) 777); + ret0->ptr[1] = exports_wasm_state2_new((void*) 888); +} + +bool exports_markdown_create(exports_markdown_t *md) { + return false; +} + +void exports_markdown_append(exports_markdown_t md, exports_string_t *s) { + abort(); +} + +void exports_markdown_render(exports_markdown_t md, exports_string_t *ret) { + abort(); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/wasm.rs new file mode 100644 index 0000000..95e906a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/handles/wasm.rs @@ -0,0 +1,131 @@ +wit_bindgen_rust::import!("../../tests/runtime/handles/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/handles/exports.wit"); + +use exports::*; +use std::cell::RefCell; +use std::sync::atomic::{AtomicU32, Ordering::SeqCst}; +use wit_bindgen_rust::Handle; + +struct Exports; + +static CLOSED: AtomicU32 = AtomicU32::new(0); + +pub struct WasmState(u32); + +pub struct WasmState2(u32); + +impl exports::Exports for Exports { + fn test_imports() { + use imports::*; + + let s: HostState = host_state_create(); + assert_eq!(host_state_get(&s), 100); + assert_eq!(host_state2_saw_close(), false); + let s: HostState2 = host_state2_create(); + assert_eq!(host_state2_saw_close(), false); + drop(s); + assert_eq!(host_state2_saw_close(), true); + + let (_a, s2) = two_host_states(&host_state_create(), &host_state2_create()); + + host_state2_param_record(HostStateParamRecord { a: &s2 }); + host_state2_param_tuple((&s2,)); + host_state2_param_option(Some(&s2)); + host_state2_param_option(None); + host_state2_param_result(Ok(&s2)); + host_state2_param_result(Err(2)); + host_state2_param_variant(HostStateParamVariant::HostState2(&s2)); + host_state2_param_variant(HostStateParamVariant::U32(2)); + host_state2_param_list(&[]); + host_state2_param_list(&[&s2]); + host_state2_param_list(&[&s2, &s2]); + + drop(host_state2_result_record().a); + drop(host_state2_result_tuple().0); + drop(host_state2_result_option().unwrap()); + drop(host_state2_result_result().unwrap()); + drop(host_state2_result_variant()); + drop(host_state2_result_list()); + + let md = Markdown2::create(); + md.append("red is the best color"); + assert_eq!(md.render(), "green is the best color"); + + let odd = OddName::create(); + odd.frob_the_odd(); + } + + fn wasm_state_create() -> Handle { + WasmState(100).into() + } + + fn wasm_state_get_val(state: Handle) -> u32 { + state.0 + } + + fn wasm_state2_create() -> Handle { + WasmState2(33).into() + } + + fn wasm_state2_saw_close() -> bool { + CLOSED.load(SeqCst) != 0 + } + + fn drop_wasm_state2(_state: WasmState2) { + CLOSED.store(1, SeqCst); + } + + fn two_wasm_states( + _a: Handle, + _b: Handle, + ) -> (Handle, Handle) { + (WasmState(101).into(), WasmState2(102).into()) + } + + fn wasm_state2_param_record(_a: WasmStateParamRecord) {} + fn wasm_state2_param_tuple(_a: (Handle,)) {} + fn wasm_state2_param_option(_a: Option>) {} + fn wasm_state2_param_result(_a: Result, u32>) {} + fn wasm_state2_param_variant(_a: WasmStateParamVariant) {} + fn wasm_state2_param_list(_a: Vec>) {} + + fn wasm_state2_result_record() -> WasmStateResultRecord { + WasmStateResultRecord { + a: WasmState2(222).into(), + } + } + fn wasm_state2_result_tuple() -> (Handle,) { + (WasmState2(333).into(),) + } + fn wasm_state2_result_option() -> Option> { + Some(WasmState2(444).into()) + } + fn wasm_state2_result_result() -> Result, u32> { + Ok(WasmState2(555).into()) + } + fn wasm_state2_result_variant() -> WasmStateResultVariant { + WasmStateResultVariant::WasmState2(Handle::new(WasmState2(666))) + } + fn wasm_state2_result_list() -> Vec> { + vec![WasmState2(777).into(), WasmState2(888).into()] + } +} + +#[derive(Default)] +pub struct Markdown { + buf: RefCell, +} + +impl exports::Markdown for Markdown { + fn create() -> Option> { + Some(Markdown::default().into()) + } + + fn append(&self, input: String) { + self.buf.borrow_mut().push_str(&input); + } + + fn render(&self) -> String { + self.buf.borrow().replace("red", "green") + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/exports.wit new file mode 100644 index 0000000..7272c49 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/exports.wit @@ -0,0 +1,9 @@ +invalid-u8: func() +invalid-s8: func() +invalid-u16: func() +invalid-s16: func() +invalid-char: func() +invalid-bool: func() +invalid-enum: func() +invalid-handle: func() +invalid-handle-close: func() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.py new file mode 100644 index 0000000..3895cb9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.py @@ -0,0 +1,74 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +from typing import Callable +import imports.bindings as i +import sys +import wasmtime + +class MyImports(Imports): + def roundtrip_u8(self, x: int) -> int: + raise Exception('unreachable') + + def roundtrip_s8(self, x: int) -> int: + raise Exception('unreachable') + + def roundtrip_u16(self, x: int) -> int: + raise Exception('unreachable') + + def roundtrip_s16(self, x: int) -> int: + raise Exception('unreachable') + + def roundtrip_bool(self, x: bool) -> bool: + raise Exception('unreachable') + + def roundtrip_char(self, x: str) -> str: + raise Exception('unreachable') + + def roundtrip_enum(self, x: i.E) -> i.E: + raise Exception('unreachable') + + def get_internal(self, x: i.HostState) -> int: + raise Exception('unreachable') + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + def assert_throws(f: Callable, msg: str) -> None: + try: + f() + raise RuntimeError('expected exception') + except TypeError as e: + actual = str(e) + except OverflowError as e: + actual = str(e) + except ValueError as e: + actual = str(e) + except IndexError as e: + actual = str(e) + if not msg in actual: + print(actual) + assert(msg in actual) + + assert_throws(lambda: wasm.invalid_bool(store), 'invalid variant discriminant for bool') + assert_throws(lambda: wasm.invalid_u8(store), 'must be between') + assert_throws(lambda: wasm.invalid_s8(store), 'must be between') + assert_throws(lambda: wasm.invalid_u16(store), 'must be between') + assert_throws(lambda: wasm.invalid_s16(store), 'must be between') + assert_throws(lambda: wasm.invalid_char(store), 'not a valid char') + assert_throws(lambda: wasm.invalid_enum(store), 'not a valid E') + assert_throws(lambda: wasm.invalid_handle(store), 'handle index not valid') + assert_throws(lambda: wasm.invalid_handle_close(store), 'handle index not valid') + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.rs new file mode 100644 index 0000000..610475f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.rs @@ -0,0 +1,99 @@ +wit_bindgen_wasmtime::export!("../../tests/runtime/invalid/imports.wit"); + +use anyhow::Result; +use imports::*; +use wasmtime::Trap; + +#[derive(Default)] +pub struct MyImports; + +impl Imports for MyImports { + type HostState = (); + + fn roundtrip_u8(&mut self, _: u8) -> u8 { + unreachable!() + } + fn roundtrip_s8(&mut self, _: i8) -> i8 { + unreachable!() + } + fn roundtrip_u16(&mut self, _: u16) -> u16 { + unreachable!() + } + fn roundtrip_s16(&mut self, _: i16) -> i16 { + unreachable!() + } + fn roundtrip_char(&mut self, _: char) -> char { + unreachable!() + } + fn roundtrip_bool(&mut self, _: bool) -> bool { + unreachable!() + } + fn roundtrip_enum(&mut self, _: imports::E) -> imports::E { + unreachable!() + } + fn get_internal(&mut self, _: &()) -> u32 { + unreachable!() + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/invalid/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let (exports, mut store) = crate::instantiate( + wasm, + |linker| { + imports::add_to_linker( + linker, + |cx: &mut crate::Context<(MyImports, imports::ImportsTables), _>| { + (&mut cx.imports.0, &mut cx.imports.1) + }, + ) + }, + |store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports), + )?; + + assert_err( + exports.invalid_bool(&mut store), + "invalid discriminant for `bool`", + )?; + assert_err( + exports.invalid_u8(&mut store), + "out-of-bounds integer conversion", + )?; + assert_err( + exports.invalid_s8(&mut store), + "out-of-bounds integer conversion", + )?; + assert_err( + exports.invalid_u16(&mut store), + "out-of-bounds integer conversion", + )?; + assert_err( + exports.invalid_s16(&mut store), + "out-of-bounds integer conversion", + )?; + assert_err( + exports.invalid_char(&mut store), + "char value out of valid range", + )?; + assert_err( + exports.invalid_enum(&mut store), + "invalid discriminant for `E`", + )?; + assert_err(exports.invalid_handle(&mut store), "invalid handle index")?; + assert_err( + exports.invalid_handle_close(&mut store), + "invalid handle index", + )?; + return Ok(()); + + fn assert_err(result: Result<(), Trap>, err: &str) -> Result<()> { + match result { + Ok(()) => anyhow::bail!("export didn't trap"), + Err(e) if e.to_string().contains(err) => Ok(()), + Err(e) => Err(e.into()), + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.ts new file mode 100644 index 0000000..5e6cb31 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/host.ts @@ -0,0 +1,39 @@ +import { addImportsToImports, Imports } from "./imports.js"; +import { Exports } from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; +// @ts-ignore +import * as assert from 'assert'; + +async function run() { + const importObj = {}; + const imports: Imports = { + roundtripU8(x) { throw new Error('unreachable'); }, + roundtripS8(x) { throw new Error('unreachable'); }, + roundtripU16(x) { throw new Error('unreachable'); }, + roundtripS16(x) { throw new Error('unreachable'); }, + roundtripBool(x) { throw new Error('unreachable'); }, + roundtripChar(x) { throw new Error('unreachable'); }, + roundtripEnum(x) { throw new Error('unreachable'); }, + getInternal(x) { throw new Error('unreachable'); }, + }; + let instance: WebAssembly.Instance; + addImportsToImports(importObj, imports); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + instance = wasm.instance; + + assert.throws(() => wasm.invalidBool(), /invalid variant discriminant for bool/); + assert.throws(() => wasm.invalidU8(), /must be between/); + assert.throws(() => wasm.invalidS8(), /must be between/); + assert.throws(() => wasm.invalidU16(), /must be between/); + assert.throws(() => wasm.invalidS16(), /must be between/); + assert.throws(() => wasm.invalidChar(), /not a valid char/); + assert.throws(() => wasm.invalidEnum(), /invalid discriminant specified for E/); + assert.throws(() => wasm.invalidHandle(), /handle index not valid/); + assert.throws(() => wasm.invalidHandleClose(), /handle index not valid/); +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/imports.wit new file mode 100644 index 0000000..420e8ad --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/imports.wit @@ -0,0 +1,13 @@ +roundtrip-u8: func(a: u8) -> u8 +roundtrip-s8: func(a: s8) -> s8 +roundtrip-u16: func(a: u16) -> u16 +roundtrip-s16: func(a: s16) -> s16 +roundtrip-char: func(a: char) -> char + +enum e { a, b, c } +roundtrip-enum: func(a: e) -> e + +roundtrip-bool: func(a: bool) -> bool + +resource host-state +get-internal: func(a: host-state) -> u32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/wasm.rs new file mode 100644 index 0000000..374ffee --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/invalid/wasm.rs @@ -0,0 +1,88 @@ +wit_bindgen_rust::export!("../../tests/runtime/invalid/exports.wit"); + +#[link(wasm_import_module = "imports")] +extern "C" { + #[link_name = "roundtrip-bool"] + fn roundtrip_bool(a: i32) -> i32; + #[link_name = "roundtrip-u16"] + fn roundtrip_u16(a: i32) -> i32; + #[link_name = "roundtrip-u8"] + fn roundtrip_u8(a: i32) -> i32; + #[link_name = "roundtrip-s16"] + fn roundtrip_s16(a: i32) -> i32; + #[link_name = "roundtrip-s8"] + fn roundtrip_s8(a: i32) -> i32; + #[link_name = "roundtrip-char"] + fn roundtrip_char(a: i32) -> i32; + #[link_name = "roundtrip-enum"] + fn roundtrip_enum(a: i32) -> i32; + #[link_name = "get-internal"] + fn get_internal(a: i32) -> i32; +} + +#[link(wasm_import_module = "canonical_abi")] +extern "C" { + #[link_name = "resource_drop_host-state"] + fn resource_drop_host_state(a: i32); +} + +struct Exports; + +impl exports::Exports for Exports { + fn invalid_u8() { + unsafe { + roundtrip_u8(i32::MAX); + } + unreachable!(); + } + fn invalid_s8() { + unsafe { + roundtrip_s8(i32::MAX); + } + unreachable!(); + } + fn invalid_u16() { + unsafe { + roundtrip_u16(i32::MAX); + } + unreachable!(); + } + fn invalid_s16() { + unsafe { + roundtrip_s16(i32::MAX); + } + unreachable!(); + } + fn invalid_char() { + unsafe { + roundtrip_char(0xd800); + } + unreachable!(); + } + fn invalid_bool() { + unsafe { + roundtrip_bool(2); + } + unreachable!(); + } + fn invalid_enum() { + unsafe { + roundtrip_enum(400); + } + unreachable!(); + } + + fn invalid_handle() { + unsafe { + get_internal(100); + } + unreachable!(); + } + + fn invalid_handle_close() { + unsafe { + resource_drop_host_state(100); + } + unreachable!(); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/exports.wit new file mode 100644 index 0000000..acf0821 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/exports.wit @@ -0,0 +1 @@ +nop: func() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/host.ts new file mode 100644 index 0000000..895d2fc --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/host.ts @@ -0,0 +1,20 @@ +import { Exports } from "./exports.js"; +import { getWasm } from "./helpers.js"; + +async function run() { + const importObj = {}; + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + + // test other methods of creating a wasm wrapper + (new Exports()).instantiate(getWasm().buffer, importObj); + (new Exports()).instantiate(new Uint8Array(getWasm()), importObj); + (new Exports()).instantiate(new WebAssembly.Module(getWasm()), importObj); + { + const obj = new Exports(); + obj.addToImports(importObj); + obj.instantiate(new WebAssembly.Instance(new WebAssembly.Module(getWasm()), importObj)); + } +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/imports.wit new file mode 100644 index 0000000..e69de29 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/wasm.c new file mode 100644 index 0000000..4764146 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/js_instantiate/wasm.c @@ -0,0 +1,3 @@ +#include + +void exports_nop() {} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/exports.wit new file mode 100644 index 0000000..07a9d99 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/exports.wit @@ -0,0 +1,13 @@ +test-imports: func() +allocated-bytes: func() -> u32 + +list-param: func(a: list) +list-param2: func(a: string) +list-param3: func(a: list) +list-param4: func(a: list>) +list-result: func() -> list +list-result2: func() -> string +list-result3: func() -> list + +list-roundtrip: func(a: list) -> list +string-roundtrip: func(a: string) -> string diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.py new file mode 100644 index 0000000..4b39b1f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.py @@ -0,0 +1,109 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +from typing import Tuple, List +import exports.bindings as e +import imports.bindings as i +import sys +import wasmtime + +class MyImports: + def list_param(self, a: bytes) -> None: + assert(a == b'\x01\x02\x03\x04') + + def list_param2(self, a: str) -> None: + assert(a == 'foo') + + def list_param3(self, a: List[str]) -> None: + assert(a == ['foo', 'bar', 'baz']) + + def list_param4(self, a: List[List[str]]) -> None: + assert(a == [['foo', 'bar'], ['baz']]) + + def list_result(self) -> bytes: + return b'\x01\x02\x03\x04\x05' + + def list_result2(self) -> str: + return 'hello!' + + def list_result3(self) -> List[str]: + return ['hello,', 'world!'] + + def list_roundtrip(self, a: bytes) -> bytes: + return a + + def string_roundtrip(self, a: str) -> str: + return a + + def unaligned_roundtrip1(self, a: List[int], b: List[int], c: List[int], d: List[i.Flag32], e: List[i.Flag64]) -> None: + assert(a == [1]) + assert(b == [2]) + assert(c == [3]) + assert(d == [i.Flag32.B8]) + assert(e == [i.Flag64.B9]) + + def unaligned_roundtrip2(self, a: List[i.UnalignedRecord], b: List[float], c: List[float], d: List[str], e: List[bytes]) -> None: + assert(a == [i.UnalignedRecord(a=10, b=11)]) + assert(b == [100.0]) + assert(c == [101.0]) + assert(d == ['foo']) + assert(e == [b'\x66']) + + def list_minmax8(self, a: bytes, b: List[int]) -> Tuple[bytes, List[int]]: + assert(a == b'\x00\xff') + assert(b == [-(1 << (8 - 1)), (1 << (8 - 1)) - 1]) + return (a, b) + + def list_minmax16(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: + assert(a == [0, (1 << 16) - 1]) + assert(b == [-(1 << (16 - 1)), (1 << (16 - 1)) - 1]) + return (a, b) + + def list_minmax32(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: + assert(a == [0, (1 << 32) - 1]) + assert(b == [-(1 << (32 - 1)), (1 << (32 - 1)) - 1]) + return (a, b) + + def list_minmax64(self, a: List[int], b: List[int]) -> Tuple[List[int], List[int]]: + assert(a == [0, (1 << 64) - 1]) + assert(b == [-(1 << (64 - 1)), (1 << (64 - 1)) - 1]) + return (a, b) + + def list_minmax_float(self, a: List[float], b: List[float]) -> Tuple[List[float], List[float]]: + assert(a == [-3.4028234663852886e+38, 3.4028234663852886e+38, -float('inf'), float('inf')]) + assert(b == [-sys.float_info.max, sys.float_info.max, -float('inf'), float('inf')]) + return (a, b) + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + allocated_bytes = wasm.allocated_bytes(store) + wasm.test_imports(store) + wasm.list_param(store, b'\x01\x02\x03\x04') + wasm.list_param2(store, "foo") + wasm.list_param3(store, ["foo", "bar", "baz"]) + wasm.list_param4(store, [["foo", "bar"], ["baz"]]) + assert(wasm.list_result(store) == b'\x01\x02\x03\x04\x05') + assert(wasm.list_result2(store) == "hello!") + assert(wasm.list_result3(store) == ["hello,", "world!"]) + + assert(wasm.string_roundtrip(store, "x") == "x") + assert(wasm.string_roundtrip(store, "") == "") + assert(wasm.string_roundtrip(store, "hello ⚑ world") == "hello ⚑ world") + + # Ensure that we properly called `free` everywhere in all the glue that we + # needed to. + assert(allocated_bytes == wasm.allocated_bytes(store)) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.rs new file mode 100644 index 0000000..9a1f6dc --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.rs @@ -0,0 +1,159 @@ +use anyhow::Result; + +wit_bindgen_wasmtime::export!("../../tests/runtime/lists/imports.wit"); + +use imports::*; +use wit_bindgen_wasmtime::Le; + +#[derive(Default)] +pub struct MyImports; + +impl Imports for MyImports { + fn list_param(&mut self, list: &[u8]) { + assert_eq!(list, [1, 2, 3, 4]); + } + + fn list_param2(&mut self, ptr: &str) { + assert_eq!(ptr, "foo"); + } + + fn list_param3(&mut self, ptr: Vec<&str>) { + assert_eq!(ptr.len(), 3); + assert_eq!(ptr[0], "foo"); + assert_eq!(ptr[1], "bar"); + assert_eq!(ptr[2], "baz"); + } + + fn list_param4(&mut self, ptr: Vec>) { + assert_eq!(ptr.len(), 2); + assert_eq!(ptr[0][0], "foo"); + assert_eq!(ptr[0][1], "bar"); + assert_eq!(ptr[1][0], "baz"); + } + + fn list_result(&mut self) -> Vec { + vec![1, 2, 3, 4, 5] + } + + fn list_result2(&mut self) -> String { + "hello!".to_string() + } + + fn list_result3(&mut self) -> Vec { + vec!["hello,".to_string(), "world!".to_string()] + } + + fn list_roundtrip(&mut self, list: &[u8]) -> Vec { + list.to_vec() + } + + fn string_roundtrip(&mut self, s: &str) -> String { + s.to_string() + } + + fn list_minmax8(&mut self, u: &[u8], s: &[i8]) -> (Vec, Vec) { + assert_eq!(u, [u8::MIN, u8::MAX]); + assert_eq!(s, [i8::MIN, i8::MAX]); + (u.to_vec(), s.to_vec()) + } + + fn list_minmax16(&mut self, u: &[Le], s: &[Le]) -> (Vec, Vec) { + assert_eq!(u, [u16::MIN, u16::MAX]); + assert_eq!(s, [i16::MIN, i16::MAX]); + ( + u.iter().map(|e| e.get()).collect(), + s.iter().map(|e| e.get()).collect(), + ) + } + + fn list_minmax32(&mut self, u: &[Le], s: &[Le]) -> (Vec, Vec) { + assert_eq!(u, [u32::MIN, u32::MAX]); + assert_eq!(s, [i32::MIN, i32::MAX]); + ( + u.iter().map(|e| e.get()).collect(), + s.iter().map(|e| e.get()).collect(), + ) + } + + fn list_minmax64(&mut self, u: &[Le], s: &[Le]) -> (Vec, Vec) { + assert_eq!(u, [u64::MIN, u64::MAX]); + assert_eq!(s, [i64::MIN, i64::MAX]); + ( + u.iter().map(|e| e.get()).collect(), + s.iter().map(|e| e.get()).collect(), + ) + } + + fn list_minmax_float(&mut self, u: &[Le], s: &[Le]) -> (Vec, Vec) { + assert_eq!(u, [f32::MIN, f32::MAX, f32::NEG_INFINITY, f32::INFINITY]); + assert_eq!(s, [f64::MIN, f64::MAX, f64::NEG_INFINITY, f64::INFINITY]); + ( + u.iter().map(|e| e.get()).collect(), + s.iter().map(|e| e.get()).collect(), + ) + } + + fn unaligned_roundtrip1( + &mut self, + u16s: &[Le], + u32s: &[Le], + u64s: &[Le], + flag32s: Vec, + flag64s: Vec, + ) { + assert_eq!(u16s, [1]); + assert_eq!(u32s, [2]); + assert_eq!(u64s, [3]); + assert_eq!(flag32s, [Flag32::B8]); + assert_eq!(flag64s, [Flag64::B9]); + } + + fn unaligned_roundtrip2( + &mut self, + records: &[Le], + f32s: &[Le], + f64s: &[Le], + strings: Vec<&str>, + lists: Vec<&[u8]>, + ) { + assert_eq!(records.len(), 1); + assert_eq!(records[0].get().a, 10); + assert_eq!(records[0].get().b, 11); + assert_eq!(f32s, [100.0]); + assert_eq!(f64s, [101.0]); + assert_eq!(strings, ["foo"]); + assert_eq!(lists, [&[102][..]]); + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/lists/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let (exports, mut store) = crate::instantiate( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut MyImports { &mut cx.imports }), + |store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports), + )?; + + let bytes = exports.allocated_bytes(&mut store)?; + exports.test_imports(&mut store)?; + exports.list_param(&mut store, &[1, 2, 3, 4])?; + exports.list_param2(&mut store, "foo")?; + exports.list_param3(&mut store, &["foo", "bar", "baz"])?; + exports.list_param4(&mut store, &[&["foo", "bar"], &["baz"]])?; + assert_eq!(exports.list_result(&mut store)?, [1, 2, 3, 4, 5]); + assert_eq!(exports.list_result2(&mut store)?, "hello!"); + assert_eq!(exports.list_result3(&mut store)?, ["hello,", "world!"]); + assert_eq!(exports.string_roundtrip(&mut store, "x")?, "x"); + assert_eq!(exports.string_roundtrip(&mut store, "")?, ""); + assert_eq!( + exports.string_roundtrip(&mut store, "hello ⚑ world")?, + "hello ⚑ world" + ); + // Ensure that we properly called `free` everywhere in all the glue that we + // needed to. + assert_eq!(bytes, exports.allocated_bytes(&mut store)?); + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.ts new file mode 100644 index 0000000..77d3899 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/host.ts @@ -0,0 +1,139 @@ +import { addImportsToImports, Imports, FLAG32_B8, FLAG64_B9 } from "./imports.js"; +import { Exports } from "./exports.js"; +import * as exports from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; +// @ts-ignore +import * as assert from 'assert'; + +async function run() { + const importObj = {}; + const imports: Imports = { + listParam(a) { + assert.deepStrictEqual(Array.from(a), [1, 2, 3, 4]); + }, + listParam2(a) { + assert.strictEqual(a, 'foo'); + }, + listParam3(a) { + assert.deepStrictEqual(a, ['foo', 'bar', 'baz']); + }, + listParam4(a) { + assert.deepStrictEqual(a, [['foo', 'bar'], ['baz']]); + }, + listResult() { + return new Uint8Array([1, 2, 3, 4, 5]); + }, + listResult2() { return 'hello!'; }, + listResult3() { return ['hello,', 'world!']; }, + listRoundtrip(x) { return x; }, + stringRoundtrip(x) { return x; }, + + unalignedRoundtrip1(u16, u32, u64, flag32, flag64) { + assert.deepStrictEqual(Array.from(u16), [1]); + assert.deepStrictEqual(Array.from(u32), [2]); + assert.deepStrictEqual(Array.from(u64), [3n]); + assert.deepStrictEqual(flag32, [FLAG32_B8]); + assert.deepStrictEqual(flag64, [FLAG64_B9]); + }, + unalignedRoundtrip2(record, f32, f64, string, list) { + assert.deepStrictEqual(Array.from(record), [{ a: 10, b: 11n }]); + assert.deepStrictEqual(Array.from(f32), [100]); + assert.deepStrictEqual(Array.from(f64), [101]); + assert.deepStrictEqual(string, ['foo']); + assert.deepStrictEqual(list, [new Uint8Array([102])]); + }, + listMinmax8(u, s) { + assert.deepEqual(u.length, 2); + assert.deepEqual(u[0], 0); + assert.deepEqual(u[1], (1 << 8) - 1); + assert.deepEqual(s.length, 2); + assert.deepEqual(s[0], -(1 << 7)); + assert.deepEqual(s[1], (1 << 7) - 1); + + return [u, s]; + }, + + listMinmax16(u, s) { + assert.deepEqual(u.length, 2); + assert.deepEqual(u[0], 0); + assert.deepEqual(u[1], (1 << 16) - 1); + assert.deepEqual(s.length, 2); + assert.deepEqual(s[0], -(1 << 15)); + assert.deepEqual(s[1], (1 << 15) - 1); + + return [u, s]; + }, + + listMinmax32(u, s) { + assert.deepEqual(u.length, 2); + assert.deepEqual(u[0], 0); + assert.deepEqual(u[1], ~0 >>> 0); + assert.deepEqual(s.length, 2); + assert.deepEqual(s[0], 1 << 31); + assert.deepEqual(s[1], ((1 << 31) - 1) >>> 0); + + return [u, s]; + }, + + listMinmax64(u, s) { + assert.deepEqual(u.length, 2); + assert.deepEqual(u[0], 0n); + assert.deepEqual(u[1], (2n ** 64n) - 1n); + assert.deepEqual(s.length, 2); + assert.deepEqual(s[0], -(2n ** 63n)); + assert.deepEqual(s[1], (2n ** 63n) - 1n); + + return [u, s]; + }, + + listMinmaxFloat(f, d) { + assert.deepEqual(f.length, 4); + assert.deepEqual(f[0], -3.4028234663852886e+38); + assert.deepEqual(f[1], 3.4028234663852886e+38); + assert.deepEqual(f[2], Number.NEGATIVE_INFINITY); + assert.deepEqual(f[3], Number.POSITIVE_INFINITY); + + assert.deepEqual(d.length, 4); + assert.deepEqual(d[0], -Number.MAX_VALUE); + assert.deepEqual(d[1], Number.MAX_VALUE); + assert.deepEqual(d[2], Number.NEGATIVE_INFINITY); + assert.deepEqual(d[3], Number.POSITIVE_INFINITY); + + return [f, d]; + }, + }; + let instance: WebAssembly.Instance; + addImportsToImports(importObj, imports, name => instance.exports[name]); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + instance = wasm.instance; + + const bytes = wasm.allocatedBytes(); + wasm.testImports(); + wasm.listParam(new Uint8Array([1, 2, 3, 4])); + wasm.listParam2("foo"); + wasm.listParam3(["foo", "bar", "baz"]); + wasm.listParam4([["foo", "bar"], ["baz"]]); + assert.deepStrictEqual(Array.from(wasm.listResult()), [1, 2, 3, 4, 5]); + assert.deepStrictEqual(wasm.listResult2(), "hello!"); + assert.deepStrictEqual(wasm.listResult3(), ["hello,", "world!"]); + + const buffer = new ArrayBuffer(8); + (new Uint8Array(buffer)).set(new Uint8Array([1, 2, 3, 4]), 2); + // Create a view of the four bytes in the middle of the buffer + const view = new Uint8Array(buffer, 2, 4); + assert.deepStrictEqual(Array.from(wasm.listRoundtrip(view)), [1, 2, 3, 4]); + + assert.deepStrictEqual(wasm.stringRoundtrip("x"), "x"); + assert.deepStrictEqual(wasm.stringRoundtrip(""), ""); + assert.deepStrictEqual(wasm.stringRoundtrip("hello ⚑ world"), "hello ⚑ world"); + + // Ensure that we properly called `free` everywhere in all the glue that we + // needed to. + assert.strictEqual(bytes, wasm.allocatedBytes()); +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/imports.wit new file mode 100644 index 0000000..67b710f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/imports.wit @@ -0,0 +1,40 @@ +flags flag32 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, +} + +flags flag64 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, + b32, b33, b34, b35, b36, b37, b38, b39, + b40, b41, b42, b43, b44, b45, b46, b47, + b48, b49, b50, b51, b52, b53, b54, b55, + b56, b57, b58, b59, b60, b61, b62, b63, +} + +list-param: func(a: list) +list-param2: func(a: string) +list-param3: func(a: list) +list-param4: func(a: list>) +list-result: func() -> list +list-result2: func() -> string +list-result3: func() -> list + +list-minmax8: func(a: list, b: list) -> tuple, list> +list-minmax16: func(a: list, b: list) -> tuple, list> +list-minmax32: func(a: list, b: list) -> tuple, list> +list-minmax64: func(a: list, b: list) -> tuple, list> +list-minmax-float: func(a: list, b: list) -> tuple, list> + +list-roundtrip: func(a: list) -> list + +string-roundtrip: func(a: string) -> string + +unaligned-roundtrip1: func(a: list, b: list, c: list, d: list, e: list) + +record unaligned-record { a: u32, b: u64 } +unaligned-roundtrip2: func(a: list, b: list, c: list, d: list, e: list>) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/wasm.c new file mode 100644 index 0000000..083a913 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/wasm.c @@ -0,0 +1,292 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// "custom allocator" which just keeps track of allocated bytes + +static size_t ALLOCATED_BYTES = 0; + +__attribute__((export_name("canonical_abi_realloc"))) +void *canonical_abi_realloc( void *ptr, size_t orig_size, size_t orig_align, size_t new_size) { + void *ret = realloc(ptr, new_size); + if (!ret) + abort(); + ALLOCATED_BYTES -= orig_size; + ALLOCATED_BYTES += new_size; + return ret; +} + +__attribute__((export_name("canonical_abi_free"))) +void canonical_abi_free(void *ptr, size_t size, size_t align) { + if (size > 0) { + ALLOCATED_BYTES -= size; + free(ptr); + } +} + +uint32_t exports_allocated_bytes(void) { + return ALLOCATED_BYTES; +} + +void exports_test_imports() { + { + uint8_t list[] = {1, 2, 3, 4}; + imports_list_u8_t a; + a.ptr = list; + a.len = 4; + imports_list_param(&a); + } + + { + imports_string_t a; + imports_string_set(&a, "foo"); + imports_list_param2(&a); + } + + { + imports_string_t list[3]; + imports_string_set(&list[0], "foo"); + imports_string_set(&list[1], "bar"); + imports_string_set(&list[2], "baz"); + imports_list_string_t a; + a.ptr = list; + a.len = 3; + imports_list_param3(&a); + } + + { + imports_string_t list1[2]; + imports_string_t list2[1]; + imports_string_set(&list1[0], "foo"); + imports_string_set(&list1[1], "bar"); + imports_string_set(&list2[0], "baz"); + imports_list_list_string_t a; + a.ptr[0].len = 2; + a.ptr[0].ptr = list1; + a.ptr[1].len = 1; + a.ptr[1].ptr = list2; + a.len = 2; + imports_list_param4(&a); + } + + { + imports_list_u8_t a; + imports_list_result(&a); + assert(a.len == 5); + assert(memcmp(a.ptr, "\x01\x02\x03\x04\x05", 5) == 0); + imports_list_u8_free(&a); + } + + { + imports_string_t a; + imports_list_result2(&a); + assert(a.len == 6); + assert(memcmp(a.ptr, "hello!", 6) == 0); + imports_string_free(&a); + } + + { + imports_list_string_t a; + imports_list_result3(&a); + assert(a.len == 2); + assert(a.ptr[0].len == 6); + assert(a.ptr[1].len == 6); + assert(memcmp(a.ptr[0].ptr, "hello,", 6) == 0); + assert(memcmp(a.ptr[1].ptr, "world!", 6) == 0); + imports_list_string_free(&a); + } + + { + imports_string_t a, b; + imports_string_set(&a, "x"); + imports_string_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + imports_string_free(&b); + + imports_string_set(&a, ""); + imports_string_roundtrip(&a, &b); + assert(b.len == a.len); + imports_string_free(&b); + + imports_string_set(&a, "hello"); + imports_string_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + imports_string_free(&b); + + imports_string_set(&a, "hello ⚑ world"); + imports_string_roundtrip(&a, &b); + assert(b.len == a.len); + assert(memcmp(b.ptr, a.ptr, a.len) == 0); + imports_string_free(&b); + } + + { + uint8_t u8[2] = {0, UCHAR_MAX}; + int8_t s8[2] = {SCHAR_MIN, SCHAR_MAX}; + imports_list_u8_t list_u8 = { u8, 2 }; + imports_list_s8_t list_s8 = { s8, 2 }; + imports_list_u8_t list_u8_out; + imports_list_s8_t list_s8_out; + imports_list_minmax8(&list_u8, &list_s8, &list_u8_out, &list_s8_out); + assert(list_u8_out.len == 2 && list_u8_out.ptr[0] == 0 && list_u8_out.ptr[1] == UCHAR_MAX); + assert(list_s8_out.len == 2 && list_s8_out.ptr[0] == SCHAR_MIN && list_s8_out.ptr[1] == SCHAR_MAX); + imports_list_u8_free(&list_u8_out); + imports_list_s8_free(&list_s8_out); + } + + { + uint16_t u16[2] = {0, USHRT_MAX}; + int16_t s16[2] = {SHRT_MIN, SHRT_MAX}; + imports_list_u16_t list_u16 = { u16, 2 }; + imports_list_s16_t list_s16 = { s16, 2 }; + imports_list_u16_t list_u16_out; + imports_list_s16_t list_s16_out; + imports_list_minmax16(&list_u16, &list_s16, &list_u16_out, &list_s16_out); + assert(list_u16_out.len == 2 && list_u16_out.ptr[0] == 0 && list_u16_out.ptr[1] == USHRT_MAX); + assert(list_s16_out.len == 2 && list_s16_out.ptr[0] == SHRT_MIN && list_s16_out.ptr[1] == SHRT_MAX); + imports_list_u16_free(&list_u16_out); + imports_list_s16_free(&list_s16_out); + } + + { + uint32_t u32[2] = {0, UINT_MAX}; + int32_t s32[2] = {INT_MIN, INT_MAX}; + imports_list_u32_t list_u32 = { u32, 2 }; + imports_list_s32_t list_s32 = { s32, 2 }; + imports_list_u32_t list_u32_out; + imports_list_s32_t list_s32_out; + imports_list_minmax32(&list_u32, &list_s32, &list_u32_out, &list_s32_out); + assert(list_u32_out.len == 2 && list_u32_out.ptr[0] == 0 && list_u32_out.ptr[1] == UINT_MAX); + assert(list_s32_out.len == 2 && list_s32_out.ptr[0] == INT_MIN && list_s32_out.ptr[1] == INT_MAX); + imports_list_u32_free(&list_u32_out); + imports_list_s32_free(&list_s32_out); + } + + { + uint64_t u64[2] = {0, ULLONG_MAX}; + int64_t s64[2] = {LLONG_MIN, LLONG_MAX}; + imports_list_u64_t list_u64 = { u64, 2 }; + imports_list_s64_t list_s64 = { s64, 2 }; + imports_list_u64_t list_u64_out; + imports_list_s64_t list_s64_out; + imports_list_minmax64(&list_u64, &list_s64, &list_u64_out, &list_s64_out); + assert(list_u64_out.len == 2 && list_u64_out.ptr[0] == 0 && list_u64_out.ptr[1] == ULLONG_MAX); + assert(list_s64_out.len == 2 && list_s64_out.ptr[0] == LLONG_MIN && list_s64_out.ptr[1] == LLONG_MAX); + imports_list_u64_free(&list_u64_out); + imports_list_s64_free(&list_s64_out); + } + + { + float f32[4] = {-FLT_MAX, FLT_MAX, -INFINITY, INFINITY}; + double f64[4] = {-DBL_MAX, DBL_MAX, -INFINITY, INFINITY}; + imports_list_float32_t list_float32 = { f32, 4 }; + imports_list_float64_t list_float64 = { f64, 4 }; + imports_list_float32_t list_float32_out; + imports_list_float64_t list_float64_out; + imports_list_minmax_float(&list_float32, &list_float64, &list_float32_out, &list_float64_out); + assert(list_float32_out.len == 4 && list_float32_out.ptr[0] == -FLT_MAX && list_float32_out.ptr[1] == FLT_MAX); + assert(list_float32_out.ptr[2] == -INFINITY && list_float32_out.ptr[3] == INFINITY); + assert(list_float64_out.len == 4 && list_float64_out.ptr[0] == -DBL_MAX && list_float64_out.ptr[1] == DBL_MAX); + assert(list_float64_out.ptr[2] == -INFINITY && list_float64_out.ptr[3] == INFINITY); + imports_list_float32_free(&list_float32_out); + imports_list_float64_free(&list_float64_out); + } +} + +void exports_list_param(exports_list_u8_t *a) { + assert(a->len == 4); + assert(a->ptr[0] == 1); + assert(a->ptr[1] == 2); + assert(a->ptr[2] == 3); + assert(a->ptr[3] == 4); + exports_list_u8_free(a); +} + +void exports_list_param2(exports_string_t *a) { + assert(a->len == 3); + assert(a->ptr[0] == 'f'); + assert(a->ptr[1] == 'o'); + assert(a->ptr[2] == 'o'); + exports_string_free(a); +} + +void exports_list_param3(exports_list_string_t *a) { + assert(a->len == 3); + assert(a->ptr[0].len == 3); + assert(a->ptr[0].ptr[0] == 'f'); + assert(a->ptr[0].ptr[1] == 'o'); + assert(a->ptr[0].ptr[2] == 'o'); + + assert(a->ptr[1].len == 3); + assert(a->ptr[1].ptr[0] == 'b'); + assert(a->ptr[1].ptr[1] == 'a'); + assert(a->ptr[1].ptr[2] == 'r'); + + assert(a->ptr[2].len == 3); + assert(a->ptr[2].ptr[0] == 'b'); + assert(a->ptr[2].ptr[1] == 'a'); + assert(a->ptr[2].ptr[2] == 'z'); + + exports_list_string_free(a); +} + +void exports_list_param4(exports_list_list_string_t *a) { + assert(a->len == 2); + assert(a->ptr[0].len == 2); + assert(a->ptr[1].len == 1); + + assert(a->ptr[0].ptr[0].len == 3); + assert(a->ptr[0].ptr[0].ptr[0] == 'f'); + assert(a->ptr[0].ptr[0].ptr[1] == 'o'); + assert(a->ptr[0].ptr[0].ptr[2] == 'o'); + + assert(a->ptr[0].ptr[1].len == 3); + assert(a->ptr[0].ptr[1].ptr[0] == 'b'); + assert(a->ptr[0].ptr[1].ptr[1] == 'a'); + assert(a->ptr[0].ptr[1].ptr[2] == 'r'); + + assert(a->ptr[1].ptr[0].len == 3); + assert(a->ptr[1].ptr[0].ptr[0] == 'b'); + assert(a->ptr[1].ptr[0].ptr[1] == 'a'); + assert(a->ptr[1].ptr[0].ptr[2] == 'z'); + + exports_list_list_string_free(a); +} + +void exports_list_result(exports_list_u8_t *ret0) { + ret0->ptr = canonical_abi_realloc(NULL, 0, 1, 5); + ret0->len = 5; + ret0->ptr[0] = 1; + ret0->ptr[1] = 2; + ret0->ptr[2] = 3; + ret0->ptr[3] = 4; + ret0->ptr[4] = 5; +} + +void exports_list_result2(exports_string_t *ret0) { + exports_string_dup(ret0, "hello!"); +} + +void exports_list_result3(exports_list_string_t *ret0) { + ret0->len = 2; + ret0->ptr = canonical_abi_realloc(NULL, 0, alignof(exports_string_t), 2 * sizeof(exports_string_t)); + + exports_string_dup(&ret0->ptr[0], "hello,"); + exports_string_dup(&ret0->ptr[1], "world!"); +} + +void exports_list_roundtrip(exports_list_u8_t *a, exports_list_u8_t *ret0) { + *ret0 = *a; +} + +void exports_string_roundtrip(exports_string_t *a, exports_string_t *ret0) { + *ret0 = *a; +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/wasm.rs new file mode 100644 index 0000000..8cb0679 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/lists/wasm.rs @@ -0,0 +1,169 @@ +wit_bindgen_rust::import!("../../tests/runtime/lists/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/lists/exports.wit"); + +use std::alloc::{self, Layout}; +use std::mem; +use std::ptr; + +struct Exports; + +impl exports::Exports for Exports { + fn allocated_bytes() -> u32 { + test_rust_wasm::get() as u32 + } + + fn test_imports() { + use imports::*; + + let _guard = test_rust_wasm::guard(); + + list_param(&[1, 2, 3, 4]); + list_param2("foo"); + list_param3(&["foo", "bar", "baz"]); + list_param4(&[&["foo", "bar"], &["baz"]]); + assert_eq!(list_result(), [1, 2, 3, 4, 5]); + assert_eq!(list_result2(), "hello!"); + assert_eq!(list_result3(), ["hello,", "world!"]); + + assert_eq!(string_roundtrip("x"), "x"); + assert_eq!(string_roundtrip(""), ""); + assert_eq!(string_roundtrip("hello"), "hello"); + assert_eq!(string_roundtrip("hello ⚑ world"), "hello ⚑ world"); + + struct Unaligned { + alloc: *mut u8, + _marker: std::marker::PhantomData, + } + + impl Unaligned { + fn layout() -> Layout { + Layout::from_size_align(2 * mem::size_of::(), 8).unwrap() + } + + fn new(data: T) -> Unaligned { + unsafe { + let alloc = alloc::alloc(Self::layout()); + assert!(!alloc.is_null()); + ptr::write_unaligned(alloc.add(1).cast(), data); + Unaligned { + alloc, + _marker: Default::default(), + } + } + } + + fn as_slice(&self) -> *const [T] { + unsafe { ptr::slice_from_raw_parts(self.alloc.add(1).cast(), 1) } + } + } + + impl Drop for Unaligned { + fn drop(&mut self) { + unsafe { + alloc::dealloc(self.alloc, Self::layout()); + } + } + } + + unsafe { + let u16s = Unaligned::new(1); + let u32s = Unaligned::new(2); + let u64s = Unaligned::new(3); + let flag32s = Unaligned::new(Flag32::B8); + let flag64s = Unaligned::new(Flag64::B9); + let records = Unaligned::new(UnalignedRecord { a: 10, b: 11 }); + let f32s = Unaligned::new(100.0); + let f64s = Unaligned::new(101.0); + let strings = Unaligned::new("foo"); + let lists = Unaligned::new(&[102][..]); + // Technically this is UB because we're creating safe slices from + // unaligned pointers, but we're hoping that because we're just passing + // off pointers to an import through a safe import we can get away with + // this. If this ever becomes a problem we'll just need to call the raw + // import with raw integers. + unaligned_roundtrip1( + &*u16s.as_slice(), + &*u32s.as_slice(), + &*u64s.as_slice(), + &*flag32s.as_slice(), + &*flag64s.as_slice(), + ); + unaligned_roundtrip2( + &*records.as_slice(), + &*f32s.as_slice(), + &*f64s.as_slice(), + &*strings.as_slice(), + &*lists.as_slice(), + ); + } + + assert_eq!( + list_minmax8(&[u8::MIN, u8::MAX], &[i8::MIN, i8::MAX]), + (vec![u8::MIN, u8::MAX], vec![i8::MIN, i8::MAX]), + ); + assert_eq!( + list_minmax16(&[u16::MIN, u16::MAX], &[i16::MIN, i16::MAX]), + (vec![u16::MIN, u16::MAX], vec![i16::MIN, i16::MAX]), + ); + assert_eq!( + list_minmax32(&[u32::MIN, u32::MAX], &[i32::MIN, i32::MAX]), + (vec![u32::MIN, u32::MAX], vec![i32::MIN, i32::MAX]), + ); + assert_eq!( + list_minmax64(&[u64::MIN, u64::MAX], &[i64::MIN, i64::MAX]), + (vec![u64::MIN, u64::MAX], vec![i64::MIN, i64::MAX]), + ); + assert_eq!( + list_minmax_float( + &[f32::MIN, f32::MAX, f32::NEG_INFINITY, f32::INFINITY], + &[f64::MIN, f64::MAX, f64::NEG_INFINITY, f64::INFINITY] + ), + ( + vec![f32::MIN, f32::MAX, f32::NEG_INFINITY, f32::INFINITY], + vec![f64::MIN, f64::MAX, f64::NEG_INFINITY, f64::INFINITY], + ), + ); + } + + fn list_param(list: Vec) { + assert_eq!(list, [1, 2, 3, 4]); + } + + fn list_param2(ptr: String) { + assert_eq!(ptr, "foo"); + } + + fn list_param3(ptr: Vec) { + assert_eq!(ptr.len(), 3); + assert_eq!(ptr[0], "foo"); + assert_eq!(ptr[1], "bar"); + assert_eq!(ptr[2], "baz"); + } + + fn list_param4(ptr: Vec>) { + assert_eq!(ptr.len(), 2); + assert_eq!(ptr[0][0], "foo"); + assert_eq!(ptr[0][1], "bar"); + assert_eq!(ptr[1][0], "baz"); + } + + fn list_result() -> Vec { + vec![1, 2, 3, 4, 5] + } + + fn list_result2() -> String { + "hello!".to_string() + } + + fn list_result3() -> Vec { + vec!["hello,".to_string(), "world!".to_string()] + } + + fn list_roundtrip(x: Vec) -> Vec { + x.clone() + } + + fn string_roundtrip(x: String) -> String { + x.clone() + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/exports.wit new file mode 100644 index 0000000..391bf26 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/exports.wit @@ -0,0 +1,22 @@ +many-arguments: func( + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + a8: u64, + a9: u64, + a10: u64, + a11: u64, + a12: u64, + a13: u64, + a14: u64, + a15: u64, + a16: u64, + a17: u64, + a18: u64, + a19: u64, + a20: u64, +) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.py new file mode 100644 index 0000000..5f5c2c0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.py @@ -0,0 +1,68 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +import math; +import sys +import wasmtime + +class MyImports: + 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, + a17: int, + a18: int, + a19: int, + a20: 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) + assert(a17 == 17) + assert(a18 == 18) + assert(a19 == 19) + assert(a20 == 20) + + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + wasm.many_arguments(store, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13,14, 15, 16, 17, 18, 19, 20) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.rs new file mode 100644 index 0000000..1123b85 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.rs @@ -0,0 +1,71 @@ +use anyhow::Result; + +wit_bindgen_wasmtime::export!("../../tests/runtime/many_arguments/imports.wit"); + +#[derive(Default)] +pub struct MyImports {} + +impl imports::Imports for MyImports { + fn many_arguments( + &mut self, + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + a8: u64, + a9: u64, + a10: u64, + a11: u64, + a12: u64, + a13: u64, + a14: u64, + a15: u64, + a16: u64, + a17: u64, + a18: u64, + a19: u64, + a20: u64, + ) { + assert_eq!(a1, 1); + assert_eq!(a2, 2); + assert_eq!(a3, 3); + assert_eq!(a4, 4); + assert_eq!(a5, 5); + assert_eq!(a6, 6); + assert_eq!(a7, 7); + assert_eq!(a8, 8); + assert_eq!(a9, 9); + assert_eq!(a10, 10); + assert_eq!(a11, 11); + assert_eq!(a12, 12); + assert_eq!(a13, 13); + assert_eq!(a14, 14); + assert_eq!(a15, 15); + assert_eq!(a16, 16); + assert_eq!(a17, 17); + assert_eq!(a18, 18); + assert_eq!(a19, 19); + assert_eq!(a20, 20); + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/many_arguments/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + let (exports, mut store) = crate::instantiate( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut MyImports { &mut cx.imports }), + |store, module, linker| { + exports::Exports::instantiate(store, module, linker, |cx| &mut cx.exports) + }, + )?; + + exports.many_arguments( + &mut store, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, + )?; + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.ts new file mode 100644 index 0000000..6d382ee --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/host.ts @@ -0,0 +1,95 @@ +import { addImportsToImports, Imports } from "./imports.js"; +import { Exports } from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; + +function assertEq(x: any, y: any) { + if (x !== y) + throw new Error(`${x} != ${y}`); +} + +function assert(x: boolean) { + if (!x) + throw new Error("assert failed"); +} + +async function run() { + const importObj = {}; + const imports: Imports = { + manyArguments( + a1, + a2, + a3, + a4, + a5, + a6, + a7, + a8, + a9, + a10, + a11, + a12, + a13, + a14, + a15, + a16, + a17, + a18, + a19, + a20, + ) { + assertEq(a1, 1n); + assertEq(a2, 2n); + assertEq(a3, 3n); + assertEq(a4, 4n); + assertEq(a5, 5n); + assertEq(a6, 6n); + assertEq(a7, 7n); + assertEq(a8, 8n); + assertEq(a9, 9n); + assertEq(a10, 10n); + assertEq(a11, 11n); + assertEq(a12, 12n); + assertEq(a13, 13n); + assertEq(a14, 14n); + assertEq(a15, 15n); + assertEq(a16, 16n); + assertEq(a17, 17n); + assertEq(a18, 18n); + assertEq(a19, 19n); + assertEq(a20, 20n); + }, + }; + let instance: WebAssembly.Instance; + addImportsToImports(importObj, imports, name => instance.exports[name]); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + instance = wasm.instance; + + wasm.manyArguments( + 1n, + 2n, + 3n, + 4n, + 5n, + 6n, + 7n, + 8n, + 9n, + 10n, + 11n, + 12n, + 13n, + 14n, + 15n, + 16n, + 17n, + 18n, + 19n, + 20n, + ); +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/imports.wit new file mode 100644 index 0000000..391bf26 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/imports.wit @@ -0,0 +1,22 @@ +many-arguments: func( + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + a8: u64, + a9: u64, + a10: u64, + a11: u64, + a12: u64, + a13: u64, + a14: u64, + a15: u64, + a16: u64, + a17: u64, + a18: u64, + a19: u64, + a20: u64, +) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/wasm.c new file mode 100644 index 0000000..4a4d03a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/wasm.c @@ -0,0 +1,72 @@ +#include +#include +#include +#include +#include + +void exports_many_arguments( + uint64_t a1, + uint64_t a2, + uint64_t a3, + uint64_t a4, + uint64_t a5, + uint64_t a6, + uint64_t a7, + uint64_t a8, + uint64_t a9, + uint64_t a10, + uint64_t a11, + uint64_t a12, + uint64_t a13, + uint64_t a14, + uint64_t a15, + uint64_t a16, + uint64_t a17, + uint64_t a18, + uint64_t a19, + uint64_t a20 + ) { + 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); + assert(a17 == 17); + assert(a18 == 18); + assert(a19 == 19); + assert(a20 == 20); + + imports_many_arguments( + a1, + a2, + a3, + a4, + a5, + a6, + a7, + a8, + a9, + a10, + a11, + a12, + a13, + a14, + a15, + a16, + a17, + a18, + a19, + a20 + ); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/wasm.rs new file mode 100644 index 0000000..0acbcf9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/many_arguments/wasm.rs @@ -0,0 +1,56 @@ +wit_bindgen_rust::import!("../../tests/runtime/many_arguments/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/many_arguments/exports.wit"); + +use imports::*; + +struct Exports; + +impl exports::Exports for Exports { + fn many_arguments( + a1: u64, + a2: u64, + a3: u64, + a4: u64, + a5: u64, + a6: u64, + a7: u64, + a8: u64, + a9: u64, + a10: u64, + a11: u64, + a12: u64, + a13: u64, + a14: u64, + a15: u64, + a16: u64, + a17: u64, + a18: u64, + a19: u64, + a20: u64, + ) { + assert_eq!(a1, 1); + assert_eq!(a2, 2); + assert_eq!(a3, 3); + assert_eq!(a4, 4); + assert_eq!(a5, 5); + assert_eq!(a6, 6); + assert_eq!(a7, 7); + assert_eq!(a8, 8); + assert_eq!(a9, 9); + assert_eq!(a10, 10); + assert_eq!(a11, 11); + assert_eq!(a12, 12); + assert_eq!(a13, 13); + assert_eq!(a14, 14); + assert_eq!(a15, 15); + assert_eq!(a16, 16); + assert_eq!(a17, 17); + assert_eq!(a18, 18); + assert_eq!(a19, 19); + assert_eq!(a20, 20); + many_arguments( + a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11, a12, a13, a14, a15, a16, a17, a18, a19, + a20, + ); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/exports.wit new file mode 100644 index 0000000..cd91feb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/exports.wit @@ -0,0 +1,16 @@ +test-imports: func() + +roundtrip-u8: func(a: u8) -> u8 +roundtrip-s8: func(a: s8) -> s8 +roundtrip-u16: func(a: u16) -> u16 +roundtrip-s16: func(a: s16) -> s16 +roundtrip-u32: func(a: u32) -> u32 +roundtrip-s32: func(a: s32) -> s32 +roundtrip-u64: func(a: u64) -> u64 +roundtrip-s64: func(a: s64) -> s64 +roundtrip-float32: func(a: float32) -> float32 +roundtrip-float64: func(a: float64) -> float64 +roundtrip-char: func(a: char) -> char + +set-scalar: func(a: u32) +get-scalar: func() -> u32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.py new file mode 100644 index 0000000..283aaf0 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.py @@ -0,0 +1,106 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +import math; +import sys +import wasmtime + +class MyImports: + def roundtrip_u8(self, a: int) -> int: + return a + + def roundtrip_s8(self, a: int) -> int: + return a + + def roundtrip_u16(self, a: int) -> int: + return a + + def roundtrip_s16(self, a: int) -> int: + return a + + def roundtrip_u32(self, a: int) -> int: + return a + + def roundtrip_s32(self, a: int) -> int: + return a + + def roundtrip_u64(self, a: int) -> int: + return a + + def roundtrip_s64(self, a: int) -> int: + return a + + 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 set_scalar(self, a: int) -> None: + self.scalar = a + + def get_scalar(self) -> int: + return self.scalar + + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + wasm.test_imports(store) + assert(wasm.roundtrip_u8(store, 1) == 1) + assert(wasm.roundtrip_u8(store, (1 << 8) - 1) == (1 << 8) - 1) + assert(wasm.roundtrip_u16(store, 1) == 1) + assert(wasm.roundtrip_u16(store, (1 << 16) - 1) == (1 << 16) - 1) + assert(wasm.roundtrip_u32(store, 1) == 1) + assert(wasm.roundtrip_u32(store, (1 << 32) - 1) == (1 << 32) - 1) + assert(wasm.roundtrip_u64(store, 1) == 1) + assert(wasm.roundtrip_u64(store, (1 << 64) - 1) == (1 << 64) - 1) + + assert(wasm.roundtrip_s8(store, 1) == 1) + assert(wasm.roundtrip_s8(store, (1 << (8 - 1) - 1)) == (1 << (8 - 1) - 1)) + assert(wasm.roundtrip_s8(store, -(1 << (8 - 1))) == -(1 << (8 - 1))) + assert(wasm.roundtrip_s16(store, 1) == 1) + assert(wasm.roundtrip_s16(store, (1 << (16 - 1) - 1)) == (1 << (16 - 1) - 1)) + assert(wasm.roundtrip_s16(store, -(1 << (16 - 1))) == -(1 << (16 - 1))) + assert(wasm.roundtrip_s32(store, 1) == 1) + assert(wasm.roundtrip_s32(store, (1 << (32 - 1) - 1)) == (1 << (32 - 1) - 1)) + assert(wasm.roundtrip_s32(store, -(1 << (32 - 1))) == -(1 << (32 - 1))) + assert(wasm.roundtrip_s64(store, 1) == 1) + assert(wasm.roundtrip_s64(store, (1 << (64 - 1) - 1)) == (1 << (64 - 1) - 1)) + assert(wasm.roundtrip_s64(store, -(1 << (64 - 1))) == -(1 << (64 - 1))) + + inf = float('inf') + assert(wasm.roundtrip_float32(store, 1.0) == 1.0) + assert(wasm.roundtrip_float32(store, inf) == inf) + assert(wasm.roundtrip_float32(store, -inf) == -inf) + assert(math.isnan(wasm.roundtrip_float32(store, float('nan')))) + + assert(wasm.roundtrip_float64(store, 1.0) == 1.0) + assert(wasm.roundtrip_float64(store, inf) == inf) + assert(wasm.roundtrip_float64(store, -inf) == -inf) + assert(math.isnan(wasm.roundtrip_float64(store, float('nan')))) + + assert(wasm.roundtrip_char(store, 'a') == 'a') + assert(wasm.roundtrip_char(store, ' ') == ' ') + assert(wasm.roundtrip_char(store, '🚩') == '🚩') + + wasm.set_scalar(store, 2) + assert(wasm.get_scalar(store) == 2) + wasm.set_scalar(store, 4) + assert(wasm.get_scalar(store) == 4) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.rs new file mode 100644 index 0000000..472f719 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.rs @@ -0,0 +1,188 @@ +use anyhow::Result; + +wit_bindgen_wasmtime::export!("../../tests/runtime/numbers/imports.wit"); + +#[derive(Default)] +pub struct MyImports { + scalar: u32, +} + +impl imports::Imports for MyImports { + fn roundtrip_u8(&mut self, val: u8) -> u8 { + val + } + + fn roundtrip_s8(&mut self, val: i8) -> i8 { + val + } + + fn roundtrip_u16(&mut self, val: u16) -> u16 { + val + } + + fn roundtrip_s16(&mut self, val: i16) -> i16 { + val + } + + fn roundtrip_u32(&mut self, val: u32) -> u32 { + val + } + + fn roundtrip_s32(&mut self, val: i32) -> i32 { + val + } + + fn roundtrip_u64(&mut self, val: u64) -> u64 { + val + } + + fn roundtrip_s64(&mut self, val: i64) -> i64 { + val + } + + fn roundtrip_float32(&mut self, val: f32) -> f32 { + val + } + + fn roundtrip_float64(&mut self, val: f64) -> f64 { + val + } + + fn roundtrip_char(&mut self, val: char) -> char { + val + } + + fn set_scalar(&mut self, val: u32) { + self.scalar = val; + } + + fn get_scalar(&mut self) -> u32 { + self.scalar + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/numbers/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + let (exports, mut store) = crate::instantiate( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut MyImports { &mut cx.imports }), + |store, module, linker| { + exports::Exports::instantiate(store, module, linker, |cx| &mut cx.exports) + }, + )?; + + exports.test_imports(&mut store)?; + assert_eq!(exports.roundtrip_u8(&mut store, 1)?, 1); + assert_eq!( + exports.roundtrip_u8(&mut store, u8::min_value())?, + u8::min_value() + ); + assert_eq!( + exports.roundtrip_u8(&mut store, u8::max_value())?, + u8::max_value() + ); + + assert_eq!(exports.roundtrip_s8(&mut store, 1)?, 1); + assert_eq!( + exports.roundtrip_s8(&mut store, i8::min_value())?, + i8::min_value() + ); + assert_eq!( + exports.roundtrip_s8(&mut store, i8::max_value())?, + i8::max_value() + ); + + assert_eq!(exports.roundtrip_u16(&mut store, 1)?, 1); + assert_eq!( + exports.roundtrip_u16(&mut store, u16::min_value())?, + u16::min_value() + ); + assert_eq!( + exports.roundtrip_u16(&mut store, u16::max_value())?, + u16::max_value() + ); + + assert_eq!(exports.roundtrip_s16(&mut store, 1)?, 1); + assert_eq!( + exports.roundtrip_s16(&mut store, i16::min_value())?, + i16::min_value() + ); + assert_eq!( + exports.roundtrip_s16(&mut store, i16::max_value())?, + i16::max_value() + ); + + assert_eq!(exports.roundtrip_u32(&mut store, 1)?, 1); + assert_eq!( + exports.roundtrip_u32(&mut store, u32::min_value())?, + u32::min_value() + ); + assert_eq!( + exports.roundtrip_u32(&mut store, u32::max_value())?, + u32::max_value() + ); + + assert_eq!(exports.roundtrip_s32(&mut store, 1)?, 1); + assert_eq!( + exports.roundtrip_s32(&mut store, i32::min_value())?, + i32::min_value() + ); + assert_eq!( + exports.roundtrip_s32(&mut store, i32::max_value())?, + i32::max_value() + ); + + assert_eq!(exports.roundtrip_u64(&mut store, 1)?, 1); + assert_eq!( + exports.roundtrip_u64(&mut store, u64::min_value())?, + u64::min_value() + ); + assert_eq!( + exports.roundtrip_u64(&mut store, u64::max_value())?, + u64::max_value() + ); + + assert_eq!(exports.roundtrip_s64(&mut store, 1)?, 1); + assert_eq!( + exports.roundtrip_s64(&mut store, i64::min_value())?, + i64::min_value() + ); + assert_eq!( + exports.roundtrip_s64(&mut store, i64::max_value())?, + i64::max_value() + ); + + assert_eq!(exports.roundtrip_float32(&mut store, 1.0)?, 1.0); + assert_eq!( + exports.roundtrip_float32(&mut store, f32::INFINITY)?, + f32::INFINITY + ); + assert_eq!( + exports.roundtrip_float32(&mut store, f32::NEG_INFINITY)?, + f32::NEG_INFINITY + ); + assert!(exports.roundtrip_float32(&mut store, f32::NAN)?.is_nan()); + + assert_eq!(exports.roundtrip_float64(&mut store, 1.0)?, 1.0); + assert_eq!( + exports.roundtrip_float64(&mut store, f64::INFINITY)?, + f64::INFINITY + ); + assert_eq!( + exports.roundtrip_float64(&mut store, f64::NEG_INFINITY)?, + f64::NEG_INFINITY + ); + assert!(exports.roundtrip_float64(&mut store, f64::NAN)?.is_nan()); + + assert_eq!(exports.roundtrip_char(&mut store, 'a')?, 'a'); + assert_eq!(exports.roundtrip_char(&mut store, ' ')?, ' '); + assert_eq!(exports.roundtrip_char(&mut store, '🚩')?, '🚩'); + + exports.set_scalar(&mut store, 2)?; + assert_eq!(exports.get_scalar(&mut store)?, 2); + exports.set_scalar(&mut store, 4)?; + assert_eq!(exports.get_scalar(&mut store)?, 4); + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.ts new file mode 100644 index 0000000..2913de2 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/host.ts @@ -0,0 +1,89 @@ +import { addImportsToImports, Imports } from "./imports.js"; +import { Exports } from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; + +function assertEq(x: any, y: any) { + if (x !== y) + throw new Error(`${x} != ${y}`); +} + +function assert(x: boolean) { + if (!x) + throw new Error("assert failed"); +} + +async function run() { + const importObj = {}; + let scalar = 0; + addImportsToImports(importObj, { + roundtripU8(x) { return x; }, + roundtripS8(x) { return x; }, + roundtripU16(x) { return x; }, + roundtripS16(x) { return x; }, + roundtripU32(x) { return x; }, + roundtripS32(x) { return x; }, + roundtripU64(x) { return x; }, + roundtripS64(x) { return x; }, + roundtripFloat32(x) { return x; }, + roundtripFloat64(x) { return x; }, + roundtripChar(x) { return x; }, + setScalar(x) { scalar = x; }, + getScalar() { return scalar; }, + }); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + + wasm.testImports(); + + assertEq(wasm.roundtripU8(1), 1); + assertEq(wasm.roundtripU8((1 << 8) - 1), (1 << 8) - 1); + + assertEq(wasm.roundtripS8(1), 1); + assertEq(wasm.roundtripS8((1 << 7) - 1), (1 << 7) - 1); + assertEq(wasm.roundtripS8(-(1 << 7)), -(1 << 7)); + + assertEq(wasm.roundtripU16(1), 1); + assertEq(wasm.roundtripU16((1 << 16) - 1), (1 << 16) - 1); + + assertEq(wasm.roundtripS16(1), 1); + assertEq(wasm.roundtripS16((1 << 15) - 1), (1 << 15) - 1); + assertEq(wasm.roundtripS16(-(1 << 15)), -(1 << 15)); + + assertEq(wasm.roundtripU32(1), 1); + assertEq(wasm.roundtripU32(~0 >>> 0), ~0 >>> 0); + + assertEq(wasm.roundtripS32(1), 1); + assertEq(wasm.roundtripS32(((1 << 31) - 1) >>> 0), ((1 << 31) - 1) >>> 0); + assertEq(wasm.roundtripS32(1 << 31), 1 << 31); + + assertEq(wasm.roundtripU64(1n), 1n); + assertEq(wasm.roundtripU64((1n << 64n) - 1n), (1n << 64n) - 1n); + + assertEq(wasm.roundtripS64(1n), 1n); + assertEq(wasm.roundtripS64((1n << 63n) - 1n), (1n << 63n) - 1n); + assertEq(wasm.roundtripS64(-(1n << 63n)), -(1n << 63n)); + + assertEq(wasm.roundtripFloat32(1), 1); + assertEq(wasm.roundtripFloat32(Infinity), Infinity); + assertEq(wasm.roundtripFloat32(-Infinity), -Infinity); + assert(Number.isNaN(wasm.roundtripFloat32(NaN))); + + assertEq(wasm.roundtripFloat64(1), 1); + assertEq(wasm.roundtripFloat64(Infinity), Infinity); + assertEq(wasm.roundtripFloat64(-Infinity), -Infinity); + assert(Number.isNaN(wasm.roundtripFloat64(NaN))); + + assertEq(wasm.roundtripChar('a'), 'a'); + assertEq(wasm.roundtripChar(' '), ' '); + assertEq(wasm.roundtripChar('🚩'), '🚩'); + + wasm.setScalar(2); + assertEq(wasm.getScalar(), 2); + wasm.setScalar(4); + assertEq(wasm.getScalar(), 4); +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/imports.wit new file mode 100644 index 0000000..ef13f9a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/imports.wit @@ -0,0 +1,14 @@ +roundtrip-u8: func(a: u8) -> u8 +roundtrip-s8: func(a: s8) -> s8 +roundtrip-u16: func(a: u16) -> u16 +roundtrip-s16: func(a: s16) -> s16 +roundtrip-u32: func(a: u32) -> u32 +roundtrip-s32: func(a: s32) -> s32 +roundtrip-u64: func(a: u64) -> u64 +roundtrip-s64: func(a: s64) -> s64 +roundtrip-float32: func(a: float32) -> float32 +roundtrip-float64: func(a: float64) -> float64 +roundtrip-char: func(a: char) -> char + +set-scalar: func(a: u32) +get-scalar: func() -> u32 diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/wasm.c new file mode 100644 index 0000000..e373aff --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/wasm.c @@ -0,0 +1,113 @@ +#include +#include +#include +#include +#include + +uint8_t exports_roundtrip_u8(uint8_t a) { + return a; +} + +int8_t exports_roundtrip_s8(int8_t a) { + return a; +} + +uint16_t exports_roundtrip_u16(uint16_t a) { + return a; +} + +int16_t exports_roundtrip_s16(int16_t a) { + return a; +} + +uint32_t exports_roundtrip_u32(uint32_t a) { + return a; +} + +int32_t exports_roundtrip_s32(int32_t a) { + return a; +} + +uint64_t exports_roundtrip_u64(uint64_t a) { + return a; +} + +int64_t exports_roundtrip_s64(int64_t a) { + return a; +} + +float exports_roundtrip_float32(float a) { + return a; +} + +double exports_roundtrip_float64(double a) { + return a; +} + +uint32_t exports_roundtrip_char(uint32_t a) { + return a; +} + +static uint32_t SCALAR = 0; + +void exports_set_scalar(uint32_t a) { + SCALAR = a; +} + +uint32_t exports_get_scalar(void) { + return SCALAR; +} + + +void exports_test_imports() { + assert(imports_roundtrip_u8(1) == 1); + assert(imports_roundtrip_u8(0) == 0); + assert(imports_roundtrip_u8(UCHAR_MAX) == UCHAR_MAX); + + assert(imports_roundtrip_s8(1) == 1); + assert(imports_roundtrip_s8(SCHAR_MIN) == SCHAR_MIN); + assert(imports_roundtrip_s8(SCHAR_MAX) == SCHAR_MAX); + + assert(imports_roundtrip_u16(1) == 1); + assert(imports_roundtrip_u16(0) == 0); + assert(imports_roundtrip_u16(USHRT_MAX) == USHRT_MAX); + + assert(imports_roundtrip_s16(1) == 1); + assert(imports_roundtrip_s16(SHRT_MIN) == SHRT_MIN); + assert(imports_roundtrip_s16(SHRT_MAX) == SHRT_MAX); + + assert(imports_roundtrip_u32(1) == 1); + assert(imports_roundtrip_u32(0) == 0); + assert(imports_roundtrip_u32(UINT_MAX) == UINT_MAX); + + assert(imports_roundtrip_s32(1) == 1); + assert(imports_roundtrip_s32(INT_MIN) == INT_MIN); + assert(imports_roundtrip_s32(INT_MAX) == INT_MAX); + + assert(imports_roundtrip_u64(1) == 1); + assert(imports_roundtrip_u64(0) == 0); + assert(imports_roundtrip_u64(ULONG_MAX) == ULONG_MAX); + + assert(imports_roundtrip_s64(1) == 1); + assert(imports_roundtrip_s64(LONG_MIN) == LONG_MIN); + assert(imports_roundtrip_s64(LONG_MAX) == LONG_MAX); + + assert(imports_roundtrip_float32(1.0) == 1.0); + assert(imports_roundtrip_float32(INFINITY) == INFINITY); + assert(imports_roundtrip_float32(-INFINITY) == -INFINITY); + assert(isnan(imports_roundtrip_float32(NAN))); + + assert(imports_roundtrip_float64(1.0) == 1.0); + assert(imports_roundtrip_float64(INFINITY) == INFINITY); + assert(imports_roundtrip_float64(-INFINITY) == -INFINITY); + assert(isnan(imports_roundtrip_float64(NAN))); + + assert(imports_roundtrip_char('a') == 'a'); + assert(imports_roundtrip_char(' ') == ' '); + assert(imports_roundtrip_char(U'🚩') == U'🚩'); + + imports_set_scalar(2); + assert(imports_get_scalar() == 2); + imports_set_scalar(4); + assert(imports_get_scalar() == 4); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/wasm.rs new file mode 100644 index 0000000..bd5342a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/numbers/wasm.rs @@ -0,0 +1,116 @@ +wit_bindgen_rust::import!("../../tests/runtime/numbers/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/numbers/exports.wit"); + +use imports::*; +use std::sync::atomic::{AtomicU32, Ordering::SeqCst}; + +struct Exports; + +static SCALAR: AtomicU32 = AtomicU32::new(0); + +impl exports::Exports for Exports { + fn test_imports() { + assert_eq!(roundtrip_u8(1), 1); + assert_eq!(roundtrip_u8(u8::min_value()), u8::min_value()); + assert_eq!(roundtrip_u8(u8::max_value()), u8::max_value()); + + assert_eq!(roundtrip_s8(1), 1); + assert_eq!(roundtrip_s8(i8::min_value()), i8::min_value()); + assert_eq!(roundtrip_s8(i8::max_value()), i8::max_value()); + + assert_eq!(roundtrip_u16(1), 1); + assert_eq!(roundtrip_u16(u16::min_value()), u16::min_value()); + assert_eq!(roundtrip_u16(u16::max_value()), u16::max_value()); + + assert_eq!(roundtrip_s16(1), 1); + assert_eq!(roundtrip_s16(i16::min_value()), i16::min_value()); + assert_eq!(roundtrip_s16(i16::max_value()), i16::max_value()); + + assert_eq!(roundtrip_u32(1), 1); + assert_eq!(roundtrip_u32(u32::min_value()), u32::min_value()); + assert_eq!(roundtrip_u32(u32::max_value()), u32::max_value()); + + assert_eq!(roundtrip_s32(1), 1); + assert_eq!(roundtrip_s32(i32::min_value()), i32::min_value()); + assert_eq!(roundtrip_s32(i32::max_value()), i32::max_value()); + + assert_eq!(roundtrip_u64(1), 1); + assert_eq!(roundtrip_u64(u64::min_value()), u64::min_value()); + assert_eq!(roundtrip_u64(u64::max_value()), u64::max_value()); + + assert_eq!(roundtrip_s64(1), 1); + assert_eq!(roundtrip_s64(i64::min_value()), i64::min_value()); + assert_eq!(roundtrip_s64(i64::max_value()), i64::max_value()); + + assert_eq!(roundtrip_float32(1.0), 1.0); + assert_eq!(roundtrip_float32(f32::INFINITY), f32::INFINITY); + assert_eq!(roundtrip_float32(f32::NEG_INFINITY), f32::NEG_INFINITY); + assert!(roundtrip_float32(f32::NAN).is_nan()); + + assert_eq!(roundtrip_float64(1.0), 1.0); + assert_eq!(roundtrip_float64(f64::INFINITY), f64::INFINITY); + assert_eq!(roundtrip_float64(f64::NEG_INFINITY), f64::NEG_INFINITY); + assert!(roundtrip_float64(f64::NAN).is_nan()); + + assert_eq!(roundtrip_char('a'), 'a'); + assert_eq!(roundtrip_char(' '), ' '); + assert_eq!(roundtrip_char('🚩'), '🚩'); + + set_scalar(2); + assert_eq!(get_scalar(), 2); + set_scalar(4); + assert_eq!(get_scalar(), 4); + } + + fn roundtrip_u8(a: u8) -> u8 { + a + } + + fn roundtrip_s8(a: i8) -> i8 { + a + } + + fn roundtrip_u16(a: u16) -> u16 { + a + } + + fn roundtrip_s16(a: i16) -> i16 { + a + } + + fn roundtrip_u32(a: u32) -> u32 { + a + } + + fn roundtrip_s32(a: i32) -> i32 { + a + } + + fn roundtrip_u64(a: u64) -> u64 { + a + } + + fn roundtrip_s64(a: i64) -> i64 { + a + } + + fn roundtrip_float32(a: f32) -> f32 { + a + } + + fn roundtrip_float64(a: f64) -> f64 { + a + } + + fn roundtrip_char(a: char) -> char { + a + } + + fn set_scalar(val: u32) { + SCALAR.store(val, SeqCst) + } + + fn get_scalar() -> u32 { + SCALAR.load(SeqCst) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/exports.wit new file mode 100644 index 0000000..45dcdbf --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/exports.wit @@ -0,0 +1,46 @@ +test-imports: func() + +multiple-results: func() -> tuple + +swap-tuple: func(a: tuple) -> tuple + +flags f1 { a, b } +roundtrip-flags1: func(a: f1) -> f1 + +flags f2 { c, d, e } +roundtrip-flags2: func(a: f2) -> f2 + +flags f8 { + b0, b1, b2, b3, b4, b5, b6, b7, +} + +flags f16 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, +} + +flags f32 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, +} + +flags f64 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, + b32, b33, b34, b35, b36, b37, b38, b39, + b40, b41, b42, b43, b44, b45, b46, b47, + b48, b49, b50, b51, b52, b53, b54, b55, + b56, b57, b58, b59, b60, b61, b62, b63, +} + +roundtrip-flags3: func(a: f8, b: f16, c: f32, d: f64) -> tuple + +record r1 { a: u8, b: f1 } +roundtrip-record1: func(a: r1) -> r1 + +tuple0: func(a: tuple<>) -> tuple<> +tuple1: func(a: tuple) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.py new file mode 100644 index 0000000..cce15b9 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.py @@ -0,0 +1,72 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +from typing import Tuple +import exports.bindings as e +import imports.bindings as i +import sys +import wasmtime + +class MyImports: + def multiple_results(self) -> Tuple[int, int]: + return (4, 5) + + def swap_tuple(self, a: Tuple[int, int]) -> Tuple[int, int]: + return (a[1], a[0]) + + def roundtrip_flags1(self, a: i.F1) -> i.F1: + return a + + def roundtrip_flags2(self, a: i.F2) -> i.F2: + return a + + def roundtrip_flags3(self, a: i.Flag8, b: i.Flag16, c: i.Flag32, d: i.Flag64) -> Tuple[i.Flag8, i.Flag16, i.Flag32, i.Flag64]: + return (a, b, c, d) + + def roundtrip_record1(self, a: i.R1) -> i.R1: + return a + + def tuple0(self, a: None) -> None: + pass + + def tuple1(self, a: Tuple[int]) -> Tuple[int]: + return (a[0],) + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + wasm.test_imports(store) + assert(wasm.multiple_results(store) == (100, 200)) + assert(wasm.swap_tuple(store, (1, 2)) == (2, 1)) + assert(wasm.roundtrip_flags1(store, e.F1.A) == e.F1.A) + assert(wasm.roundtrip_flags1(store, e.F1(0)) == e.F1(0)) + assert(wasm.roundtrip_flags1(store, e.F1.A | e.F1.B) == (e.F1.A | e.F1.B)) + + assert(wasm.roundtrip_flags2(store, e.F2.C) == e.F2.C) + assert(wasm.roundtrip_flags2(store, e.F2(0)) == e.F2(0)) + assert(wasm.roundtrip_flags2(store, e.F2.D) == e.F2.D) + assert(wasm.roundtrip_flags2(store, e.F2.C | e.F2.E) == (e.F2.C | e.F2.E)) + + r = wasm.roundtrip_record1(store, e.R1(8, e.F1(0))) + assert(r.a == 8) + assert(r.b == e.F1(0)) + + r = wasm.roundtrip_record1(store, e.R1(a=0, b=e.F1.A | e.F1.B)) + assert(r.a == 0) + assert(r.b == (e.F1.A | e.F1.B)) + + wasm.tuple0(store, None) + assert(wasm.tuple1(store, (1,)) == (1,)) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.rs new file mode 100644 index 0000000..5c56ed1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.rs @@ -0,0 +1,111 @@ +use anyhow::Result; + +wit_bindgen_wasmtime::export!("../../tests/runtime/records/imports.wit"); + +use imports::*; + +#[derive(Default)] +pub struct MyImports; + +impl Imports for MyImports { + fn multiple_results(&mut self) -> (u8, u16) { + (4, 5) + } + + fn swap_tuple(&mut self, a: (u8, u32)) -> (u32, u8) { + (a.1, a.0) + } + + fn roundtrip_flags1(&mut self, a: F1) -> F1 { + drop(a.to_string()); + drop(format!("{:?}", a)); + drop(a & F1::all()); + a + } + + fn roundtrip_flags2(&mut self, a: F2) -> F2 { + a + } + + fn roundtrip_flags3( + &mut self, + a: Flag8, + b: Flag16, + c: Flag32, + d: Flag64, + ) -> (Flag8, Flag16, Flag32, Flag64) { + (a, b, c, d) + } + + fn roundtrip_record1(&mut self, a: R1) -> R1 { + drop(format!("{:?}", a)); + a + } + + fn tuple0(&mut self, _: ()) {} + + fn tuple1(&mut self, a: (u8,)) -> (u8,) { + (a.0,) + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/records/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let (exports, mut store) = crate::instantiate( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut MyImports { &mut cx.imports }), + |store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports), + )?; + + exports.test_imports(&mut store)?; + assert_eq!(exports.multiple_results(&mut store,)?, (100, 200)); + assert_eq!(exports.swap_tuple(&mut store, (1u8, 2u32))?, (2u32, 1u8)); + assert_eq!(exports.roundtrip_flags1(&mut store, F1::A)?, F1::A); + assert_eq!( + exports.roundtrip_flags1(&mut store, F1::empty())?, + F1::empty() + ); + assert_eq!(exports.roundtrip_flags1(&mut store, F1::B)?, F1::B); + assert_eq!( + exports.roundtrip_flags1(&mut store, F1::A | F1::B)?, + F1::A | F1::B + ); + + assert_eq!(exports.roundtrip_flags2(&mut store, F2::C)?, F2::C); + assert_eq!( + exports.roundtrip_flags2(&mut store, F2::empty())?, + F2::empty() + ); + assert_eq!(exports.roundtrip_flags2(&mut store, F2::D)?, F2::D); + assert_eq!( + exports.roundtrip_flags2(&mut store, F2::C | F2::E)?, + F2::C | F2::E + ); + + let r = exports.roundtrip_record1( + &mut store, + R1 { + a: 8, + b: F1::empty(), + }, + )?; + assert_eq!(r.a, 8); + assert_eq!(r.b, F1::empty()); + + let r = exports.roundtrip_record1( + &mut store, + R1 { + a: 0, + b: F1::A | F1::B, + }, + )?; + assert_eq!(r.a, 0); + assert_eq!(r.b, F1::A | F1::B); + + assert_eq!(exports.tuple0(&mut store, ())?, ()); + assert_eq!(exports.tuple1(&mut store, (1,))?, (1,)); + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.ts new file mode 100644 index 0000000..51184a7 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/host.ts @@ -0,0 +1,57 @@ +import { addImportsToImports, Imports } from "./imports.js"; +import { Exports } from "./exports.js"; +import * as exports from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; +// @ts-ignore +import * as assert from 'assert'; + +async function run() { + const importObj = {}; + const imports: Imports = { + multipleResults() { return [4, 5]; }, + swapTuple([a, b]) { return [b, a]; }, + roundtripFlags1(x) { return x; }, + roundtripFlags2(x) { return x; }, + roundtripFlags3(r0, r1, r2, r3) { return [r0, r1, r2, r3]; }, + roundtripRecord1(x) { return x; }, + tuple0([]) { return []; }, + tuple1([x]) { return [x]; }, + }; + let instance: WebAssembly.Instance; + addImportsToImports(importObj, imports, name => instance.exports[name]); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + instance = wasm.instance; + + wasm.testImports(); + assert.deepEqual(wasm.multipleResults(), [100, 200]); + assert.deepStrictEqual(wasm.swapTuple([1, 2]), [2, 1]); + assert.deepEqual(wasm.roundtripFlags1(exports.F1_A), exports.F1_A); + assert.deepEqual(wasm.roundtripFlags1(0), 0); + assert.deepEqual(wasm.roundtripFlags1(exports.F1_A | exports.F1_B), exports.F1_A | exports.F1_B); + + assert.deepEqual(wasm.roundtripFlags2(exports.F2_C), exports.F2_C); + assert.deepEqual(wasm.roundtripFlags2(0), 0); + assert.deepEqual(wasm.roundtripFlags2(exports.F2_D), exports.F2_D); + assert.deepEqual(wasm.roundtripFlags2(exports.F2_C | exports.F2_E), exports.F2_C | exports.F2_E); + + { + const { a, b } = wasm.roundtripRecord1({ a: 8, b: 0 }); + assert.deepEqual(a, 8); + assert.deepEqual(b, 0); + } + + { + const { a, b } = wasm.roundtripRecord1({ a: 0, b: exports.F1_A | exports.F1_B }); + assert.deepEqual(a, 0); + assert.deepEqual(b, exports.F1_A | exports.F1_B); + } + + assert.deepStrictEqual(wasm.tuple0([]), []); + assert.deepStrictEqual(wasm.tuple1([1]), [1]); +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/imports.wit new file mode 100644 index 0000000..304fa31 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/imports.wit @@ -0,0 +1,44 @@ +multiple-results: func() -> tuple + +swap-tuple: func(a: tuple) -> tuple + +flags f1 { a, b } +roundtrip-flags1: func(a: f1) -> f1 + +flags f2 { c, d, e } +roundtrip-flags2: func(a: f2) -> f2 + +flags flag8 { + b0, b1, b2, b3, b4, b5, b6, b7, +} + +flags flag16 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, +} + +flags flag32 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, +} + +flags flag64 { + b0, b1, b2, b3, b4, b5, b6, b7, + b8, b9, b10, b11, b12, b13, b14, b15, + b16, b17, b18, b19, b20, b21, b22, b23, + b24, b25, b26, b27, b28, b29, b30, b31, + b32, b33, b34, b35, b36, b37, b38, b39, + b40, b41, b42, b43, b44, b45, b46, b47, + b48, b49, b50, b51, b52, b53, b54, b55, + b56, b57, b58, b59, b60, b61, b62, b63, +} + +roundtrip-flags3: func(a: flag8, b: flag16, c: flag32, d: flag64) -> tuple + +record r1 { a: u8, b: f1 } +roundtrip-record1: func(a: r1) -> r1 + +tuple0: func(a: tuple<>) -> tuple<> +tuple1: func(a: tuple) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/wasm.c new file mode 100644 index 0000000..0b62452 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/wasm.c @@ -0,0 +1,106 @@ +#include +#include +#include + +void exports_test_imports() { + { + uint8_t a; + uint16_t b; + imports_multiple_results(&a, &b); + assert(a == 4); + assert(b == 5); + } + + imports_tuple2_u8_u32_t input; + input.f0 = 1; + input.f1 = 2; + uint32_t a; + uint8_t b; + imports_swap_tuple(&input, &a, &b); + assert(a == 2); + assert(b == 1); + + assert(imports_roundtrip_flags1(IMPORTS_F1_A) == IMPORTS_F1_A); + assert(imports_roundtrip_flags1(0) == 0); + assert(imports_roundtrip_flags1(IMPORTS_F1_B) == IMPORTS_F1_B); + assert(imports_roundtrip_flags1(IMPORTS_F1_A | IMPORTS_F1_B) == (IMPORTS_F1_A | IMPORTS_F1_B)); + + assert(imports_roundtrip_flags2(IMPORTS_F2_C) == IMPORTS_F2_C); + assert(imports_roundtrip_flags2(0) == 0); + assert(imports_roundtrip_flags2(IMPORTS_F2_D) == IMPORTS_F2_D); + assert(imports_roundtrip_flags2(IMPORTS_F2_C | IMPORTS_F2_E) == (IMPORTS_F2_C | IMPORTS_F2_E)); + + imports_flag8_t flag8; + imports_flag16_t flag16; + imports_flag32_t flag32; + imports_flag64_t flag64; + imports_roundtrip_flags3(IMPORTS_FLAG8_B0, IMPORTS_FLAG16_B1, IMPORTS_FLAG32_B2, IMPORTS_FLAG64_B3, + &flag8, &flag16, &flag32, &flag64); + assert(flag8 == IMPORTS_FLAG8_B0); + assert(flag16 == IMPORTS_FLAG16_B1); + assert(flag32 == IMPORTS_FLAG32_B2); + assert(flag64 == IMPORTS_FLAG64_B3); + + { + imports_r1_t a, b; + a.a = 8; + a.b = 0; + imports_roundtrip_record1(&a, &b); + assert(b.a == 8); + assert(b.b == 0); + } + + { + imports_r1_t a, b; + a.a = 0; + a.b = IMPORTS_F1_A | IMPORTS_F1_B; + imports_roundtrip_record1(&a, &b); + assert(b.a == 0); + assert(b.b == (IMPORTS_F1_A | IMPORTS_F1_B)); + } + + imports_tuple0_t t0; + imports_tuple0(&t0); + + imports_tuple1_u8_t t1; + t1.f0 = 1; + uint8_t ret; + imports_tuple1(&t1, &ret); + assert(ret == 1); +} + +void exports_multiple_results(uint8_t *ret0, uint16_t *ret1) { + *ret0 = 100; + *ret1 = 200; +} + +void exports_swap_tuple(exports_tuple2_u8_u32_t *a, uint32_t *ret0, uint8_t *ret1) { + *ret0 = a->f1; + *ret1 = a->f0; +} + +exports_f1_t exports_roundtrip_flags1(exports_f1_t a) { + return a; +} + +exports_f2_t exports_roundtrip_flags2(exports_f2_t a) { + return a; +} + +void exports_roundtrip_flags3(exports_f8_t a, exports_f16_t b, exports_f32_t c, exports_f64_t d, exports_f8_t *ret0, exports_f16_t *ret1, exports_f32_t *ret2, exports_f64_t *ret3) { + *ret0 = a; + *ret1 = b; + *ret2 = c; + *ret3 = d; +} + +void exports_roundtrip_record1(exports_r1_t *a, exports_r1_t *ret0) { + *ret0 = *a; +} + +void exports_tuple0(exports_tuple0_t *a) { +} + +void exports_tuple1(exports_tuple1_u8_t *a, uint8_t *ret0) { + *ret0 = a->f0; +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/wasm.rs new file mode 100644 index 0000000..e9484d8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/records/wasm.rs @@ -0,0 +1,77 @@ +wit_bindgen_rust::import!("../../tests/runtime/records/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/records/exports.wit"); + +use exports::*; + +struct Exports; + +impl exports::Exports for Exports { + fn test_imports() { + use imports::*; + + assert_eq!(multiple_results(), (4, 5)); + + assert_eq!(swap_tuple((1u8, 2u32)), (2u32, 1u8)); + assert_eq!(roundtrip_flags1(F1::A), F1::A); + assert_eq!(roundtrip_flags1(F1::empty()), F1::empty()); + assert_eq!(roundtrip_flags1(F1::B), F1::B); + assert_eq!(roundtrip_flags1(F1::A | F1::B), F1::A | F1::B); + + assert_eq!(roundtrip_flags2(F2::C), F2::C); + assert_eq!(roundtrip_flags2(F2::empty()), F2::empty()); + assert_eq!(roundtrip_flags2(F2::D), F2::D); + assert_eq!(roundtrip_flags2(F2::C | F2::E), F2::C | F2::E); + + assert_eq!( + roundtrip_flags3(Flag8::B0, Flag16::B1, Flag32::B2, Flag64::B3), + (Flag8::B0, Flag16::B1, Flag32::B2, Flag64::B3) + ); + + let r = roundtrip_record1(R1 { + a: 8, + b: F1::empty(), + }); + assert_eq!(r.a, 8); + assert_eq!(r.b, F1::empty()); + + let r = roundtrip_record1(R1 { + a: 0, + b: F1::A | F1::B, + }); + assert_eq!(r.a, 0); + assert_eq!(r.b, F1::A | F1::B); + + assert_eq!(tuple0(()), ()); + assert_eq!(tuple1((1,)), (1,)); + } + + fn multiple_results() -> (u8, u16) { + (100, 200) + } + + fn swap_tuple(a: (u8, u32)) -> (u32, u8) { + (a.1, a.0) + } + + fn roundtrip_flags1(a: F1) -> F1 { + a + } + + fn roundtrip_flags2(a: F2) -> F2 { + a + } + + fn roundtrip_flags3(a: F8, b: F16, c: F32, d: F64) -> (F8, F16, F32, F64) { + (a, b, c, d) + } + + fn roundtrip_record1(a: R1) -> R1 { + a + } + + fn tuple0(_: ()) {} + + fn tuple1(a: (u8,)) -> (u8,) { + (a.0,) + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/exports.wit new file mode 100644 index 0000000..ac15d82 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/exports.wit @@ -0,0 +1 @@ +thunk: func() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.py new file mode 100644 index 0000000..38fa86c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.py @@ -0,0 +1,27 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +import sys +import wasmtime + +class MyImports: + def thunk(self) -> None: + self.hit = True + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + wasm.thunk(store) + assert(imports.hit) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.rs new file mode 100644 index 0000000..2f93d0c --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.rs @@ -0,0 +1,33 @@ +use anyhow::Result; + +wit_bindgen_wasmtime::export!("../../tests/runtime/smoke/imports.wit"); + +#[derive(Default)] +pub struct MyImports { + hit: bool, +} + +impl imports::Imports for MyImports { + fn thunk(&mut self) { + self.hit = true; + println!("in the host"); + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/smoke/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + let (exports, mut store) = crate::instantiate( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut MyImports { &mut cx.imports }), + |store, module, linker| { + exports::Exports::instantiate(store, module, linker, |cx| &mut cx.exports) + }, + )?; + + exports.thunk(&mut store)?; + + assert!(store.data().imports.hit); + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.ts new file mode 100644 index 0000000..0cdb09a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/host.ts @@ -0,0 +1,28 @@ +import { addImportsToImports, Imports } from "./imports.js"; +import { Exports } from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; + +function assert(x: boolean, msg: string) { + if (!x) + throw new Error(msg); +} + +async function run() { + const importObj = {}; + let hit = false; + addImportsToImports(importObj, { + thunk() { + hit = true; + } + }); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + + wasm.thunk(); + assert(hit, "import not called"); +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/imports.wit new file mode 100644 index 0000000..ac15d82 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/imports.wit @@ -0,0 +1 @@ +thunk: func() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/wasm.c new file mode 100644 index 0000000..fd6927b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/wasm.c @@ -0,0 +1,6 @@ +#include +#include + +void exports_thunk() { + imports_thunk(); +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/wasm.rs new file mode 100644 index 0000000..69e66a8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smoke/wasm.rs @@ -0,0 +1,10 @@ +wit_bindgen_rust::import!("../../tests/runtime/smoke/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/smoke/exports.wit"); + +struct Exports; + +impl exports::Exports for Exports { + fn thunk() { + imports::thunk(); + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/exports.wit new file mode 100644 index 0000000..802fcdb --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/exports.wit @@ -0,0 +1,12 @@ +test-imports: func() + +f1: func() +f2: func(a: u32) +f3: func(a: u32, b: u32) + +f4: func() -> u32 + +// TODO: shoudl re-enable when re-implemented +//f5: func() -> tuple +// +//f6: func(a: u32, b: u32, c: u32) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/ignore_host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/ignore_host.rs new file mode 100644 index 0000000..bc56fd5 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/ignore_host.rs @@ -0,0 +1,127 @@ +use anyhow::Context; + +wit_bindgen_wasmtime::export!("../../tests/runtime/smw_functions/imports.wit"); + +#[derive(Default)] +pub struct Host { + pub f1_called: bool, + pub f2_arg: u32, + pub f3_a: u32, + pub f3_b: u32, + pub f4_called: bool, + // pub f5_called: bool, + // pub f6_a: u32, + // pub f6_b: u32, + // pub f6_c: u32, +} + +impl imports::Imports for Host { + fn f1(&mut self) { + self.f1_called = true; + } + + fn f2(&mut self, arg: u32) { + self.f2_arg = arg; + } + + fn f3(&mut self, a: u32, b: u32) { + self.f3_a = a; + self.f3_b = b; + } + + fn f4(&mut self) -> u32 { + self.f4_called = true; + 1337 + } + + // fn f5(&mut self) -> (u32, u32) { + // self.f5_called = true; + // (1, 2) + // } + + // fn f6(&mut self, a: u32, b: u32, c: u32) -> (u32, u32, u32) { + // self.f6_a = a; + // self.f6_b = b; + // self.f6_c = c; + // (a + 1, b + 1, c + 1) + // } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/smw_functions/exports.wit"); + +fn run(wasm: &str) -> anyhow::Result<()> { + let (exports, mut store) = crate::instantiate_smw( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut Host { &mut cx.imports }), + |store, module, linker| { + exports::Exports::instantiate(store, module, linker, |cx| &mut cx.exports) + }, + )?; + + // Test that the import instance called the functions we made available with + // the expected arguments. + + exports.test_imports(&mut store)?; + + assert!( + store.data().imports.f1_called, + "top-level JS imported and called `f1`", + ); + + assert_eq!( + store.data().imports.f2_arg, + 42, + "f2 should have been called with 42", + ); + + assert_eq!(store.data().imports.f3_a, 0); + assert_eq!(store.data().imports.f3_b, u32::MAX); + + assert!( + store.data().imports.f4_called, + "the top-level JS imported and called `f4`", + ); + + // assert!( + // store.data().imports.f5_called, + // "the top-level JS imported and called `f5`" + // ); + + // assert_eq!(store.data().imports.f6_a, 100); + // assert_eq!(store.data().imports.f6_b, 200); + // assert_eq!(store.data().imports.f6_c, 300); + + // Test that the export instance behaves as we expect it to. + + exports + .f1(&mut store) + .context("calling the `f1` export should succeed")?; + + exports + .f2(&mut store, 42) + .context("calling the `f2` export should succeed")?; + + exports + .f3(&mut store, 0, u32::MAX) + .context("calling the `f3` export should succeed")?; + + let a = exports + .f4(&mut store) + .context("calling the `f4` export should succeed")?; + assert_eq!(a, 1337); + + // let (a, b) = exports + // .f5(&mut store) + // .context("calling the `f5` export should succeed")?; + // assert_eq!(a, 1); + // assert_eq!(b, 2); + + // let (a, b, c) = exports + // .f6(&mut store, 100, 200, 300) + // .context("calling the `f6` export should succeed")?; + // assert_eq!(a, 101); + // assert_eq!(b, 201); + // assert_eq!(c, 301); + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/imports.wit new file mode 100644 index 0000000..e3fe649 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/imports.wit @@ -0,0 +1,9 @@ +f1: func() +f2: func(a: u32) +f3: func(a: u32, b: u32) + +f4: func() -> u32 +// TODO: should re-enable when re-implemented +//f5: func() -> tuple +// +//f6: func(a: u32, b: u32, c: u32) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/wasm.js b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/wasm.js new file mode 100644 index 0000000..21adadf --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_functions/wasm.js @@ -0,0 +1,83 @@ +import * as imports from "imports"; + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +function assertEq(a, b) { + assert(a == b, `assertEq failed: ${a} != ${b}`); +} + +export function test_imports() { + // const { f1, f2, f3, f4, f5, f6 } = imports; + const { f1, f2, f3, f4 } = imports; + + // + // Testing arguments. + // + + f1(); + + f2(42); + + // Min and max `u32`. + f3(0, 4294967295); + + // + // Testing returns. + // + + { + const a = f4(); + assertEq(a, 1337); + } + + // { + // const [a, b] = f5(); + // assertEq(a, 1); + // assertEq(b, 2); + // } + + // { + // const [a, b, c] = f6(100, 200, 300); + // assertEq(a, 101); + // assertEq(b, 201); + // assertEq(c, 301); + // } +} + +// +// Testing arguments. +// + +export function f1() {} + +export function f2(x) { + assertEq(x, 42); +} + +export function f3(a, b) { + assertEq(a, 0); + assertEq(b, 4294967295); +} + +// +// Testing returns. +// + +export function f4() { + return 1337; +} + +export function f5() { + return [1, 2]; +} + +export function f6(a, b, c) { + assertEq(a, 100); + assertEq(b, 200); + assertEq(c, 300); + return [a + 1, b + 1, c + 1]; +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/exports.wit new file mode 100644 index 0000000..f87a89b --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/exports.wit @@ -0,0 +1,7 @@ +test-imports: func() + +f1: func(l: list) +f2: func() -> list +// TODO: should re-enable when re-implemented +// f3: func(a: list, b: list) -> tuple, list> +f4: func(l: list>) -> list> diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/ignore_host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/ignore_host.rs new file mode 100644 index 0000000..00c619d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/ignore_host.rs @@ -0,0 +1,88 @@ +use anyhow::Context; +use wit_bindgen_wasmtime::Le; + +wit_bindgen_wasmtime::export!("../../tests/runtime/smw_lists/imports.wit"); + +#[derive(Default)] +pub struct Host { + pub f1_l: Vec, + pub f2_called: bool, + // pub f3_a: Vec, + // pub f3_b: Vec, + pub f4_l: Vec>, +} + +impl imports::Imports for Host { + fn f1(&mut self, l: &[Le]) { + self.f1_l = l.iter().map(|le| le.get()).collect(); + } + + fn f2(&mut self) -> Vec { + self.f2_called = true; + vec![1, 2, 3] + } + + // fn f3(&mut self, a: &[Le], b: &[Le]) -> (Vec, Vec) { + // self.f3_a = a.iter().map(|le| le.get()).collect(); + // self.f3_b = b.iter().map(|le| le.get()).collect(); + // (vec![], vec![1, 2, 3]) + // } + + fn f4(&mut self, l: Vec<&[Le]>) -> Vec> { + self.f4_l = l + .into_iter() + .map(|xs| xs.iter().map(|le| le.get()).collect()) + .collect(); + vec![vec![], vec![4], vec![5, 6]] + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/smw_lists/exports.wit"); + +fn run(wasm: &str) -> anyhow::Result<()> { + let (exports, mut store) = crate::instantiate_smw( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut Host { &mut cx.imports }), + |store, module, linker| { + exports::Exports::instantiate(store, module, linker, |cx| &mut cx.exports) + }, + )?; + + // Test that the import instance called the functions we made available with + // the expected arguments. + + exports.test_imports(&mut store)?; + + assert_eq!(store.data().imports.f1_l, vec![1, 2, 3]); + + assert!(store.data().imports.f2_called); + + // assert_eq!(store.data().imports.f3_a, vec![]); + // assert_eq!(store.data().imports.f3_b, vec![1, 2, 3]); + + assert_eq!(store.data().imports.f4_l, vec![vec![], vec![1], vec![2, 3]]); + + // Test that the export instance behaves as we expect it to. + + exports + .f1(&mut store, &[1, 2, 3]) + .context("calling the `f1` export should succeed")?; + + let l = exports + .f2(&mut store) + .context("calling the `f2` export should succeed")?; + assert_eq!(l, vec![1, 2, 3]); + + // let (a, b) = exports + // .f3(&mut store, &[], &[1, 2, 3]) + // .context("calling the `f3` export should succeed")?; + // assert_eq!(a, vec![]); + // assert_eq!(b, vec![1, 2, 3]); + + let l = exports + .f4(&mut store, &[&[], &[1], &[2, 3]]) + .context("calling the `f4` export should succeed")?; + assert_eq!(l, vec![vec![], vec![4], vec![5, 6]]); + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/imports.wit new file mode 100644 index 0000000..19bf5db --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/imports.wit @@ -0,0 +1,5 @@ +f1: func(l: list) +f2: func() -> list +// TODO: should re-enable when re-implemented +// f3: func(a: list, b: list) -> tuple, list> +f4: func(l: list>) -> list> diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/wasm.js b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/wasm.js new file mode 100644 index 0000000..e55cdf6 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_lists/wasm.js @@ -0,0 +1,77 @@ +import * as imports from "imports"; + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +function assertEq(a, b) { + assert(a == b, `assertEq failed: ${a} != ${b}`); +} + +export function test_imports() { + // const { f1, f2, f3, f4 } = imports; + const { f1, f2, f4 } = imports; + f1([1, 2, 3]); + + const l = f2(); + assertEq(l.length, 3); + assertEq(l[0], 1); + assertEq(l[1], 2); + assertEq(l[2], 3); + + // const [a, b] = f3([], [1, 2, 3]); + // assertEq(a.length, 0); + // assertEq(b.length, 3); + // assertEq(b[0], 1); + // assertEq(b[1], 2); + // assertEq(b[2], 3); + + const l2 = f4([[], [1], [2, 3]]); + assertEq(l2.length, 3); + assertEq(l2[0].length, 0); + assertEq(l2[1].length, 1); + assertEq(l2[1][0], 4); + assertEq(l2[2].length, 2); + assertEq(l2[2][0], 5); + assertEq(l2[2][1], 6); +} + +export function f1(l) { + assertEq(l.length, 3); + assertEq(l[0], 1); + assertEq(l[1], 2); + assertEq(l[2], 3); +} + +export function f2() { + return [1, 2, 3]; +} + +export function f3(a, b) { + assertEq(a.length, 0); + assertEq(b.length, 3); + assertEq(b[0], 1); + assertEq(b[1], 2); + assertEq(b[2], 3); + return [ + [], + [1, 2, 3] + ]; +} + +export function f4(l) { + assertEq(l.length, 3); + assertEq(l[0].length, 0); + assertEq(l[1].length, 1); + assertEq(l[1][0], 1); + assertEq(l[2].length, 2); + assertEq(l[2][0], 2); + assertEq(l[2][1], 3); + return [ + [], + [4], + [5, 6] + ]; +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/exports.wit new file mode 100644 index 0000000..9c1292e --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/exports.wit @@ -0,0 +1,7 @@ +test-imports: func() + +f1: func(s: string) +f2: func() -> string + +// TODO: should re-enable when fixed +//f3: func(a: string, b:string, c: string) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/ignore_host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/ignore_host.rs new file mode 100644 index 0000000..4f404cd --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/ignore_host.rs @@ -0,0 +1,75 @@ +use anyhow::Context; + +wit_bindgen_wasmtime::export!("../../tests/runtime/smw_strings/imports.wit"); + +#[derive(Default)] +pub struct Host { + pub f1_s: String, + pub f2_called: bool, + // pub f3_a: String, + // pub f3_b: String, + // pub f3_c: String, +} + +impl imports::Imports for Host { + fn f1(&mut self, s: &str) { + self.f1_s = s.to_string(); + } + + fn f2(&mut self) -> String { + self.f2_called = true; + "36 chambers".into() + } + + // fn f3(&mut self, a: &str, b: &str, c: &str) -> (String, String, String) { + // self.f3_a = a.into(); + // self.f3_b = b.into(); + // self.f3_c = c.into(); + // (a.into(), b.into(), c.into()) + // } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/smw_strings/exports.wit"); + +fn run(wasm: &str) -> anyhow::Result<()> { + let (exports, mut store) = crate::instantiate_smw( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut Host { &mut cx.imports }), + |store, module, linker| { + exports::Exports::instantiate(store, module, linker, |cx| &mut cx.exports) + }, + )?; + + // Test that the import instance called the functions we made available with + // the expected arguments. + + exports.test_imports(&mut store)?; + + assert_eq!(store.data().imports.f1_s, "Hello, WIT!"); + + assert!(store.data().imports.f2_called, "JS should have called `f2`"); + + // assert_eq!(store.data().imports.f3_a, ""); + // assert_eq!(store.data().imports.f3_b, "🚀"); + // assert_eq!(store.data().imports.f3_c, "hello"); + + // Test that the export instance behaves as we expect it to. + + exports + .f1(&mut store, "Hello, WIT!") + .context("calling the `f1` export should succeed")?; + + let s = exports + .f2(&mut store) + .context("calling the `f2` export should succeed")?; + assert_eq!(s, "36 chambers"); + + // let (a, b, c) = exports + // .f3(&mut store, "", "🚀", "hello") + // .context("calling the `f3` export should succeed")?; + // assert_eq!(a, ""); + // assert_eq!(b, "🚀"); + // assert_eq!(c, "hello"); + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/imports.wit new file mode 100644 index 0000000..56c81b1 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/imports.wit @@ -0,0 +1,4 @@ +f1: func(s: string) +f2: func() -> string +// TODO: should re-enable when fixed +//f3: func(a: string, b:string, c: string) -> tuple diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/wasm.js b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/wasm.js new file mode 100644 index 0000000..053be8f --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/smw_strings/wasm.js @@ -0,0 +1,40 @@ +import * as imports from "imports"; + +function assert(condition, message) { + if (!condition) { + throw new Error(message); + } +} + +function assertEq(a, b) { + assert(a == b, `assertEq failed: ${a} != ${b}`); +} + +export function test_imports() { + const { f1, f2 } = imports; + // const { f1, f2, f3 } = imports; + f1("Hello, WIT!"); + + const s = f2(); + assertEq(s, "36 chambers"); + + // const [a, b, c] = f3("", "🚀", "hello"); + // assertEq(a, ""); + // assertEq(b, "🚀"); + // assertEq(c, "hello"); +} + +export function f1(s) { + assertEq(s, "Hello, WIT!"); +} + +export function f2() { + return "36 chambers"; +} + +export function f3(a, b, c) { + assertEq(a, ""); + assertEq(b, "🚀"); + assertEq(c, "hello"); + return [a, b, c]; +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/exports.wit new file mode 100644 index 0000000..c706c56 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/exports.wit @@ -0,0 +1,59 @@ +test-imports: func() + +/// A union of all of the integral types +union all-integers { + /// Bool is equivalent to a 1 bit integer + /// and is treated that way in some languages + bool, + u8, u16, u32, u64, + s8, s16, s32, s64 +} +union all-floats { + float32, float64 +} +union all-text { + char, string +} + +// Returns the same case as the input but with 1 added +add-one-integer: func(num: all-integers) -> all-integers +// Returns the same case as the input but with 1 added +add-one-float: func(num: all-floats) -> all-floats +// Returns the same case as the input but with the first character replaced +replace-first-char: func(text: all-text, letter: char) -> all-text + +// Returns the index of the case provided +identify-integer: func(num: all-integers) -> u8 +// Returns the index of the case provided +identify-float: func(num: all-floats) -> u8 +// Returns the index of the case provided +identify-text: func(text: all-text) -> u8 + +union duplicated-s32 { + /// The first s32 + s32, + /// The second s32 + s32, + /// The third s32 + s32 +} + +// Returns the same case as the input but with 1 added +add-one-duplicated: func(num: duplicated-s32) -> duplicated-s32 + +// Returns the index of the case provided +identify-duplicated: func(num: duplicated-s32) -> u8 + +/// A type containing numeric types that are distinct in most languages +union distinguishable-num { + /// A Floating Point Number + float64, + /// A Signed Integer + s64 +} + +// Returns the same case as the input but with 1 added +add-one-distinguishable-num: func(num: distinguishable-num) -> distinguishable-num + +// Returns the index of the case provided +identify-distinguishable-num: func(num: distinguishable-num) -> u8 \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/host.py new file mode 100644 index 0000000..bba92b8 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/host.py @@ -0,0 +1,237 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +from typing import Union +import exports.bindings as e +import imports.bindings as i +import sys +import wasmtime + +class MyImports: + # Simple uses of unions whose inner values all have the same Python representation + def add_one_integer(self, num: i.AllIntegers) -> i.AllIntegers: + # Bool + if isinstance(num, i.AllIntegers0): + assert num.value in (True, False) + return i.AllIntegers0(not num.value) + # The unsigned numbers + elif isinstance(num, i.AllIntegers1): + lower_limit = 0 + upper_limit = 2**8 + assert lower_limit <= num.value < upper_limit + return i.AllIntegers1(num.value + 1 % upper_limit) + elif isinstance(num, i.AllIntegers2): + lower_limit = 0 + upper_limit = 2**16 + assert lower_limit <= num.value < upper_limit + return i.AllIntegers2(num.value + 1 % upper_limit) + elif isinstance(num, i.AllIntegers3): + lower_limit = 0 + upper_limit = 2**32 + assert lower_limit <= num.value < upper_limit + return i.AllIntegers3(num.value + 1 % upper_limit) + elif isinstance(num, i.AllIntegers4): + lower_limit = 0 + upper_limit = 2**64 + assert lower_limit <= num.value < upper_limit + return i.AllIntegers4(num.value + 1 % upper_limit) + # The signed numbers + elif isinstance(num, i.AllIntegers5): + lower_limit = -2**7 + upper_limit = 2**7 + assert lower_limit <= num.value < upper_limit + return i.AllIntegers5(num.value + 1 % upper_limit) + elif isinstance(num, i.AllIntegers6): + lower_limit = -2**15 + upper_limit = 2**15 + assert lower_limit <= num.value < upper_limit + return i.AllIntegers6(num.value + 1 % upper_limit) + elif isinstance(num, i.AllIntegers7): + lower_limit = -2**31 + upper_limit = 2**31 + assert lower_limit <= num.value < upper_limit + return i.AllIntegers7(num.value + 1 % upper_limit) + elif isinstance(num, i.AllIntegers8): + lower_limit = -2**63 + upper_limit = 2**63 + assert lower_limit <= num.value < upper_limit + return i.AllIntegers8(num.value + 1 % upper_limit) + else: + raise ValueError("Invalid input value!") + + def add_one_float(self, num: i.AllFloats) -> i.AllFloats: + if isinstance(num, i.AllFloats0): + return i.AllFloats0(num.value + 1) + if isinstance(num, i.AllFloats1): + return i.AllFloats1(num.value + 1) + else: + raise ValueError("Invalid input value!") + + def replace_first_char(self, text: i.AllText, letter: str) -> i.AllText: + if isinstance(text, i.AllText0): + return i.AllText0(letter) + if isinstance(text, i.AllFloats1): + return i.AllText1(letter + text.value[1:]) + else: + raise ValueError("Invalid input value!") + + # Identify each case of unions whose inner values all have the same Python representation + def identify_integer(self, num: i.AllIntegers) -> int: + # Bool + if isinstance(num, i.AllIntegers0): + return 0 + # The unsigned numbers + elif isinstance(num, i.AllIntegers1): + return 1 + elif isinstance(num, i.AllIntegers2): + return 2 + elif isinstance(num, i.AllIntegers3): + return 3 + elif isinstance(num, i.AllIntegers4): + return 4 + # The signed numbers + elif isinstance(num, i.AllIntegers5): + return 5 + elif isinstance(num, i.AllIntegers6): + return 6 + elif isinstance(num, i.AllIntegers7): + return 7 + elif isinstance(num, i.AllIntegers8): + return 8 + else: + raise ValueError("Invalid input value!") + + def identify_float(self, num: i.AllFloats) -> int: + if isinstance(num, i.AllFloats0): + return 0 + if isinstance(num, i.AllFloats1): + return 1 + else: + raise ValueError("Invalid input value!") + + def identify_text(self, text: i.AllText) -> int: + if isinstance(text, i.AllText0): + return 0 + if isinstance(text, i.AllFloats1): + return 1 + else: + raise ValueError("Invalid input value!") + + # A simple use of a union which contains multiple entries of the same type + def add_one_duplicated(self, num: i.DuplicatedS32) -> i.DuplicatedS32: + if isinstance(num, i.DuplicatedS320): + return i.DuplicatedS320(num.value + 1) + if isinstance(num, i.DuplicatedS321): + return i.DuplicatedS321(num.value + 1) + if isinstance(num, i.DuplicatedS322): + return i.DuplicatedS322(num.value + 1) + else: + raise ValueError("Invalid input value!") + + # Identify each case of unions which contains multiple entries of the same type + def identify_duplicated(self, num: i.DuplicatedS32) -> int: + if isinstance(num, i.DuplicatedS320): + return 0 + if isinstance(num, i.DuplicatedS321): + return 1 + if isinstance(num, i.DuplicatedS322): + return 2 + else: + raise ValueError("Invalid input value!") + + # A simple use of a union whose cases have distinct Python representations + def add_one_distinguishable_num(self, num: Union[float, int]) -> Union[float, int]: + return num + 1 + + # Identify each case of unions whose cases have distinct Python representations + def identify_distinguishable_num(self, num: i.DistinguishableNum) -> int: + if isinstance(num, float): + return 0 + elif isinstance(num, int): + return 1 + else: + raise ValueError("Invalid input value!") + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + # wasm.test_imports(store) + + # All-Integers + # Booleans + assert wasm.add_one_integer(store, e.AllIntegers0(False)) == e.AllIntegers0(True) + assert wasm.add_one_integer(store, e.AllIntegers0(True)) == e.AllIntegers0(False) + # Unsigned integers + assert wasm.add_one_integer(store, e.AllIntegers1(0)) == e.AllIntegers1(1) + assert wasm.add_one_integer(store, e.AllIntegers1(2**8-1)) == e.AllIntegers1(0) + assert wasm.add_one_integer(store, e.AllIntegers2(0)) == e.AllIntegers2(1) + assert wasm.add_one_integer(store, e.AllIntegers2(2**16-1)) == e.AllIntegers2(0) + assert wasm.add_one_integer(store, e.AllIntegers3(0)) == e.AllIntegers3(1) + assert wasm.add_one_integer(store, e.AllIntegers3(2**32-1)) == e.AllIntegers3(0) + assert wasm.add_one_integer(store, e.AllIntegers4(0)) == e.AllIntegers4(1) + assert wasm.add_one_integer(store, e.AllIntegers4(2**64-1)) == e.AllIntegers4(0) + # Signed integers + assert wasm.add_one_integer(store, e.AllIntegers5(0)) == e.AllIntegers5(1) + assert wasm.add_one_integer(store, e.AllIntegers5(2**7-1)) == e.AllIntegers5(-2**7) + assert wasm.add_one_integer(store, e.AllIntegers6(0)) == e.AllIntegers6(1) + assert wasm.add_one_integer(store, e.AllIntegers6(2**15-1)) == e.AllIntegers6(-2**15) + assert wasm.add_one_integer(store, e.AllIntegers7(0)) == e.AllIntegers7(1) + assert wasm.add_one_integer(store, e.AllIntegers7(2**31-1)) == e.AllIntegers7(-2**31) + assert wasm.add_one_integer(store, e.AllIntegers8(0)) == e.AllIntegers8(1) + assert wasm.add_one_integer(store, e.AllIntegers8(2**63-1)) == e.AllIntegers8(-2**63) + + # All-Floats + assert wasm.add_one_float(store, e.AllFloats0(0.0)) == e.AllFloats0(1.0) + assert wasm.add_one_float(store, e.AllFloats1(0.0)) == e.AllFloats1(1.0) + + # All-Text + assert wasm.replace_first_char(store, e.AllText0('a'), 'z') == e.AllText0('z') + assert wasm.replace_first_char(store, e.AllText1('abc'), 'z') == e.AllText1('zbc') + + # All-Integers + assert wasm.identify_integer(store, e.AllIntegers0(True)) == 0 + assert wasm.identify_integer(store, e.AllIntegers1(0)) == 1 + assert wasm.identify_integer(store, e.AllIntegers2(0)) == 2 + assert wasm.identify_integer(store, e.AllIntegers3(0)) == 3 + assert wasm.identify_integer(store, e.AllIntegers4(0)) == 4 + assert wasm.identify_integer(store, e.AllIntegers5(0)) == 5 + assert wasm.identify_integer(store, e.AllIntegers6(0)) == 6 + assert wasm.identify_integer(store, e.AllIntegers7(0)) == 7 + assert wasm.identify_integer(store, e.AllIntegers8(0)) == 8 + + # All-Floats + assert wasm.identify_float(store, e.AllFloats0(0.0)) == 0 + assert wasm.identify_float(store, e.AllFloats1(0.0)) == 1 + + # All-Text + assert wasm.identify_text(store, e.AllText0('a')) == 0 + assert wasm.identify_text(store, e.AllText1('abc')) == 1 + + # Duplicated + assert wasm.add_one_duplicated(store, e.DuplicatedS320(0)) == e.DuplicatedS320(1) + assert wasm.add_one_duplicated(store, e.DuplicatedS321(1)) == e.DuplicatedS321(2) + assert wasm.add_one_duplicated(store, e.DuplicatedS322(2)) == e.DuplicatedS322(3) + + assert wasm.identify_duplicated(store, e.DuplicatedS320(0)) == 0 + assert wasm.identify_duplicated(store, e.DuplicatedS321(0)) == 1 + assert wasm.identify_duplicated(store, e.DuplicatedS322(0)) == 2 + + # Distinguishable + assert wasm.add_one_distinguishable_num(store, 0.0) == 1.0 + assert wasm.add_one_distinguishable_num(store, 0) == 1 + + assert wasm.identify_distinguishable_num(store, 0.0) == 0 + assert wasm.identify_distinguishable_num(store, 1) == 1 + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/imports.wit new file mode 100644 index 0000000..c7d3422 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/imports.wit @@ -0,0 +1,57 @@ +/// A union of all of the integral types +union all-integers { + /// Bool is equivalent to a 1 bit integer + /// and is treated that way in some languages + bool, + u8, u16, u32, u64, + s8, s16, s32, s64 +} +union all-floats { + float32, float64 +} +union all-text { + char, string +} + +// Returns the same case as the input but with 1 added +add-one-integer: func(num: all-integers) -> all-integers +// Returns the same case as the input but with 1 added +add-one-float: func(num: all-floats) -> all-floats +// Returns the same case as the input but with the first character replaced +replace-first-char: func(text: all-text, letter: char) -> all-text + +// Returns the index of the case provided +identify-integer: func(num: all-integers) -> u8 +// Returns the index of the case provided +identify-float: func(num: all-floats) -> u8 +// Returns the index of the case provided +identify-text: func(text: all-text) -> u8 + +union duplicated-s32 { + /// The first s32 + s32, + /// The second s32 + s32, + /// The third s32 + s32 +} + +// Returns the same case as the input but with 1 added +add-one-duplicated: func(num: duplicated-s32) -> duplicated-s32 + +// Returns the index of the case provided +identify-duplicated: func(num: duplicated-s32) -> u8 + +/// A type containing numeric types that are distinct in most languages +union distinguishable-num { + /// A Floating Point Number + float64, + /// A Signed Integer + s64 +} + +// Returns the same case as the input but with 1 added +add-one-distinguishable-num: func(num: distinguishable-num) -> distinguishable-num + +// Returns the index of the case provided +identify-distinguishable-num: func(num: distinguishable-num) -> u8 \ No newline at end of file diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/wasm.rs new file mode 100644 index 0000000..4527f73 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/unions/wasm.rs @@ -0,0 +1,171 @@ +wit_bindgen_rust::import!("../../tests/runtime/unions/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/unions/exports.wit"); + +use exports::*; + +struct Exports; + +impl exports::Exports for Exports { + fn test_imports() { + use imports::*; + + // All-Integers + // Booleans + assert!(matches!(add_one_integer(AllIntegers::Bool(false)), AllIntegers::Bool(true))); + assert!(matches!(add_one_integer(AllIntegers::Bool(true)), AllIntegers::Bool(false))); + // Unsigned integers + assert!(matches!(add_one_integer(AllIntegers::U8(0)), AllIntegers::U8(1))); + assert!(matches!(add_one_integer(AllIntegers::U8(u8::MAX)), AllIntegers::U8(0))); + assert!(matches!(add_one_integer(AllIntegers::U16(0)), AllIntegers::U16(1))); + assert!(matches!(add_one_integer(AllIntegers::U16(u16::MAX)), AllIntegers::U16(0))); + assert!(matches!(add_one_integer(AllIntegers::U32(0)), AllIntegers::U32(1))); + assert!(matches!(add_one_integer(AllIntegers::U32(u32::MAX)), AllIntegers::U32(0))); + assert!(matches!(add_one_integer(AllIntegers::U64(0)), AllIntegers::U64(1))); + assert!(matches!(add_one_integer(AllIntegers::U64(u64::MAX)), AllIntegers::U64(0))); + // Signed integers + assert!(matches!(add_one_integer(AllIntegers::I8(0)), AllIntegers::I8(1))); + assert!(matches!(add_one_integer(AllIntegers::I8(i8::MAX)), AllIntegers::I8(i8::MIN))); + assert!(matches!(add_one_integer(AllIntegers::I16(0)), AllIntegers::I16(1))); + assert!(matches!(add_one_integer(AllIntegers::I16(i16::MAX)), AllIntegers::I16(i16::MIN))); + assert!(matches!(add_one_integer(AllIntegers::I32(0)), AllIntegers::I32(1))); + assert!(matches!(add_one_integer(AllIntegers::I32(i32::MAX)), AllIntegers::I32(i32::MIN))); + assert!(matches!(add_one_integer(AllIntegers::I64(0)), AllIntegers::I64(1))); + assert!(matches!(add_one_integer(AllIntegers::I64(i64::MAX)), AllIntegers::I64(i64::MIN))); + + // All-Floats + assert!(matches!(add_one_float(AllFloats::F32(0.0)), AllFloats::F32(1.0))); + assert!(matches!(add_one_float(AllFloats::F64(0.0)), AllFloats::F64(1.0))); + + // All-Text + assert!(matches!(replace_first_char(AllTextParam::Char('a'), 'z'), AllTextResult::Char('z'))); + let rhs = "zbc".to_string(); + assert!(matches!(replace_first_char(AllTextParam::String("abc"), 'z'), AllTextResult::String(rhs))); + + // All-Integers + assert!(matches!(identify_integer(AllIntegers::Bool(true)), 0)); + assert!(matches!(identify_integer(AllIntegers::U8(0)), 1)); + assert!(matches!(identify_integer(AllIntegers::U16(0)), 2)); + assert!(matches!(identify_integer(AllIntegers::U32(0)), 3)); + assert!(matches!(identify_integer(AllIntegers::U64(0)), 4)); + assert!(matches!(identify_integer(AllIntegers::I8(0)), 5)); + assert!(matches!(identify_integer(AllIntegers::I16(0)), 6)); + assert!(matches!(identify_integer(AllIntegers::I32(0)), 7)); + assert!(matches!(identify_integer(AllIntegers::I64(0)), 8)); + + // All-Floats + assert!(matches!(identify_float(AllFloats::F32(0.0)), 0)); + assert!(matches!(identify_float(AllFloats::F64(0.0)), 1)); + + // All-Text + assert!(matches!(identify_text(AllTextParam::Char('a')), 0)); + assert!(matches!(identify_text(AllTextParam::String("abc")), 1)); + + // Duplicated + assert!(matches!(add_one_duplicated(DuplicatedS32::I320(0)), DuplicatedS32::I320(1))); + assert!(matches!(add_one_duplicated(DuplicatedS32::I321(1)), DuplicatedS32::I321(2))); + assert!(matches!(add_one_duplicated(DuplicatedS32::I322(2)), DuplicatedS32::I322(3))); + + assert!(matches!(identify_duplicated(DuplicatedS32::I320(0)), 0)); + assert!(matches!(identify_duplicated(DuplicatedS32::I321(0)), 1)); + assert!(matches!(identify_duplicated(DuplicatedS32::I321(0)), 2)); + + // Distinguishable + assert!(matches!(add_one_distinguishable_num(DistinguishableNum::F64(0.0)), DistinguishableNum::F64(1.0))); + assert!(matches!(add_one_distinguishable_num(DistinguishableNum::I64(0)), DistinguishableNum::I64(1))); + + assert!(matches!(identify_distinguishable_num(DistinguishableNum::F64(0.0)), 0)); + assert!(matches!(identify_distinguishable_num(DistinguishableNum::I64(1)), 1)); + } + + fn add_one_integer(num: AllIntegers) -> AllIntegers { + match num { + // Boolean + AllIntegers::Bool(b) => AllIntegers::Bool(!b), + // Unsigneed Integers + AllIntegers::U8(n) => AllIntegers::U8(n.wrapping_add(1)), + AllIntegers::U16(n) => AllIntegers::U16(n.wrapping_add(1)), + AllIntegers::U32(n) => AllIntegers::U32(n.wrapping_add(1)), + AllIntegers::U64(n) => AllIntegers::U64(n.wrapping_add(1)), + // Signed Integers + AllIntegers::I8(n) => AllIntegers::I8(n.wrapping_add(1)), + AllIntegers::I16(n) => AllIntegers::I16(n.wrapping_add(1)), + AllIntegers::I32(n) => AllIntegers::I32(n.wrapping_add(1)), + AllIntegers::I64(n) => AllIntegers::I64(n.wrapping_add(1)), + } + } + + fn add_one_float(num: AllFloats) -> AllFloats { + match num { + AllFloats::F32(n) => AllFloats::F32(n + 1.0), + AllFloats::F64(n) => AllFloats::F64(n + 1.0), + } + } + + fn replace_first_char(text: AllText, letter: char) -> AllText { + match text { + AllText::Char(c) => AllText::Char(letter), + AllText::String(s) => AllText::String(format!("{}{}", letter, &s[1..])) + } + } + + fn identify_integer(num: AllIntegers) -> u8 { + match num { + // Boolean + AllIntegers::Bool(_b) => 0, + // Unsigneed Integers + AllIntegers::U8(_n) => 1, + AllIntegers::U16(_n) => 2, + AllIntegers::U32(_n) => 3, + AllIntegers::U64(_n) => 4, + // Signed Integers + AllIntegers::I8(_n) => 5, + AllIntegers::I16(_n) => 6, + AllIntegers::I32(_n) => 7, + AllIntegers::I64(_n) => 8, + } + } + + fn identify_float(num: AllFloats) -> u8 { + match num { + AllFloats::F32(_n) => 0, + AllFloats::F64(_n) => 1, + } + } + + fn identify_text(text: AllText) -> u8 { + match text { + AllText::Char(_c) => 0, + AllText::String(_s) => 1 + } + } + + fn add_one_duplicated(num: DuplicatedS32) -> DuplicatedS32 { + match num { + DuplicatedS32::I320(n) => DuplicatedS32::I320(n.wrapping_add(1)), + DuplicatedS32::I321(n) => DuplicatedS32::I321(n.wrapping_add(1)), + DuplicatedS32::I322(n) => DuplicatedS32::I322(n.wrapping_add(1)), + } + } + + fn identify_duplicated(num: DuplicatedS32) -> u8 { + match num { + DuplicatedS32::I320(_n) => 0, + DuplicatedS32::I321(_n) => 1, + DuplicatedS32::I322(_n) => 2, + } + } + + fn add_one_distinguishable_num(num: DistinguishableNum) -> DistinguishableNum { + match num { + DistinguishableNum::F64(n) => DistinguishableNum::F64(n + 1.0), + DistinguishableNum::I64(n) => DistinguishableNum::I64(n.wrapping_add(1)), + } + } + + fn identify_distinguishable_num(num: DistinguishableNum) -> u8 { + match num { + DistinguishableNum::F64(_n) => 0, + DistinguishableNum::I64(_n) => 1, + } + } +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/exports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/exports.wit new file mode 100644 index 0000000..de0f943 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/exports.wit @@ -0,0 +1,30 @@ +test-imports: func() + +roundtrip-option: func(a: option) -> option +roundtrip-result: func(a: expected) -> expected + +enum e1 { a, b } +roundtrip-enum: func(a: e1) -> e1 + +invert-bool: func(a: bool) -> bool + +variant c1 { a(s32), b(s64) } +variant c2 { a(s32), b(float32) } +variant c3 { a(s32), b(float64) } +variant c4 { a(s64), b(float32) } +variant c5 { a(s64), b(float64) } +variant c6 { a(float32), b(float64) } +type casts = tuple +variant-casts: func(a: casts) -> casts + +variant z1 { a(s32), b } +variant z2 { a(s64), b } +variant z3 { a(float32), b } +variant z4 { a(float64), b } +type zeros = tuple +variant-zeros: func(a: zeros) -> zeros + +type option-typedef = option +type bool-typedef = bool +type result-typedef = expected +variant-typedefs: func(a: option-typedef, b: bool-typedef, c: result-typedef) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.py b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.py new file mode 100644 index 0000000..2fe1e7a --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.py @@ -0,0 +1,114 @@ +from exports.bindings import Exports +from imports.bindings import add_imports_to_linker, Imports +from typing import Optional, Tuple +import exports.bindings as e +import imports.bindings as i +import sys +import wasmtime + +class MyImports: + def roundtrip_option(self, a: Optional[float]) -> Optional[int]: + if a: + return int(a) + return None + + def roundtrip_result(self, a: i.Expected[int, float]) -> i.Expected[float, int]: + if isinstance(a, i.Ok): + return i.Ok(float(a.value)) + return i.Err(int(a.value)) + + def roundtrip_enum(self, a: i.E1) -> i.E1: + return a + + def invert_bool(self, a: bool) -> bool: + return not a + + def variant_casts(self, a: i.Casts) -> i.Casts: + return a + + def variant_zeros(self, a: i.Zeros) -> i.Zeros: + return a + + def variant_typedefs(self, a: i.OptionTypedef, b: i.BoolTypedef, c: i.ResultTypedef) -> None: + pass + + def variant_enums(self, a: bool, b: i.Expected[None, None], c: i.MyErrno) -> Tuple[bool, i.Expected[None, None], i.MyErrno]: + assert(a) + assert(isinstance(b, i.Ok)) + assert(c == i.MyErrno.SUCCESS) + return (False, i.Err(None), i.MyErrno.A) + +def run(wasm_file: str) -> None: + store = wasmtime.Store() + module = wasmtime.Module.from_file(store.engine, wasm_file) + linker = wasmtime.Linker(store.engine) + linker.define_wasi() + wasi = wasmtime.WasiConfig() + wasi.inherit_stdout() + wasi.inherit_stderr() + store.set_wasi(wasi) + + imports = MyImports() + add_imports_to_linker(linker, store, imports) + wasm = Exports(store, linker, module) + + wasm.test_imports(store) + + assert(wasm.roundtrip_option(store, 1.) == 1) + assert(wasm.roundtrip_option(store, None) == None) + assert(wasm.roundtrip_option(store, 2.) == 2) + assert(wasm.roundtrip_result(store, e.Ok(2)) == e.Ok(2)) + assert(wasm.roundtrip_result(store, e.Ok(4)) == e.Ok(4)) + assert(wasm.roundtrip_result(store, e.Err(5)) == e.Err(5)) + + assert(wasm.roundtrip_enum(store, e.E1.A) == e.E1.A) + assert(wasm.roundtrip_enum(store, e.E1.B) == e.E1.B) + + assert(wasm.invert_bool(store, True) == False) + assert(wasm.invert_bool(store, False) == True) + + a1, a2, a3, a4, a5, a6 = wasm.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 = wasm.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 = wasm.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)) + + wasm.variant_typedefs(store, None, False, e.Err(None)) + +if __name__ == '__main__': + run(sys.argv[1]) diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.rs new file mode 100644 index 0000000..8105384 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.rs @@ -0,0 +1,119 @@ +use anyhow::Result; + +wit_bindgen_wasmtime::export!("../../tests/runtime/variants/imports.wit"); + +use imports::*; + +#[derive(Default)] +pub struct MyImports; + +impl Imports for MyImports { + fn roundtrip_option(&mut self, a: Option) -> Option { + a.map(|x| x as u8) + } + + fn roundtrip_result(&mut self, a: Result) -> Result { + match a { + Ok(a) => Ok(a.into()), + Err(b) => Err(b as u8), + } + } + + fn roundtrip_enum(&mut self, a: E1) -> E1 { + assert_eq!(a, a); + a + } + + fn invert_bool(&mut self, a: bool) -> bool { + !a + } + + fn variant_casts(&mut self, a: Casts) -> Casts { + a + } + + fn variant_zeros(&mut self, a: Zeros) -> Zeros { + a + } + + fn variant_typedefs(&mut self, _: Option, _: bool, _: Result) {} + + fn variant_enums( + &mut self, + a: bool, + b: Result<(), ()>, + c: MyErrno, + ) -> (bool, Result<(), ()>, MyErrno) { + assert_eq!(a, true); + assert_eq!(b, Ok(())); + assert_eq!(c, MyErrno::Success); + (false, Err(()), MyErrno::A) + } +} + +wit_bindgen_wasmtime::import!("../../tests/runtime/variants/exports.wit"); + +fn run(wasm: &str) -> Result<()> { + use exports::*; + + let (exports, mut store) = crate::instantiate( + wasm, + |linker| imports::add_to_linker(linker, |cx| -> &mut MyImports { &mut cx.imports }), + |store, module, linker| Exports::instantiate(store, module, linker, |cx| &mut cx.exports), + )?; + + exports.test_imports(&mut store)?; + + assert_eq!(exports.roundtrip_option(&mut store, Some(1.0))?, Some(1)); + assert_eq!(exports.roundtrip_option(&mut store, None)?, None); + assert_eq!(exports.roundtrip_option(&mut store, Some(2.0))?, Some(2)); + assert_eq!(exports.roundtrip_result(&mut store, Ok(2))?, Ok(2.0)); + assert_eq!(exports.roundtrip_result(&mut store, Ok(4))?, Ok(4.0)); + assert_eq!(exports.roundtrip_result(&mut store, Err(5.3))?, Err(5)); + + assert_eq!(exports.roundtrip_enum(&mut store, E1::A)?, E1::A); + assert_eq!(exports.roundtrip_enum(&mut store, E1::B)?, E1::B); + + assert_eq!(exports.invert_bool(&mut store, true)?, false); + assert_eq!(exports.invert_bool(&mut store, false)?, true); + + let (a1, a2, a3, a4, a5, a6) = exports.variant_casts( + &mut store, + (C1::A(1), C2::A(2), C3::A(3), C4::A(4), C5::A(5), C6::A(6.0)), + )?; + assert!(matches!(a1, C1::A(1))); + assert!(matches!(a2, C2::A(2))); + assert!(matches!(a3, C3::A(3))); + assert!(matches!(a4, C4::A(4))); + assert!(matches!(a5, C5::A(5))); + assert!(matches!(a6, C6::A(b) if b == 6.0)); + + let (a1, a2, a3, a4, a5, a6) = exports.variant_casts( + &mut store, + ( + C1::B(1), + C2::B(2.0), + C3::B(3.0), + C4::B(4.0), + C5::B(5.0), + C6::B(6.0), + ), + )?; + assert!(matches!(a1, C1::B(1))); + assert!(matches!(a2, C2::B(b) if b == 2.0)); + assert!(matches!(a3, C3::B(b) if b == 3.0)); + assert!(matches!(a4, C4::B(b) if b == 4.0)); + assert!(matches!(a5, C5::B(b) if b == 5.0)); + assert!(matches!(a6, C6::B(b) if b == 6.0)); + + let (a1, a2, a3, a4) = + exports.variant_zeros(&mut store, (Z1::A(1), Z2::A(2), Z3::A(3.0), Z4::A(4.0)))?; + assert!(matches!(a1, Z1::A(1))); + assert!(matches!(a2, Z2::A(2))); + assert!(matches!(a3, Z3::A(b) if b == 3.0)); + assert!(matches!(a4, Z4::A(b) if b == 4.0)); + + exports.variant_typedefs(&mut store, None, false, Err(()))?; + + Ok(()) +} diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.ts b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.ts new file mode 100644 index 0000000..1948d00 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/host.ts @@ -0,0 +1,108 @@ +import { addImportsToImports, Imports, MyErrno } from "./imports.js"; +import { Exports } from "./exports.js"; +import * as exports from "./exports.js"; +import { getWasm, addWasiToImports } from "./helpers.js"; +// @ts-ignore +import * as assert from 'assert'; + +async function run() { + const importObj = {}; + const imports: Imports = { + roundtripOption(x) { return x; }, + roundtripResult(x) { + if (x.tag == 'ok') { + return { tag: 'ok', val: x.val }; + } else { + return { tag: 'err', val: Math.round(x.val) }; + } + }, + roundtripEnum(x) { return x; }, + invertBool(x) { return !x; }, + variantCasts(x) { return x; }, + variantZeros(x) { return x; }, + variantTypedefs(x, y, z) {}, + variantEnums(a, b, c) { + assert.deepStrictEqual(a, true); + assert.deepStrictEqual(b, { tag: 'ok', val: undefined }); + assert.deepStrictEqual(c, "success"); + return [ + false, + { tag: 'err', val: undefined }, + "a", + ]; + }, + }; + let instance: WebAssembly.Instance; + addImportsToImports(importObj, imports, name => instance.exports[name]); + const wasi = addWasiToImports(importObj); + + const wasm = new Exports(); + await wasm.instantiate(getWasm(), importObj); + wasi.start(wasm.instance); + instance = wasm.instance; + + wasm.testImports(); + assert.deepStrictEqual(wasm.roundtripOption(1), 1); + assert.deepStrictEqual(wasm.roundtripOption(null), null); + assert.deepStrictEqual(wasm.roundtripOption(2), 2); + assert.deepStrictEqual(wasm.roundtripResult({ tag: 'ok', val: 2 }), { tag: 'ok', val: 2 }); + assert.deepStrictEqual(wasm.roundtripResult({ tag: 'ok', val: 4 }), { tag: 'ok', val: 4 }); + const f = Math.fround(5.2); + assert.deepStrictEqual(wasm.roundtripResult({ tag: 'err', val: f }), { tag: 'err', val: 5 }); + + assert.deepStrictEqual(wasm.roundtripEnum("a"), "a"); + assert.deepStrictEqual(wasm.roundtripEnum("b"), "b"); + + assert.deepStrictEqual(wasm.invertBool(true), false); + assert.deepStrictEqual(wasm.invertBool(false), true); + + { + const [a1, a2, a3, a4, a5, a6] = wasm.variantCasts([ + { tag: 'a', val: 1 }, + { tag: 'a', val: 2 }, + { tag: 'a', val: 3 }, + { tag: 'a', val: 4n }, + { tag: 'a', val: 5n }, + { tag: 'a', val: 6 }, + ]); + assert.deepStrictEqual(a1, { tag: 'a', val: 1 }); + assert.deepStrictEqual(a2, { tag: 'a', val: 2 }); + assert.deepStrictEqual(a3, { tag: 'a', val: 3 }); + assert.deepStrictEqual(a4, { tag: 'a', val: 4n }); + assert.deepStrictEqual(a5, { tag: 'a', val: 5n }); + assert.deepStrictEqual(a6, { tag: 'a', val: 6 }); + } + { + const [b1, b2, b3, b4, b5, b6] = wasm.variantCasts([ + { tag: 'b', val: 1n }, + { tag: 'b', val: 2 }, + { tag: 'b', val: 3 }, + { tag: 'b', val: 4 }, + { tag: 'b', val: 5 }, + { tag: 'b', val: 6 }, + ]); + assert.deepStrictEqual(b1, { tag: 'b', val: 1n }); + assert.deepStrictEqual(b2, { tag: 'b', val: 2 }); + assert.deepStrictEqual(b3, { tag: 'b', val: 3 }); + assert.deepStrictEqual(b4, { tag: 'b', val: 4 }); + assert.deepStrictEqual(b5, { tag: 'b', val: 5 }); + assert.deepStrictEqual(b6, { tag: 'b', val: 6 }); + } + + { + const [a1, a2, a3, a4] = wasm.variantZeros([ + { tag: 'a', val: 1 }, + { tag: 'a', val: 2n }, + { tag: 'a', val: 3 }, + { tag: 'a', val: 4 }, + ]); + assert.deepStrictEqual(a1, { tag: 'a', val: 1 }); + assert.deepStrictEqual(a2, { tag: 'a', val: 2n }); + assert.deepStrictEqual(a3, { tag: 'a', val: 3 }); + assert.deepStrictEqual(a4, { tag: 'a', val: 4 }); + } + + wasm.variantTypedefs(null, false, { tag: 'err', val: undefined }); +} + +await run() diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/imports.wit b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/imports.wit new file mode 100644 index 0000000..5223d4d --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/imports.wit @@ -0,0 +1,31 @@ +roundtrip-option: func(a: option) -> option +roundtrip-result: func(a: expected) -> expected + +enum e1 { a, b } +roundtrip-enum: func(a: e1) -> e1 + +invert-bool: func(a: bool) -> bool + +variant c1 { a(s32), b(s64) } +variant c2 { a(s32), b(float32) } +variant c3 { a(s32), b(float64) } +variant c4 { a(s64), b(float32) } +variant c5 { a(s64), b(float64) } +variant c6 { a(float32), b(float64) } +type casts = tuple +variant-casts: func(a: casts) -> casts + +variant z1 { a(s32), b } +variant z2 { a(s64), b } +variant z3 { a(float32), b } +variant z4 { a(float64), b } +type zeros = tuple +variant-zeros: func(a: zeros) -> zeros + +type option-typedef = option +type bool-typedef = bool +type result-typedef = expected +variant-typedefs: func(a: option-typedef, b: bool-typedef, c: result-typedef) + +enum my-errno { success, a, b } +variant-enums: func(a: bool, b: expected, c: my-errno) -> tuple, my-errno> diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/wasm.c b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/wasm.c new file mode 100644 index 0000000..cf371e4 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/wasm.c @@ -0,0 +1,208 @@ +#include +#include +#include + +void exports_test_imports() { + { + imports_option_float32_t a; + uint8_t r; + a.is_some = true; + a.val = 1; + assert(imports_roundtrip_option(&a, &r) && r == 1); + assert(r == 1); + a.is_some = false; + assert(!imports_roundtrip_option(&a, &r)); + a.is_some = true; + a.val = 2; + assert(imports_roundtrip_option(&a, &r) && r == 2); + } + + + { + imports_expected_u32_float32_t a; + imports_expected_float64_u8_t b; + + a.is_err = false; + a.val.ok = 2; + imports_roundtrip_result(&a, &b); + assert(!b.is_err); + assert(b.val.ok == 2.0); + + a.val.ok = 4; + imports_roundtrip_result(&a, &b); + assert(!b.is_err); + assert(b.val.ok == 4); + + a.is_err = true; + a.val.err = 5.3; + imports_roundtrip_result(&a, &b); + assert(b.is_err); + assert(b.val.err == 5); + } + + assert(imports_roundtrip_enum(IMPORTS_E1_A) == IMPORTS_E1_A); + assert(imports_roundtrip_enum(IMPORTS_E1_B) == IMPORTS_E1_B); + + assert(imports_invert_bool(true) == false); + assert(imports_invert_bool(false) == true); + + { + imports_casts_t c; + imports_c1_t r1; + imports_c2_t r2; + imports_c3_t r3; + imports_c4_t r4; + imports_c5_t r5; + imports_c6_t r6; + c.f0.tag = IMPORTS_C1_A; + c.f0.val.a = 1; + c.f1.tag = IMPORTS_C2_A; + c.f1.val.a = 2; + c.f2.tag = IMPORTS_C3_A; + c.f2.val.a = 3; + c.f3.tag = IMPORTS_C4_A; + c.f3.val.a = 4; + c.f4.tag = IMPORTS_C5_A; + c.f4.val.a = 5; + c.f5.tag = IMPORTS_C6_A; + c.f5.val.a = 6; + imports_variant_casts(&c, &r1, &r2, &r3, &r4, &r5, &r6); + assert(r1.tag == IMPORTS_C1_A && r1.val.a == 1); + assert(r2.tag == IMPORTS_C2_A && r2.val.a == 2); + assert(r3.tag == IMPORTS_C3_A && r3.val.a == 3); + assert(r4.tag == IMPORTS_C4_A && r4.val.a == 4); + assert(r5.tag == IMPORTS_C5_A && r5.val.a == 5); + assert(r6.tag == IMPORTS_C6_A && r6.val.a == 6); + } + + { + imports_casts_t c; + imports_c1_t r1; + imports_c2_t r2; + imports_c3_t r3; + imports_c4_t r4; + imports_c5_t r5; + imports_c6_t r6; + c.f0.tag = IMPORTS_C1_B; + c.f0.val.b = 1; + c.f1.tag = IMPORTS_C2_B; + c.f1.val.b = 2; + c.f2.tag = IMPORTS_C3_B; + c.f2.val.b = 3; + c.f3.tag = IMPORTS_C4_B; + c.f3.val.b = 4; + c.f4.tag = IMPORTS_C5_B; + c.f4.val.b = 5; + c.f5.tag = IMPORTS_C6_B; + c.f5.val.b = 6; + imports_variant_casts(&c, &r1, &r2, &r3, &r4, &r5, &r6); + assert(r1.tag == IMPORTS_C1_B && r1.val.b == 1); + assert(r2.tag == IMPORTS_C2_B && r2.val.b == 2); + assert(r3.tag == IMPORTS_C3_B && r3.val.b == 3); + assert(r4.tag == IMPORTS_C4_B && r4.val.b == 4); + assert(r5.tag == IMPORTS_C5_B && r5.val.b == 5); + assert(r6.tag == IMPORTS_C6_B && r6.val.b == 6); + } + + { + imports_zeros_t c; + imports_z1_t r1; + imports_z2_t r2; + imports_z3_t r3; + imports_z4_t r4; + c.f0.tag = IMPORTS_Z1_A; + c.f0.val.a = 1; + c.f1.tag = IMPORTS_Z2_A; + c.f1.val.a = 2; + c.f2.tag = IMPORTS_Z3_A; + c.f2.val.a = 3; + c.f3.tag = IMPORTS_Z4_A; + c.f3.val.a = 4; + imports_variant_zeros(&c, &r1, &r2, &r3, &r4); + assert(r1.tag == IMPORTS_Z1_A && r1.val.a == 1); + assert(r2.tag == IMPORTS_Z2_A && r2.val.a == 2); + assert(r3.tag == IMPORTS_Z3_A && r3.val.a == 3); + assert(r4.tag == IMPORTS_Z4_A && r4.val.a == 4); + } + + { + imports_zeros_t c; + imports_z1_t r1; + imports_z2_t r2; + imports_z3_t r3; + imports_z4_t r4; + c.f0.tag = IMPORTS_Z1_B; + c.f1.tag = IMPORTS_Z2_B; + c.f2.tag = IMPORTS_Z3_B; + c.f3.tag = IMPORTS_Z4_B; + imports_variant_zeros(&c, &r1, &r2, &r3, &r4); + assert(r1.tag == IMPORTS_Z1_B); + assert(r2.tag == IMPORTS_Z2_B); + assert(r3.tag == IMPORTS_Z3_B); + assert(r4.tag == IMPORTS_Z4_B); + } + + { + imports_option_typedef_t a; + a.is_some = false; + bool b = false; + imports_result_typedef_t c; + c.is_err = true; + imports_variant_typedefs(&a, b, &c); + } + + { + bool a; + imports_expected_unit_unit_t b; + imports_my_errno_t c; + b.is_err = false; + imports_variant_enums(true, &b, IMPORTS_MY_ERRNO_SUCCESS, &a, &b, &c); + assert(a == false); + assert(b.is_err); + assert(c == IMPORTS_MY_ERRNO_A); + } +} + +bool exports_roundtrip_option(exports_option_float32_t *a, uint8_t *ret0) { + if (a->is_some) { + *ret0 = a->val; + } + return a->is_some; +} + +void exports_roundtrip_result(exports_expected_u32_float32_t *a, exports_expected_float64_u8_t *ret0) { + ret0->is_err = a->is_err; + if (a->is_err) { + ret0->val.err = a->val.err; + } else { + ret0->val.ok = a->val.ok; + } +} + +exports_e1_t exports_roundtrip_enum(exports_e1_t a) { + return a; +} + +bool exports_invert_bool(bool a) { + return !a; +} + +void exports_variant_casts(exports_casts_t *a, exports_c1_t *ret0, exports_c2_t *ret1, exports_c3_t *ret2, exports_c4_t *ret3, exports_c5_t *ret4, exports_c6_t *ret5) { + *ret0 = a->f0; + *ret1 = a->f1; + *ret2 = a->f2; + *ret3 = a->f3; + *ret4 = a->f4; + *ret5 = a->f5; +} + +void exports_variant_zeros(exports_zeros_t *a, exports_z1_t *ret0, exports_z2_t *ret1, exports_z3_t *ret2, exports_z4_t *ret3) { + *ret0 = a->f0; + *ret1 = a->f1; + *ret2 = a->f2; + *ret3 = a->f3; +} + +void exports_variant_typedefs(exports_option_typedef_t *a, exports_bool_typedef_t b, exports_result_typedef_t *c) { +} + diff --git a/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/wasm.rs b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/wasm.rs new file mode 100644 index 0000000..f75eb11 --- /dev/null +++ b/__wasm/wit-bindgen-sample/wit-bindgen/tests/runtime/variants/wasm.rs @@ -0,0 +1,98 @@ +wit_bindgen_rust::import!("../../tests/runtime/variants/imports.wit"); +wit_bindgen_rust::export!("../../tests/runtime/variants/exports.wit"); + +use exports::*; + +struct Exports; + +impl exports::Exports for Exports { + fn test_imports() { + use imports::*; + + assert_eq!(roundtrip_option(Some(1.0)), Some(1)); + assert_eq!(roundtrip_option(None), None); + assert_eq!(roundtrip_option(Some(2.0)), Some(2)); + assert_eq!(roundtrip_result(Ok(2)), Ok(2.0)); + assert_eq!(roundtrip_result(Ok(4)), Ok(4.0)); + assert_eq!(roundtrip_result(Err(5.3)), Err(5)); + + assert_eq!(roundtrip_enum(E1::A), E1::A); + assert_eq!(roundtrip_enum(E1::B), E1::B); + + assert_eq!(invert_bool(true), false); + assert_eq!(invert_bool(false), true); + + let (a1, a2, a3, a4, a5, a6) = + variant_casts((C1::A(1), C2::A(2), C3::A(3), C4::A(4), C5::A(5), C6::A(6.0))); + assert!(matches!(a1, C1::A(1))); + assert!(matches!(a2, C2::A(2))); + assert!(matches!(a3, C3::A(3))); + assert!(matches!(a4, C4::A(4))); + assert!(matches!(a5, C5::A(5))); + assert!(matches!(a6, C6::A(b) if b == 6.0)); + + let (a1, a2, a3, a4, a5, a6) = variant_casts(( + C1::B(1), + C2::B(2.0), + C3::B(3.0), + C4::B(4.0), + C5::B(5.0), + C6::B(6.0), + )); + assert!(matches!(a1, C1::B(1))); + assert!(matches!(a2, C2::B(b) if b == 2.0)); + assert!(matches!(a3, C3::B(b) if b == 3.0)); + assert!(matches!(a4, C4::B(b) if b == 4.0)); + assert!(matches!(a5, C5::B(b) if b == 5.0)); + assert!(matches!(a6, C6::B(b) if b == 6.0)); + + let (a1, a2, a3, a4) = variant_zeros((Z1::A(1), Z2::A(2), Z3::A(3.0), Z4::A(4.0))); + assert!(matches!(a1, Z1::A(1))); + assert!(matches!(a2, Z2::A(2))); + assert!(matches!(a3, Z3::A(b) if b == 3.0)); + assert!(matches!(a4, Z4::A(b) if b == 4.0)); + + let (a1, a2, a3, a4) = variant_zeros((Z1::B, Z2::B, Z3::B, Z4::B)); + assert!(matches!(a1, Z1::B)); + assert!(matches!(a2, Z2::B)); + assert!(matches!(a3, Z3::B)); + assert!(matches!(a4, Z4::B)); + + variant_typedefs(None, false, Err(())); + + assert_eq!( + variant_enums(true, Ok(()), MyErrno::Success), + (false, Err(()), MyErrno::A) + ); + } + + fn roundtrip_option(a: Option) -> Option { + a.map(|x| x as u8) + } + + fn roundtrip_result(a: Result) -> Result { + match a { + Ok(a) => Ok(a.into()), + Err(b) => Err(b as u8), + } + } + + fn roundtrip_enum(a: E1) -> E1 { + assert_eq!(a, a); + a + } + + fn invert_bool(a: bool) -> bool { + !a + } + + fn variant_casts(a: Casts) -> Casts { + a + } + + fn variant_zeros(a: Zeros) -> Zeros { + a + } + + fn variant_typedefs(_: Option, _: bool, _: Result) {} +}